+#ifdef IFLA_IPTUN_MAX
+static int system_add_ip6_tunnel(const char *name, const unsigned int link,
+ struct blob_attr **tb)
+{
+ struct nl_msg *nlm = nlmsg_alloc_simple(RTM_NEWLINK,
+ NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE);
+ struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC };
+ struct blob_attr *cur;
+ int ret = 0, ttl = 0;
+
+ if (!nlm)
+ return -1;
+
+ nlmsg_append(nlm, &ifi, sizeof(ifi), 0);
+ nla_put_string(nlm, IFLA_IFNAME, name);
+
+ if (link)
+ nla_put_u32(nlm, IFLA_LINK, link);
+
+ struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO);
+ if (!linkinfo) {
+ ret = -ENOMEM;
+ goto failure;
+ }
+
+ nla_put_string(nlm, IFLA_INFO_KIND, "ip6tnl");
+ struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA);
+ if (!infodata) {
+ ret = -ENOMEM;
+ goto failure;
+ }
+
+ if (link)
+ nla_put_u32(nlm, IFLA_IPTUN_LINK, link);
+
+ if ((cur = tb[TUNNEL_ATTR_TTL]))
+ ttl = blobmsg_get_u32(cur);
+
+ nla_put_u8(nlm, IFLA_IPTUN_PROTO, IPPROTO_IPIP);
+ nla_put_u8(nlm, IFLA_IPTUN_TTL, (ttl) ? ttl : 64);
+ nla_put_u8(nlm, IFLA_IPTUN_ENCAP_LIMIT, 4);
+
+ struct in6_addr in6buf;
+ if ((cur = tb[TUNNEL_ATTR_LOCAL])) {
+ if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ nla_put(nlm, IFLA_IPTUN_LOCAL, sizeof(in6buf), &in6buf);
+ }
+
+ if ((cur = tb[TUNNEL_ATTR_REMOTE])) {
+ if (inet_pton(AF_INET6, blobmsg_data(cur), &in6buf) < 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ nla_put(nlm, IFLA_IPTUN_REMOTE, sizeof(in6buf), &in6buf);
+ }
+
+#ifdef IFLA_IPTUN_FMR_MAX
+ if ((cur = tb[TUNNEL_ATTR_DATA])) {
+ struct nlattr *fmrs = nla_nest_start(nlm, IFLA_IPTUN_FMRS);
+ struct blob_attr *dcur;
+ unsigned drem, fmrcnt = 0;
+
+ blobmsg_for_each_attr(dcur, cur, drem) {
+ if (blobmsg_type(dcur) != BLOBMSG_TYPE_ARRAY ||
+ strcmp(blobmsg_name(dcur), "fmrs") ||
+ blobmsg_check_array(dcur, BLOBMSG_TYPE_UNSPEC) <= 0)
+ continue;
+
+ struct blob_attr *rcur;
+ unsigned rrem;
+ blobmsg_for_each_attr(rcur, dcur, rrem) {
+ struct blob_attr *tb_fmr[__FMR_DATA_ATTR_MAX], *tb_cur;
+ struct in6_addr ip6prefix;
+ struct in_addr ip4prefix;
+ unsigned ip4len, ip6len, ealen, offset;
+
+ blobmsg_parse(fmr_data_attr_list.params, __FMR_DATA_ATTR_MAX, tb_fmr,
+ blobmsg_data(rcur), blobmsg_len(rcur));
+
+ if (!(tb_cur = tb_fmr[FMR_DATA_PREFIX6]) ||
+ !parse_ip_and_netmask(AF_INET6,
+ blobmsg_data(tb_cur), &ip6prefix,
+ &ip6len)) {
+ ret = -EINVAL;
+ goto failure;
+ }
+
+ if (!(tb_cur = tb_fmr[FMR_DATA_PREFIX4]) ||
+ !parse_ip_and_netmask(AF_INET,
+ blobmsg_data(tb_cur), &ip4prefix,
+ &ip4len)) {
+ ret = -EINVAL;
+ goto failure;
+ }
+
+ if (!(tb_cur = tb_fmr[FMR_DATA_EALEN])) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ ealen = blobmsg_get_u32(tb_cur);
+
+ if (!(tb_cur = tb_fmr[FMR_DATA_OFFSET])) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ offset = blobmsg_get_u32(tb_cur);
+
+ struct nlattr *rule = nla_nest_start(nlm, ++fmrcnt);
+
+ nla_put(nlm, IFLA_IPTUN_FMR_IP6_PREFIX, sizeof(ip6prefix), &ip6prefix);
+ nla_put(nlm, IFLA_IPTUN_FMR_IP4_PREFIX, sizeof(ip4prefix), &ip4prefix);
+ nla_put_u8(nlm, IFLA_IPTUN_FMR_IP6_PREFIX_LEN, ip6len);
+ nla_put_u8(nlm, IFLA_IPTUN_FMR_IP4_PREFIX_LEN, ip4len);
+ nla_put_u8(nlm, IFLA_IPTUN_FMR_EA_LEN, ealen);
+ nla_put_u8(nlm, IFLA_IPTUN_FMR_OFFSET, offset);
+
+ nla_nest_end(nlm, rule);
+ }
+ }
+ nla_nest_end(nlm, fmrs);
+ }
+#endif
+
+ nla_nest_end(nlm, infodata);
+ nla_nest_end(nlm, linkinfo);
+
+ return system_rtnl_call(nlm);
+
+failure:
+ nlmsg_free(nlm);
+ return ret;
+}
+#endif
+