+
+#ifndef IP_DF
+#define IP_DF 0x4000
+#endif
+
+static int tunnel_ioctl(const char *name, int cmd, void *p)
+{
+ struct ifreq ifr;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
+ ifr.ifr_ifru.ifru_data = p;
+ return ioctl(sock_ioctl, cmd, &ifr);
+}
+
+int system_del_ip_tunnel(const char *name)
+{
+ return tunnel_ioctl(name, SIOCDELTUNNEL, NULL);
+}
+
+int system_update_ipv6_mtu(struct device *dev, int mtu)
+{
+ int ret = -1;
+ char buf[64];
+ snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/conf/%s/mtu",
+ dev->ifname);
+
+ int fd = open(buf, O_RDWR);
+ ssize_t len = read(fd, buf, sizeof(buf) - 1);
+ if (len < 0)
+ goto out;
+
+ buf[len] = 0;
+ ret = atoi(buf);
+
+ if (!mtu || ret <= mtu)
+ goto out;
+
+ lseek(fd, 0, SEEK_SET);
+ if (write(fd, buf, snprintf(buf, sizeof(buf), "%i", mtu)) <= 0)
+ ret = -1;
+
+out:
+ close(fd);
+ return ret;
+}
+
+int system_add_ip_tunnel(const char *name, struct blob_attr *attr)
+{
+ struct blob_attr *tb[__TUNNEL_ATTR_MAX];
+ struct blob_attr *cur;
+ bool set_df = true;
+ const char *str;
+
+ system_del_ip_tunnel(name);
+
+ blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb,
+ blob_data(attr), blob_len(attr));
+
+ if (!(cur = tb[TUNNEL_ATTR_TYPE]))
+ return -EINVAL;
+ str = blobmsg_data(cur);
+
+ if ((cur = tb[TUNNEL_ATTR_DF]))
+ set_df = blobmsg_get_bool(cur);
+
+ unsigned int ttl = 0;
+ if ((cur = tb[TUNNEL_ATTR_TTL])) {
+ ttl = blobmsg_get_u32(cur);
+ if (ttl > 255 || (!set_df && ttl))
+ return -EINVAL;
+ }
+
+ unsigned int link = 0;
+ if ((cur = tb[TUNNEL_ATTR_LINK])) {
+ struct interface *iface = vlist_find(&interfaces, blobmsg_data(cur), iface, node);
+ if (!iface)
+ return -EINVAL;
+
+ if (iface->l3_dev.dev)
+ link = iface->l3_dev.dev->ifindex;
+ }
+
+ if (!strcmp(str, "sit")) {
+ struct ip_tunnel_parm p = {
+ .link = link,
+ .iph = {
+ .version = 4,
+ .ihl = 5,
+ .frag_off = set_df ? htons(IP_DF) : 0,
+ .protocol = IPPROTO_IPV6,
+ .ttl = ttl
+ }
+ };
+
+ if ((cur = tb[TUNNEL_ATTR_LOCAL]) &&
+ inet_pton(AF_INET, blobmsg_data(cur), &p.iph.saddr) < 1)
+ return -EINVAL;
+
+ if ((cur = tb[TUNNEL_ATTR_REMOTE]) &&
+ inet_pton(AF_INET, blobmsg_data(cur), &p.iph.daddr) < 1)
+ return -EINVAL;
+
+ strncpy(p.name, name, sizeof(p.name));
+ if (tunnel_ioctl("sit0", SIOCADDTUNNEL, &p) < 0)
+ return -1;
+
+#ifdef SIOCADD6RD
+ if ((cur = tb[TUNNEL_ATTR_6RD_PREFIX])) {
+ unsigned int mask;
+ struct ip_tunnel_6rd p6;
+
+ memset(&p6, 0, sizeof(p6));
+
+ if (!parse_ip_and_netmask(AF_INET6, blobmsg_data(cur),
+ &p6.prefix, &mask) || mask > 128)
+ return -EINVAL;
+ p6.prefixlen = mask;
+
+ if ((cur = tb[TUNNEL_ATTR_6RD_RELAY_PREFIX])) {
+ if (!parse_ip_and_netmask(AF_INET, blobmsg_data(cur),
+ &p6.relay_prefix, &mask) || mask > 32)
+ return -EINVAL;
+ p6.relay_prefixlen = mask;
+ }
+
+ if (tunnel_ioctl(name, SIOCADD6RD, &p6) < 0) {
+ system_del_ip_tunnel(name);
+ return -1;
+ }
+ }
+#endif
+ } else if (!strcmp(str, "ipip6")) {
+ 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 };
+ 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);
+ nla_put_string(nlm, IFLA_INFO_KIND, "ip6tnl");
+ struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA);
+
+ if (link)
+ nla_put_u32(nlm, IFLA_IPTUN_LINK, link);
+
+ 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)
+ return -EINVAL;
+ 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)
+ return -EINVAL;
+ nla_put(nlm, IFLA_IPTUN_REMOTE, sizeof(in6buf), &in6buf);
+ }
+
+#ifdef IFLA_IPTUN_FMR_MAX
+ if ((cur = tb[TUNNEL_ATTR_FMRS])) {
+ struct nlattr *fmrs = nla_nest_start(nlm, IFLA_IPTUN_FMRS);
+
+ struct blob_attr *fmr;
+ unsigned rem, fmrcnt = 0;
+ blobmsg_for_each_attr(fmr, cur, rem) {
+ if (blobmsg_type(fmr) != BLOBMSG_TYPE_STRING)
+ continue;
+
+ unsigned ip4len, ip6len, ealen, offset = 6;
+ char ip6buf[48];
+ char ip4buf[16];
+
+ if (sscanf(blobmsg_get_string(fmr), "%47[^/]/%u,%15[^/]/%u,%u,%u",
+ ip6buf, &ip6len, ip4buf, &ip4len, &ealen, &offset) < 5)
+ return -EINVAL;
+
+ struct in6_addr ip6prefix;
+ struct in_addr ip4prefix;
+ if (inet_pton(AF_INET6, ip6buf, &ip6prefix) != 1 ||
+ inet_pton(AF_INET, ip4buf, &ip4prefix) != 1)
+ return -EINVAL;
+
+ 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);
+ } else
+ return -EINVAL;
+
+ return 0;
+}