From bb932910dd0c764d323789ebb9a8c214059bbd1d Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Thu, 22 May 2014 20:04:53 +0000 Subject: [PATCH] Initial support for MAP-E and Lightweight 4over6 protocol SVN-Revision: 40823 --- package/network/ipv6/map/Makefile | 44 +++ package/network/ipv6/map/files/map.sh | 160 +++++++++ package/network/ipv6/map/src/CMakeLists.txt | 26 ++ package/network/ipv6/map/src/mapcalc.c | 367 ++++++++++++++++++++ 4 files changed, 597 insertions(+) create mode 100644 package/network/ipv6/map/Makefile create mode 100755 package/network/ipv6/map/files/map.sh create mode 100644 package/network/ipv6/map/src/CMakeLists.txt create mode 100644 package/network/ipv6/map/src/mapcalc.c diff --git a/package/network/ipv6/map/Makefile b/package/network/ipv6/map/Makefile new file mode 100644 index 0000000000..f965afd6d3 --- /dev/null +++ b/package/network/ipv6/map/Makefile @@ -0,0 +1,44 @@ +# +# Copyright (C) 2014 OpenWrt.org +# +# This is free software, licensed under the GNU General Public License v2. +# See /LICENSE for more information. +# + +include $(TOPDIR)/rules.mk + +PKG_NAME:=map +PKG_VERSION:=1 +PKG_RELEASE:=1 + +include $(INCLUDE_DIR)/package.mk +include $(INCLUDE_DIR)/cmake.mk + +define Package/map + SECTION:=net + CATEGORY:=Network + DEPENDS:=+kmod-ipv6 +kmod-ip6-tunnel +libubox +libubus +iptables-mod-conntrack-extra + TITLE:=MAP-E and Lightweight 4over6 configuration support + MAINTAINER:=Steven Barth +endef + +define Package/map/description + Provides support for MAP-E (draft-ietf-softwire-map) and + Lightweight 4over6 (draft-ietf-softwire-lw4over6) in /etc/config/network. + Refer to http://wiki.openwrt.org/doc/uci/network for + configuration details. +endef + +define Build/Prepare + mkdir -p $(PKG_BUILD_DIR) + $(CP) ./src/* $(PKG_BUILD_DIR)/ +endef + +define Package/map/install + $(INSTALL_DIR) $(1)/lib/netifd/proto + $(INSTALL_BIN) ./files/map.sh $(1)/lib/netifd/proto/map.sh + $(INSTALL_DIR) $(1)/usr/sbin + $(INSTALL_BIN) $(PKG_BUILD_DIR)/mapcalc $(1)/usr/sbin/ +endef + +$(eval $(call BuildPackage,map)) diff --git a/package/network/ipv6/map/files/map.sh b/package/network/ipv6/map/files/map.sh new file mode 100755 index 0000000000..906829be06 --- /dev/null +++ b/package/network/ipv6/map/files/map.sh @@ -0,0 +1,160 @@ +#!/bin/sh +# map.sh - IPv4-in-IPv6 tunnel backend +# +# Author: Steven Barth +# Copyright (c) 2014 cisco Systems, Inc. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +[ -n "$INCLUDE_ONLY" ] || { + . /lib/functions.sh + . /lib/functions/network.sh + . ../netifd-proto.sh + init_proto "$@" +} + +proto_map_setup() { + local cfg="$1" + local iface="$2" + local link="map-$cfg" + + # uncomment for legacy MAP0 mode + #export LEGACY=1 + + local type mtu ttl tunlink zone + local rule ipaddr ip4prefixlen ip6prefix ip6prefixlen peeraddr ealen psidlen psid offset + json_get_vars type mtu ttl tunlink zone + json_get_vars rule ipaddr ip4prefixlen ip6prefix ip6prefixlen peeraddr ealen psidlen psid offset + + [ -z "$zone" ] && zone="wan" + [ -z "$type" ] && type="map-e" + [ -z "$ip4prefixlen" ] && ip4prefixlen=32 + + ( proto_add_host_dependency "$cfg" "::" "$tunlink" ) + + if [ -z "$rule" ]; then + rule="type=$type,ipv6prefix=$ip6prefix,prefix6len=$ip6prefixlen,ipv4prefix=$ipaddr,prefix4len=$ip4prefixlen" + [ -n "$psid" ] && rule="$rule,psid=$psid" + [ -n "$psidlen" ] && rule="$rule,psidlen=$psidlen" + [ -n "$offset" ] && rule="$rule,offset=$offset" + [ -n "$ealen" ] && rule="$rule,ealen=$ealen" + rule="$rule,br=$peeraddr" + fi + + RULE_DATA=$(mapcalc ${tunlink:-\*} $rule) + if [ "$?" != 0 ]; then + proto_notify_error "$cfg" "INVALID_MAP_RULE" + proto_block_restart "$cfg" + return + fi + + eval $RULE_DATA + + if [ -z "$RULE_BMR" ]; then + proto_notify_error "$cfg" "NO_MATCHING_PD" + proto_block_restart "$cfg" + return + fi + + k=$RULE_BMR + if [ "$type" = "lw4o6" -o "$type" = "map-e" ]; then + proto_init_update "$link" 1 + proto_add_ipv4_address $(eval "echo \$RULE_${k}_IPV4ADDR") "" "" "" + + proto_add_tunnel + json_add_string mode ipip6 + json_add_int mtu "${mtu:-1280}" + json_add_int ttl "${ttl:-64}" + json_add_string local $(eval "echo \$RULE_${k}_IPV6ADDR") + json_add_string remote $(eval "echo \$RULE_${k}_BR") + json_add_string link $(eval "echo \$RULE_${k}_PD6IFACE") + + if [ "$type" = "map-e" ]; then + json_add_array "fmrs" + for i in $(seq $RULE_COUNT); do + [ "$(eval "echo \$RULE_${i}_FMR")" != 1 ] && continue + fmr="$(eval "echo \$RULE_${i}_IPV6PREFIX")/$(eval "echo \$RULE_${i}_PREFIX6LEN")" + fmr="$fmr,$(eval "echo \$RULE_${i}_IPV4PREFIX")/$(eval "echo \$RULE_${i}_PREFIX4LEN")" + fmr="$fmr,$(eval "echo \$RULE_${i}_EALEN"),$(eval "echo \$RULE_${i}_OFFSET")" + json_add_string "" "$fmr" + done + json_close_array + fi + + proto_close_tunnel + else + proto_notify_error "$cfg" "UNSUPPORTED_TYPE" + proto_block_restart "$cfg" + fi + + proto_add_ipv4_route "0.0.0.0" 0 + proto_add_data + [ "$zone" != "-" ] && json_add_string zone "$zone" + + json_add_array firewall + for portset in $(eval "echo \$RULE_${k}_PORTSETS"); do + for proto in icmp tcp udp; do + json_add_object "" + json_add_string type nat + json_add_string target SNAT + json_add_string family inet + json_add_string proto "$proto" + json_add_boolean connlimit_ports 1 + json_add_string snat_ip $(eval "echo \$RULE_${k}_IPV4ADDR") + json_add_string snat_port "$portset" + json_close_object + done + done + json_close_array + proto_close_data + + proto_send_update "$cfg" + + if [ "$type" = "lw4o6" -o "$type" = "map-e" ]; then + json_init + json_add_string name "${cfg}_local" + json_add_string ifname "@$(eval "echo \$RULE_${k}_PD6IFACE")" + json_add_string proto "static" + json_add_array ip6addr + json_add_string "" "$(eval "echo \$RULE_${k}_IPV6ADDR")" + json_close_array + json_close_object + ubus call network add_dynamic "$(json_dump)" + fi +} + +proto_map_teardown() { + local cfg="$1" + ifdown "${cfg}_local" +} + +proto_map_init_config() { + no_device=1 + available=1 + + proto_config_add_string "rule" + proto_config_add_string "ipaddr" + proto_config_add_int "ip4prefixlen" + proto_config_add_string "ip6prefix" + proto_config_add_int "ip6prefixlen" + proto_config_add_string "peeraddr" + proto_config_add_int "psidlen" + proto_config_add_int "psid" + proto_config_add_int "offset" + + proto_config_add_string "tunlink" + proto_config_add_int "mtu" + proto_config_add_int "ttl" + proto_config_add_string "zone" +} + +[ -n "$INCLUDE_ONLY" ] || { + add_protocol map +} diff --git a/package/network/ipv6/map/src/CMakeLists.txt b/package/network/ipv6/map/src/CMakeLists.txt new file mode 100644 index 0000000000..2cbeb2c261 --- /dev/null +++ b/package/network/ipv6/map/src/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 2.8.1) + +project(mapcalc C) + +set(CMAKE_SHARED_LIBRARY_LINK_C_FLAGS "") +set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -std=c99") + +add_definitions(-D_GNU_SOURCE -Wall -Wno-gnu -Wextra) + +add_executable(mapcalc mapcalc.c) +target_link_libraries(mapcalc ubus ubox) + +install(TARGETS mapcalc DESTINATION sbin/) + + +# Packaging rules +set(CPACK_PACKAGE_VERSION "1") +set(CPACK_PACKAGE_CONTACT "Steven Barth ") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "hnetd") +set(CPACK_GENERATOR "DEB;RPM;STGZ") +set(CPACK_STRIP_FILES true) + +SET(CPACK_DEBIAN_PACKAGE_VERSION ${CPACK_PACKAGE_VERSION}) +set(CPACK_PACKAGE_FILE_NAME "${PROJECT_NAME}_${CPACK_DEBIAN_PACKAGE_VERSION}") + +include(CPack) diff --git a/package/network/ipv6/map/src/mapcalc.c b/package/network/ipv6/map/src/mapcalc.c new file mode 100644 index 0000000000..03f8165be0 --- /dev/null +++ b/package/network/ipv6/map/src/mapcalc.c @@ -0,0 +1,367 @@ +/* + * mapcalc - MAP parameter calculation + * + * Author: Steven Barth + * Copyright (c) 2014 cisco Systems, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include + + +struct blob_attr *dump = NULL; + +enum { + DUMP_ATTR_INTERFACE, + DUMP_ATTR_MAX +}; + +static const struct blobmsg_policy dump_attrs[DUMP_ATTR_MAX] = { + [DUMP_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_ARRAY }, +}; + + +enum { + IFACE_ATTR_INTERFACE, + IFACE_ATTR_PREFIX, + IFACE_ATTR_MAX, +}; + +static const struct blobmsg_policy iface_attrs[IFACE_ATTR_MAX] = { + [IFACE_ATTR_INTERFACE] = { .name = "interface", .type = BLOBMSG_TYPE_STRING }, + [IFACE_ATTR_PREFIX] = { .name = "ipv6-prefix", .type = BLOBMSG_TYPE_ARRAY }, +}; + + +enum { + PREFIX_ATTR_ADDRESS, + PREFIX_ATTR_MASK, + PREFIX_ATTR_MAX, +}; + +static const struct blobmsg_policy prefix_attrs[PREFIX_ATTR_MAX] = { + [PREFIX_ATTR_ADDRESS] = { .name = "address", .type = BLOBMSG_TYPE_STRING }, + [PREFIX_ATTR_MASK] = { .name = "mask", .type = BLOBMSG_TYPE_INT32 }, +}; + +static int bmemcmp(const void *av, const void *bv, size_t bits) +{ + const uint8_t *a = av, *b = bv; + size_t bytes = bits / 8; + bits %= 8; + + int res = memcmp(a, b, bytes); + if (res == 0 && bits > 0) + res = (a[bytes] >> (8 - bits)) - (b[bytes] >> (8 - bits)); + + return res; +} + +static void bmemcpy(void *av, const void *bv, size_t bits) +{ + uint8_t *a = av; + const uint8_t *b = bv; + + size_t bytes = bits / 8; + bits %= 8; + memcpy(a, b, bytes); + + if (bits > 0) { + uint8_t mask = (1 << (8 - bits)) - 1; + a[bytes] = (a[bytes] & mask) | ((~mask) & b[bytes]); + } +} + +static void bmemcpys64(void *av, const void *bv, size_t frombits, size_t nbits) +{ + uint64_t buf = 0; + const uint8_t *b = bv; + size_t frombyte = frombits / 8, tobyte = (frombits + nbits) / 8; + + memcpy(&buf, &b[frombyte], tobyte - frombyte + 1); + buf = cpu_to_be64(be64_to_cpu(buf) << (frombits % 8)); + + bmemcpy(av, &buf, nbits); +} + +static void handle_dump(struct ubus_request *req __attribute__((unused)), + int type __attribute__((unused)), struct blob_attr *msg) +{ + struct blob_attr *tb[DUMP_ATTR_INTERFACE]; + blobmsg_parse(dump_attrs, DUMP_ATTR_MAX, tb, blob_data(msg), blob_len(msg)); + + if (!tb[DUMP_ATTR_INTERFACE]) + return; + + dump = blob_memdup(tb[DUMP_ATTR_INTERFACE]); +} + +enum { + OPT_TYPE, + OPT_FMR, + OPT_EALEN, + OPT_PREFIX4LEN, + OPT_PREFIX6LEN, + OPT_IPV6PREFIX, + OPT_IPV4PREFIX, + OPT_OFFSET, + OPT_PSIDLEN, + OPT_PSID, + OPT_BR, + OPT_DMR, + OPT_MAX +}; + +static char *const token[] = { + [OPT_TYPE] = "type", + [OPT_FMR] = "fmr", + [OPT_EALEN] = "ealen", + [OPT_PREFIX4LEN] = "prefix4len", + [OPT_PREFIX6LEN] = "prefix6len", + [OPT_IPV6PREFIX] = "ipv6prefix", + [OPT_IPV4PREFIX] = "ipv4prefix", + [OPT_OFFSET] = "offset", + [OPT_PSIDLEN] = "psidlen", + [OPT_PSID] = "psid", + [OPT_BR] = "br", + [OPT_DMR] = "dmr", + [OPT_MAX] = NULL +}; + + +int main(int argc, char *argv[]) +{ + int status = 0; + const char *iface = argv[1]; + + const char *legacy_env = getenv("LEGACY"); + bool legacy = legacy_env && atoi(legacy_env); + + + if (argc < 3) { + fprintf(stderr, "Usage: %s [rule2] [...]\n", argv[0]); + return 1; + } + + uint32_t network_interface; + struct ubus_context *ubus = ubus_connect(NULL); + if (ubus) { + ubus_lookup_id(ubus, "network.interface", &network_interface); + ubus_invoke(ubus, network_interface, "dump", NULL, handle_dump, NULL, 5000); + } + + int rulecnt = 0; + for (int i = 2; i < argc; ++i) { + bool lw4o6 = false; + bool fmr = false; + int ealen = -1; + int addr4len = 32; + int prefix4len = 32; + int prefix6len = -1; + int pdlen = -1; + struct in_addr ipv4prefix = {INADDR_ANY}; + struct in_addr ipv4addr = {INADDR_ANY}; + struct in6_addr ipv6addr = IN6ADDR_ANY_INIT; + struct in6_addr ipv6prefix = IN6ADDR_ANY_INIT; + struct in6_addr pd = IN6ADDR_ANY_INIT; + int offset = -1; + int psidlen = -1; + int psid = -1; + uint16_t psid16 = 0; + const char *dmr = NULL; + const char *br = NULL; + + for (char *rule = strdup(argv[i]); *rule; ) { + char *value; + int intval; + int idx = getsubopt(&rule, token, &value); + errno = 0; + + if (idx == OPT_TYPE) { + lw4o6 = (value && !strcmp(value, "lw4o6")); + } else if (idx == OPT_FMR) { + fmr = true; + } else if (idx == OPT_EALEN && (intval = strtoul(value, NULL, 0)) <= 48 && !errno) { + ealen = intval; + } else if (idx == OPT_PREFIX4LEN && (intval = strtoul(value, NULL, 0)) <= 32 && !errno) { + prefix4len = intval; + } else if (idx == OPT_PREFIX6LEN && (intval = strtoul(value, NULL, 0)) <= 112 && !errno) { + prefix6len = intval; + } else if (idx == OPT_IPV4PREFIX && inet_pton(AF_INET, value, &ipv4prefix) == 1) { + // dummy + } else if (idx == OPT_IPV6PREFIX && inet_pton(AF_INET6, value, &ipv6prefix) == 1) { + // dummy + } else if (idx == OPT_OFFSET && (intval = strtoul(value, NULL, 0)) <= 16 && !errno) { + offset = intval; + } else if (idx == OPT_PSIDLEN && (intval = strtoul(value, NULL, 0)) <= 16 && !errno) { + psidlen = intval; + } else if (idx == OPT_PSID && (intval = strtoul(value, NULL, 0)) <= 65535 && !errno) { + psid = intval; + } else if (idx == OPT_DMR) { + dmr = value; + } else if (idx == OPT_BR) { + br = value; + } else { + if (idx == -1 || idx >= OPT_MAX) + fprintf(stderr, "Skipped invalid option: %s\n", value); + else + fprintf(stderr, "Skipped invalid value %s for option %s\n", + value, token[idx]); + } + } + + if (offset < 0) + offset = (lw4o6) ? 0 : (legacy) ? 4 : 6; + + // Find PD + struct blob_attr *c, *cur; + unsigned rem; + blobmsg_for_each_attr(c, dump, rem) { + struct blob_attr *tb[IFACE_ATTR_MAX]; + blobmsg_parse(iface_attrs, IFACE_ATTR_MAX, tb, blobmsg_data(c), blobmsg_data_len(c)); + + if (!tb[IFACE_ATTR_INTERFACE] || (strcmp(argv[1], "*") && strcmp(argv[1], + blobmsg_get_string(tb[IFACE_ATTR_INTERFACE])))) + continue; + + if ((cur = tb[IFACE_ATTR_PREFIX])) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, NULL)) + continue; + + struct blob_attr *d; + unsigned drem; + blobmsg_for_each_attr(d, cur, drem) { + struct blob_attr *ptb[PREFIX_ATTR_MAX]; + blobmsg_parse(prefix_attrs, PREFIX_ATTR_MAX, ptb, + blobmsg_data(d), blobmsg_data_len(d)); + + if (!ptb[PREFIX_ATTR_ADDRESS] || !ptb[PREFIX_ATTR_MASK]) + continue; + + struct in6_addr prefix = IN6ADDR_ANY_INIT; + int mask = blobmsg_get_u32(ptb[PREFIX_ATTR_MASK]); + inet_pton(AF_INET6, blobmsg_get_string(ptb[PREFIX_ATTR_ADDRESS]), &prefix); + + if (mask >= prefix6len && !bmemcmp(&prefix, &ipv6prefix, prefix6len)) { + pd = prefix; + pdlen = mask; + iface = blobmsg_get_string(tb[IFACE_ATTR_INTERFACE]); + break; + } + } + + if (pdlen >= 0) + break; + } + } + + if (ealen < 0 && pdlen >= 0) + ealen = pdlen - prefix6len; + + if (psidlen < 0) + psidlen = ealen - (32 - prefix4len); + + if (psid < 0 && psidlen <= 16 && psidlen >= 0 && pdlen >= 0 && ealen >= psidlen) { + bmemcpys64(&psid16, &pd, prefix6len + ealen - psidlen, psidlen); + psid = be16_to_cpu(psid16); + } + + psid16 = cpu_to_be16(psid >> (16 - psidlen)); + + if ((pdlen >= 0 || ealen == psidlen) && ealen >= psidlen) { + bmemcpys64(&ipv4addr, &pd, prefix6len, ealen - psidlen); + ipv4addr.s_addr = htonl(ntohl(ipv4addr.s_addr) >> prefix4len); + bmemcpy(&ipv4addr, &ipv4prefix, prefix4len); + + if (prefix4len + ealen < 32) + addr4len = prefix4len + ealen; + } + + if (prefix4len < 0 || prefix6len < 0 || ealen < 0 || ealen < psidlen) { + fprintf(stderr, "Skipping invalid or incomplete rule: %s\n", argv[i]); + status = 1; + continue; + } + + if (pdlen < 0 && !fmr) { + fprintf(stderr, "Skipping non-FMR without matching PD: %s\n", argv[i]); + status = 1; + continue; + } else if (pdlen >= 0) { + size_t v4offset = (legacy) ? 9 : 10; + memcpy(&ipv6addr.s6_addr[v4offset], &ipv4addr, 4); + memcpy(&ipv6addr.s6_addr[v4offset + 4], &psid16, 2); + bmemcpy(&ipv6addr, &pd, pdlen); + } + + ++rulecnt; + char ipv4addrbuf[INET_ADDRSTRLEN]; + char ipv4prefixbuf[INET_ADDRSTRLEN]; + char ipv6prefixbuf[INET6_ADDRSTRLEN]; + char ipv6addrbuf[INET6_ADDRSTRLEN]; + char pdbuf[INET6_ADDRSTRLEN]; + + inet_ntop(AF_INET, &ipv4addr, ipv4addrbuf, sizeof(ipv4addrbuf)); + inet_ntop(AF_INET, &ipv4prefix, ipv4prefixbuf, sizeof(ipv4prefixbuf)); + inet_ntop(AF_INET6, &ipv6prefix, ipv6prefixbuf, sizeof(ipv6prefixbuf)); + inet_ntop(AF_INET6, &ipv6addr, ipv6addrbuf, sizeof(ipv6addrbuf)); + inet_ntop(AF_INET6, &pd, pdbuf, sizeof(pdbuf)); + + printf("RULE_%d_FMR=%d\n", rulecnt, fmr); + printf("RULE_%d_EALEN=%d\n", rulecnt, ealen); + printf("RULE_%d_PSIDLEN=%d\n", rulecnt, psidlen); + printf("RULE_%d_OFFSET=%d\n", rulecnt, offset); + printf("RULE_%d_PREFIX4LEN=%d\n", rulecnt, prefix4len); + printf("RULE_%d_PREFIX6LEN=%d\n", rulecnt, prefix6len); + printf("RULE_%d_IPV4PREFIX=%s\n", rulecnt, ipv4prefixbuf); + printf("RULE_%d_IPV6PREFIX=%s\n", rulecnt, ipv6prefixbuf); + + if (pdlen >= 0) { + printf("RULE_%d_IPV6PD=%s\n", rulecnt, pdbuf); + printf("RULE_%d_PD6LEN=%d\n", rulecnt, pdlen); + printf("RULE_%d_PD6IFACE=%s\n", rulecnt, iface); + printf("RULE_%d_IPV6ADDR=%s\n", rulecnt, ipv6addrbuf); + printf("RULE_BMR=%d\n", rulecnt); + } + + if (ipv4addr.s_addr) { + printf("RULE_%d_IPV4ADDR=%s\n", rulecnt, ipv4addrbuf); + printf("RULE_%d_ADDR4LEN=%d\n", rulecnt, addr4len); + } + + + if (psidlen == 0) { + printf("RULE_%d_PORTSETS=0-65535\n", rulecnt); + } else if (psid >= 0) { + printf("RULE_%d_PORTSETS='", rulecnt); + for (int k = (offset) ? 1 : 0; k < (1 << offset); ++k) { + int start = (k << (16 - offset)) | (psid >> offset); + int end = start + (1 << (16 - offset - psidlen)) - 1; + printf("%d-%d ", start, end); + } + printf("'\n"); + } + + if (dmr) + printf("RULE_%d_DMR=%s\n", rulecnt, dmr); + + if (br) + printf("RULE_%d_BR=%s\n", rulecnt, br); + } + + printf("RULE_COUNT=%d\n", rulecnt); + return status; +}