--- /dev/null
+#!/bin/sh
+# map.sh - IPv4-in-IPv6 tunnel backend
+#
+# Author: Steven Barth <cyrus@openwrt.org>
+# 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
+}
--- /dev/null
+/*
+ * mapcalc - MAP parameter calculation
+ *
+ * Author: Steven Barth <cyrus@openwrt.org>
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <arpa/inet.h>
+#include <errno.h>
+#include <libubus.h>
+#include <libubox/utils.h>
+
+
+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 <interface|*> <rule1> [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;
+}