ASR_BASE

Change-Id: Icf3719cc0afe3eeb3edc7fa80a2eb5199ca9dda1
diff --git a/package/network/ipv6/map/Makefile b/package/network/ipv6/map/Makefile
new file mode 100644
index 0000000..f73b5ee
--- /dev/null
+++ b/package/network/ipv6/map/Makefile
@@ -0,0 +1,40 @@
+#
+# Copyright (C) 2014-2015 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_RELEASE:=7
+PKG_LICENSE:=GPL-2.0
+
+include $(INCLUDE_DIR)/package.mk
+include $(INCLUDE_DIR)/cmake.mk
+
+define Package/map
+  SECTION:=net
+  CATEGORY:=Network
+  DEPENDS:=@IPV6 +kmod-ip6-tunnel +libubox +libubus +iptables-mod-conntrack-extra +kmod-nat46
+  TITLE:=MAP-E/MAP-T and Lightweight 4over6 configuration support
+  MAINTAINER:=Hans Dedecker <dedeckeh@gmail.com>
+  PROVIDES:=map-t
+endef
+
+define Package/map/description
+ Provides support for MAP-E (RFC7597), MAP-T (RFC7599) and
+ Lightweight 4over6 (RFC7596) in /etc/config/network.
+ MAP combines address and port translation with the tunneling
+ of IPv4 packets over an IPv6 network
+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 0000000..652c306
--- /dev/null
+++ b/package/network/ipv6/map/files/map.sh
@@ -0,0 +1,245 @@
+#!/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"
+
+	local maptype type legacymap mtu ttl tunlink zone encaplimit
+	local rule ipaddr ip4prefixlen ip6prefix ip6prefixlen peeraddr ealen psidlen psid offset
+	json_get_vars maptype type legacymap mtu ttl tunlink zone encaplimit
+	json_get_vars rule ipaddr ip4prefixlen ip6prefix ip6prefixlen peeraddr ealen psidlen psid offset
+
+	[ "$zone" = "-" ] && zone=""
+
+	# Compatibility with older config: use $type if $maptype is missing
+	[ -z "$maptype" ] && maptype="$type"
+	[ -z "$maptype" ] && maptype="map-e"
+	[ -z "$ip4prefixlen" ] && ip4prefixlen=32
+
+	( proto_add_host_dependency "$cfg" "::" "$tunlink" )
+
+	# fixme: handle RA/DHCPv6 address race for LW
+	[ "$maptype" = lw4o6 ] && sleep 5
+
+	if [ -z "$rule" ]; then
+		rule="type=$maptype,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"
+		if [ "$maptype" = "map-t" ]; then
+			rule="$rule,dmr=$peeraddr"
+		else
+			rule="$rule,br=$peeraddr"
+		fi
+	fi
+
+	echo "rule=$rule" > /tmp/map-$cfg.rules
+	RULE_DATA=$(LEGACY="$legacymap" mapcalc ${tunlink:-\*} $rule)
+	if [ "$?" != 0 ]; then
+		proto_notify_error "$cfg" "INVALID_MAP_RULE"
+		proto_block_restart "$cfg"
+		return
+	fi
+
+	echo "$RULE_DATA" >> /tmp/map-$cfg.rules
+	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 [ "$maptype" = "lw4o6" -o "$maptype" = "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")
+		json_add_object "data"
+			[ -n "$encaplimit" ] && json_add_string encaplimit "$encaplimit"
+			if [ "$maptype" = "map-e" ]; then
+				json_add_array "fmrs"
+				for i in $(seq $RULE_COUNT); do
+					[ "$(eval "echo \$RULE_${i}_FMR")" != 1 ] && continue
+					json_add_object ""
+					json_add_string prefix6 "$(eval "echo \$RULE_${i}_IPV6PREFIX")/$(eval "echo \$RULE_${i}_PREFIX6LEN")"
+					json_add_string prefix4 "$(eval "echo \$RULE_${i}_IPV4PREFIX")/$(eval "echo \$RULE_${i}_PREFIX4LEN")"
+					json_add_int ealen $(eval "echo \$RULE_${i}_EALEN")
+					json_add_int offset $(eval "echo \$RULE_${i}_OFFSET")
+					json_close_object
+				done
+				json_close_array
+			fi
+		json_close_object
+
+
+		proto_close_tunnel
+	elif [ "$maptype" = "map-t" -a -f "/proc/net/nat46/control" ]; then
+		proto_init_update "$link" 1
+		local style="MAP"
+		[ "$legacymap" = 1 ] && style="MAP0"
+
+		echo add $link > /proc/net/nat46/control
+		local cfgstr="local.style $style local.v4 $(eval "echo \$RULE_${k}_IPV4PREFIX")/$(eval "echo \$RULE_${k}_PREFIX4LEN")"
+		cfgstr="$cfgstr local.v6 $(eval "echo \$RULE_${k}_IPV6PREFIX")/$(eval "echo \$RULE_${k}_PREFIX6LEN")"
+		cfgstr="$cfgstr local.ea-len $(eval "echo \$RULE_${k}_EALEN") local.psid-offset $(eval "echo \$RULE_${k}_OFFSET")"
+		cfgstr="$cfgstr remote.v4 0.0.0.0/0 remote.v6 $(eval "echo \$RULE_${k}_DMR") remote.style RFC6052 remote.ea-len 0 remote.psid-offset 0"
+		echo config $link $cfgstr > /proc/net/nat46/control
+
+		for i in $(seq $RULE_COUNT); do
+			[ "$(eval "echo \$RULE_${i}_FMR")" != 1 ] && continue
+			local cfgstr="remote.style $style remote.v4 $(eval "echo \$RULE_${i}_IPV4PREFIX")/$(eval "echo \$RULE_${i}_PREFIX4LEN")"
+			cfgstr="$cfgstr remote.v6 $(eval "echo \$RULE_${i}_IPV6PREFIX")/$(eval "echo \$RULE_${i}_PREFIX6LEN")"
+			cfgstr="$cfgstr remote.ea-len $(eval "echo \$RULE_${i}_EALEN") remote.psid-offset $(eval "echo \$RULE_${i}_OFFSET")"
+			echo insert $link $cfgstr > /proc/net/nat46/control
+		done
+	else
+		proto_notify_error "$cfg" "UNSUPPORTED_TYPE"
+		proto_block_restart "$cfg"
+	fi
+
+	proto_add_ipv4_route "0.0.0.0" 0
+	proto_add_data
+	[ -n "$zone" ] && json_add_string zone "$zone"
+
+	json_add_array firewall
+	  if [ -z "$(eval "echo \$RULE_${k}_PORTSETS")" ]; then
+	    json_add_object ""
+	      json_add_string type nat
+	      json_add_string target SNAT
+	      json_add_string family inet
+	      json_add_string snat_ip $(eval "echo \$RULE_${k}_IPV4ADDR")
+	    json_close_object
+	  else
+	    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
+	  fi
+	  if [ "$maptype" = "map-t" ]; then
+		[ -z "$zone" ] && zone=$(fw3 -q network $iface 2>/dev/null)
+
+		[ -n "$zone" ] && {
+			json_add_object ""
+				json_add_string type rule
+				json_add_string family inet6
+				json_add_string proto all
+				json_add_string direction in
+				json_add_string dest "$zone"
+				json_add_string src "$zone"
+				json_add_string src_ip $(eval "echo \$RULE_${k}_IPV6ADDR")
+				json_add_string target ACCEPT
+			json_close_object
+			json_add_object ""
+				json_add_string type rule
+				json_add_string family inet6
+				json_add_string proto all
+				json_add_string direction out
+				json_add_string dest "$zone"
+				json_add_string src "$zone"
+				json_add_string dest_ip $(eval "echo \$RULE_${k}_IPV6ADDR")
+				json_add_string target ACCEPT
+			json_close_object
+		}
+		proto_add_ipv6_route $(eval "echo \$RULE_${k}_IPV6ADDR") 128
+	  fi
+	json_close_array
+	proto_close_data
+
+	proto_send_update "$cfg"
+
+	if [ "$maptype" = "lw4o6" -o "$maptype" = "map-e" ]; then
+		json_init
+		json_add_string name "${cfg}_"
+		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"
+	local link="map-$cfg"
+
+	json_get_var type type
+
+	[ -z "$maptype" ] && maptype="$type"
+	[ -z "$maptype" ] && maptype="map-e"
+
+	case "$maptype" in
+		"map-e"|"lw4o6") ifdown "${cfg}_" ;;
+		"map-t") [ -f "/proc/net/nat46/control" ] && echo del $link > /proc/net/nat46/control ;;
+	esac
+
+	rm -f /tmp/map-$cfg.rules
+}
+
+proto_map_init_config() {
+	no_device=1
+	available=1
+
+	proto_config_add_string "maptype"
+	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 "ealen"
+	proto_config_add_int "psidlen"
+	proto_config_add_int "psid"
+	proto_config_add_int "offset"
+	proto_config_add_boolean "legacymap"
+
+	proto_config_add_string "tunlink"
+	proto_config_add_int "mtu"
+	proto_config_add_int "ttl"
+	proto_config_add_string "zone"
+	proto_config_add_string "encaplimit"
+}
+
+[ -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 0000000..a839373
--- /dev/null
+++ b/package/network/ipv6/map/src/CMakeLists.txt
@@ -0,0 +1,29 @@
+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")
+
+FIND_PATH(ubus_include_dir libubus.h)
+INCLUDE_DIRECTORIES(${ubus_include_dir})
+
+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 <steven@midlink.org>")
+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 0000000..66610c4
--- /dev/null
+++ b/package/network/ipv6/map/src/mapcalc.c
@@ -0,0 +1,418 @@
+/*
+ * mapcalc - MAP parameter calculation
+ *
+ * Author: Steven Barth <cyrus@openwrt.org>
+ * Copyright (c) 2014-2015 cisco Systems, Inc.
+ * Copyright (c) 2015 Steven Barth <cyrus@openwrt.org>
+ * Copyright (c) 2018 Hans Dedecker <dedeckeh@gmail.com>
+ *
+ * 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_ADDRESS,
+	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 },
+	[IFACE_ATTR_ADDRESS] = { .name = "ipv6-address", .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]);
+}
+
+static void match_prefix(int *pdlen, struct in6_addr *pd, struct blob_attr *cur,
+		const struct in6_addr *ipv6prefix, int prefix6len, bool lw4o6)
+{
+	struct blob_attr *d;
+	unsigned drem;
+
+	if (!cur || blobmsg_type(cur) != BLOBMSG_TYPE_ARRAY || !blobmsg_check_attr(cur, false))
+		return;
+
+	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);
+
+		// lw4over6 /128-address-as-PD matching madness workaround
+		if (lw4o6 && mask == 128)
+			mask = 64;
+
+		if (*pdlen < mask && mask >= prefix6len &&
+				!bmemcmp(&prefix, ipv6prefix, prefix6len)) {
+			bmemcpy(pd, &prefix, mask);
+			*pdlen = mask;
+		} else if (lw4o6 && *pdlen < prefix6len && mask < prefix6len &&
+				!bmemcmp(&prefix, ipv6prefix, mask)) {
+			bmemcpy(pd, ipv6prefix, prefix6len);
+			*pdlen = prefix6len;
+		}
+	}
+}
+
+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_PD,
+	OPT_PDLEN,
+	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_PD] = "pd",
+	[OPT_PDLEN] = "pdlen",
+	[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)) <= 128 && !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_PD && inet_pton(AF_INET6, value, &pd) == 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_PDLEN && (intval = strtoul(value, NULL, 0)) <= 128 && !errno) {
+				pdlen = 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;
+
+		// LW4over6 doesn't have an EALEN and has no psid-autodetect
+		if (lw4o6) {
+			if (psidlen < 0)
+				psidlen = 0;
+
+			ealen = psidlen;
+		}
+
+		// Find PD
+		if (pdlen < 0) {
+			struct blob_attr *c;
+			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;
+
+				match_prefix(&pdlen, &pd, tb[IFACE_ATTR_PREFIX], &ipv6prefix, prefix6len, lw4o6);
+
+				if (lw4o6)
+					match_prefix(&pdlen, &pd, tb[IFACE_ATTR_ADDRESS], &ipv6prefix, prefix6len, lw4o6);
+
+				if (pdlen >= 0) {
+					iface = blobmsg_get_string(tb[IFACE_ATTR_INTERFACE]);
+					break;
+				}
+			}
+		}
+
+		if (ealen < 0 && pdlen >= 0)
+			ealen = pdlen - prefix6len;
+
+		if (psidlen <= 0) {
+			psidlen = ealen - (32 - prefix4len);
+			if (psidlen < 0)
+				psidlen = 0;
+
+			psid = -1;
+		}
+
+		if (prefix4len < 0 || prefix6len < 0 || ealen < 0 || psidlen > 16 || ealen < psidlen) {
+			fprintf(stderr, "Skipping invalid or incomplete rule: %s\n", argv[i]);
+			status = 1;
+			continue;
+		}
+
+		if (psid < 0 && psidlen >= 0 && pdlen >= 0) {
+			bmemcpys64(&psid16, &pd, prefix6len + ealen - psidlen, psidlen);
+			psid = be16_to_cpu(psid16);
+		}
+
+		if (psidlen > 0) {
+			psid = psid >> (16 - psidlen);
+			psid16 = cpu_to_be16(psid);
+			psid = psid << (16 - psidlen);
+		}
+
+		if (pdlen >= 0 || 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 (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 && 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;
+
+				if (start == 0)
+					start = 1;
+
+				if (start <= end)
+					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;
+}