+
+#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);
+}
+
+#ifdef IFLA_IPTUN_MAX
+#define IP6_FLOWINFO_TCLASS htonl(0x0FF00000)
+static int system_add_gre_tunnel(const char *name, const char *kind,
+ const unsigned int link, struct blob_attr **tb, bool v6)
+{
+ struct nl_msg *nlm;
+ struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC, };
+ struct blob_attr *cur;
+ uint32_t ikey = 0, okey = 0, flags = 0, flowinfo = 0;
+ uint16_t iflags = 0, oflags = 0;
+ uint8_t tos = 0;
+ int ret = 0, ttl = 0;
+
+ nlm = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE);
+ if (!nlm)
+ return -1;
+
+ nlmsg_append(nlm, &ifi, sizeof(ifi), 0);
+ nla_put_string(nlm, IFLA_IFNAME, name);
+
+ struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO);
+ if (!linkinfo) {
+ ret = -ENOMEM;
+ goto failure;
+ }
+
+ nla_put_string(nlm, IFLA_INFO_KIND, kind);
+ struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA);
+ if (!infodata) {
+ ret = -ENOMEM;
+ goto failure;
+ }
+
+ if (link)
+ nla_put_u32(nlm, IFLA_GRE_LINK, link);
+
+ if ((cur = tb[TUNNEL_ATTR_TTL]))
+ ttl = blobmsg_get_u32(cur);
+
+ if ((cur = tb[TUNNEL_ATTR_TOS])) {
+ char *str = blobmsg_get_string(cur);
+ if (strcmp(str, "inherit")) {
+ unsigned uval;
+
+ if (!system_tos_aton(str, &uval)) {
+ ret = -EINVAL;
+ goto failure;
+ }
+
+ if (v6)
+ flowinfo |= htonl(uval << 20) & IP6_FLOWINFO_TCLASS;
+ else
+ tos = uval;
+ } else {
+ if (v6)
+ flags |= IP6_TNL_F_USE_ORIG_TCLASS;
+ else
+ tos = 1;
+ }
+ }
+
+ if ((cur = tb[TUNNEL_ATTR_INFO]) && (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)) {
+ uint8_t icsum, ocsum, iseqno, oseqno;
+ if (sscanf(blobmsg_get_string(cur), "%u,%u,%hhu,%hhu,%hhu,%hhu",
+ &ikey, &okey, &icsum, &ocsum, &iseqno, &oseqno) < 6) {
+ ret = -EINVAL;
+ goto failure;
+ }
+
+ if (ikey)
+ iflags |= GRE_KEY;
+
+ if (okey)
+ oflags |= GRE_KEY;
+
+ if (icsum)
+ iflags |= GRE_CSUM;
+
+ if (ocsum)
+ oflags |= GRE_CSUM;
+
+ if (iseqno)
+ iflags |= GRE_SEQ;
+
+ if (oseqno)
+ oflags |= GRE_SEQ;
+ }
+
+ if (v6) {
+ 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_GRE_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_GRE_REMOTE, sizeof(in6buf), &in6buf);
+ }
+ nla_put_u8(nlm, IFLA_GRE_ENCAP_LIMIT, 4);
+
+ if (flowinfo)
+ nla_put_u32(nlm, IFLA_GRE_FLOWINFO, flowinfo);
+
+ if (flags)
+ nla_put_u32(nlm, IFLA_GRE_FLAGS, flags);
+
+ if (!ttl)
+ ttl = 64;
+ } else {
+ struct in_addr inbuf;
+ bool set_df = true;
+
+ if ((cur = tb[TUNNEL_ATTR_LOCAL])) {
+ if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ nla_put(nlm, IFLA_GRE_LOCAL, sizeof(inbuf), &inbuf);
+ }
+
+ if ((cur = tb[TUNNEL_ATTR_REMOTE])) {
+ if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ nla_put(nlm, IFLA_GRE_REMOTE, sizeof(inbuf), &inbuf);
+
+ if (IN_MULTICAST(ntohl(inbuf.s_addr))) {
+ if (!okey) {
+ okey = inbuf.s_addr;
+ oflags |= GRE_KEY;
+ }
+
+ if (!ikey) {
+ ikey = inbuf.s_addr;
+ iflags |= GRE_KEY;
+ }
+ }
+ }
+
+ if ((cur = tb[TUNNEL_ATTR_DF]))
+ set_df = blobmsg_get_bool(cur);
+
+ if (!set_df) {
+ /* ttl != 0 and nopmtudisc are incompatible */
+ if (ttl) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ } else if (!ttl)
+ ttl = 64;
+
+ nla_put_u8(nlm, IFLA_GRE_PMTUDISC, set_df ? 1 : 0);
+
+ nla_put_u8(nlm, IFLA_GRE_TOS, tos);
+ }
+
+ if (ttl)
+ nla_put_u8(nlm, IFLA_GRE_TTL, ttl);
+
+ if (oflags)
+ nla_put_u16(nlm, IFLA_GRE_OFLAGS, oflags);
+
+ if (iflags)
+ nla_put_u16(nlm, IFLA_GRE_IFLAGS, iflags);
+
+ if (okey)
+ nla_put_u32(nlm, IFLA_GRE_OKEY, okey);
+
+ if (ikey)
+ nla_put_u32(nlm, IFLA_GRE_IKEY, ikey);
+
+ nla_nest_end(nlm, infodata);
+ nla_nest_end(nlm, linkinfo);
+
+ return system_rtnl_call(nlm);
+
+failure:
+ nlmsg_free(nlm);
+ return ret;
+}
+#endif
+
+#ifdef IFLA_VTI_MAX
+static int system_add_vti_tunnel(const char *name, const char *kind,
+ const unsigned int link, struct blob_attr **tb, bool v6)
+{
+ struct nl_msg *nlm;
+ struct ifinfomsg ifi = { .ifi_family = AF_UNSPEC, };
+ struct blob_attr *cur;
+ uint32_t ikey = 0, okey = 0;
+ int ret = 0;
+
+ nlm = nlmsg_alloc_simple(RTM_NEWLINK, NLM_F_REQUEST | NLM_F_REPLACE | NLM_F_CREATE);
+ if (!nlm)
+ return -1;
+
+ nlmsg_append(nlm, &ifi, sizeof(ifi), 0);
+ nla_put_string(nlm, IFLA_IFNAME, name);
+
+ struct nlattr *linkinfo = nla_nest_start(nlm, IFLA_LINKINFO);
+ if (!linkinfo) {
+ ret = -ENOMEM;
+ goto failure;
+ }
+
+ nla_put_string(nlm, IFLA_INFO_KIND, kind);
+ struct nlattr *infodata = nla_nest_start(nlm, IFLA_INFO_DATA);
+ if (!infodata) {
+ ret = -ENOMEM;
+ goto failure;
+ }
+
+ if (link)
+ nla_put_u32(nlm, IFLA_VTI_LINK, link);
+
+ if ((cur = tb[TUNNEL_ATTR_INFO]) && (blobmsg_type(cur) == BLOBMSG_TYPE_STRING)) {
+ if (sscanf(blobmsg_get_string(cur), "%u,%u",
+ &ikey, &okey) < 2) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ }
+
+ if (v6) {
+ 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_VTI_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_VTI_REMOTE, sizeof(in6buf), &in6buf);
+ }
+
+ } else {
+ struct in_addr inbuf;
+
+ if ((cur = tb[TUNNEL_ATTR_LOCAL])) {
+ if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ nla_put(nlm, IFLA_VTI_LOCAL, sizeof(inbuf), &inbuf);
+ }
+
+ if ((cur = tb[TUNNEL_ATTR_REMOTE])) {
+ if (inet_pton(AF_INET, blobmsg_data(cur), &inbuf) < 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+ nla_put(nlm, IFLA_VTI_REMOTE, sizeof(inbuf), &inbuf);
+ }
+
+ }
+
+ if (okey)
+ nla_put_u32(nlm, IFLA_VTI_OKEY, htonl(okey));
+
+ if (ikey)
+ nla_put_u32(nlm, IFLA_VTI_IKEY, htonl(ikey));
+
+ nla_nest_end(nlm, infodata);
+ nla_nest_end(nlm, linkinfo);
+
+ return system_rtnl_call(nlm);
+
+failure:
+ nlmsg_free(nlm);
+ return ret;
+}
+#endif
+
+static int system_add_proto_tunnel(const char *name, const uint8_t proto, const unsigned int link, struct blob_attr **tb)
+{
+ struct blob_attr *cur;
+ bool set_df = true;
+ struct ip_tunnel_parm p = {
+ .link = link,
+ .iph = {
+ .version = 4,
+ .ihl = 5,
+ .protocol = proto,
+ }
+ };
+
+ 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;
+
+ if ((cur = tb[TUNNEL_ATTR_DF]))
+ set_df = blobmsg_get_bool(cur);
+
+ if ((cur = tb[TUNNEL_ATTR_TTL]))
+ p.iph.ttl = blobmsg_get_u32(cur);
+
+ if ((cur = tb[TUNNEL_ATTR_TOS])) {
+ char *str = blobmsg_get_string(cur);
+ if (strcmp(str, "inherit")) {
+ unsigned uval;
+
+ if (!system_tos_aton(str, &uval))
+ return -EINVAL;
+
+ p.iph.tos = uval;
+ } else
+ p.iph.tos = 1;
+ }
+
+ p.iph.frag_off = set_df ? htons(IP_DF) : 0;
+ /* ttl !=0 and nopmtudisc are incompatible */
+ if (p.iph.ttl && p.iph.frag_off == 0)
+ return -EINVAL;
+
+ strncpy(p.name, name, sizeof(p.name));
+
+ switch (p.iph.protocol) {
+ case IPPROTO_IPIP:
+ return tunnel_ioctl("tunl0", SIOCADDTUNNEL, &p);
+ case IPPROTO_IPV6:
+ return tunnel_ioctl("sit0", SIOCADDTUNNEL, &p);
+ default:
+ break;
+ }
+ return -1;
+}
+
+static int __system_del_ip_tunnel(const char *name, struct blob_attr **tb)
+{
+ struct blob_attr *cur;
+ const char *str;
+
+ if (!(cur = tb[TUNNEL_ATTR_TYPE]))
+ return -EINVAL;
+ str = blobmsg_data(cur);
+
+ if (!strcmp(str, "greip") || !strcmp(str, "gretapip") ||
+ !strcmp(str, "greip6") || !strcmp(str, "gretapip6") ||
+ !strcmp(str, "vtiip") || !strcmp(str, "vtiip6"))
+ return system_link_del(name);
+ else
+ return tunnel_ioctl(name, SIOCDELTUNNEL, NULL);
+}
+
+int system_del_ip_tunnel(const char *name, struct blob_attr *attr)
+{
+ struct blob_attr *tb[__TUNNEL_ATTR_MAX];
+
+ blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb,
+ blob_data(attr), blob_len(attr));
+
+ return __system_del_ip_tunnel(name, tb);
+}
+
+int system_update_ipv6_mtu(struct device *dev, int mtu)
+{
+ int ret = -1;
+ char buf[64];
+ int fd;
+
+ snprintf(buf, sizeof(buf), "/proc/sys/net/ipv6/conf/%s/mtu",
+ dev->ifname);
+
+ fd = open(buf, O_RDWR);
+ if (fd < 0)
+ return ret;
+
+ if (!mtu) {
+ ssize_t len = read(fd, buf, sizeof(buf) - 1);
+ if (len < 0)
+ goto out;
+
+ buf[len] = 0;
+ ret = atoi(buf);
+ } else {
+ if (write(fd, buf, snprintf(buf, sizeof(buf), "%i", mtu)) > 0)
+ ret = mtu;
+ }
+
+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;
+ const char *str;
+
+ blobmsg_parse(tunnel_attr_list.params, __TUNNEL_ATTR_MAX, tb,
+ blob_data(attr), blob_len(attr));
+
+ __system_del_ip_tunnel(name, tb);
+
+ if (!(cur = tb[TUNNEL_ATTR_TYPE]))
+ return -EINVAL;
+ str = blobmsg_data(cur);
+
+ unsigned int ttl = 0;
+ if ((cur = tb[TUNNEL_ATTR_TTL])) {
+ ttl = blobmsg_get_u32(cur);
+ if (ttl > 255)
+ 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")) {
+ if (system_add_proto_tunnel(name, IPPROTO_IPV6, link, tb) < 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, tb);
+ return -1;
+ }
+ }
+#endif
+#ifdef IFLA_IPTUN_MAX
+ } 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 };
+ int ret = 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);
+
+ 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_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) {
+ ret = -EINVAL;
+ goto failure;
+ }
+
+ struct in6_addr ip6prefix;
+ struct in_addr ip4prefix;
+ if (inet_pton(AF_INET6, ip6buf, &ip6prefix) != 1 ||
+ inet_pton(AF_INET, ip4buf, &ip4prefix) != 1) {
+ ret = -EINVAL;
+ goto failure;
+ }
+
+ 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;
+ } else if (!strcmp(str, "greip")) {
+ return system_add_gre_tunnel(name, "gre", link, tb, false);
+ } else if (!strcmp(str, "gretapip")) {
+ return system_add_gre_tunnel(name, "gretap", link, tb, false);
+ } else if (!strcmp(str, "greip6")) {
+ return system_add_gre_tunnel(name, "ip6gre", link, tb, true);
+ } else if (!strcmp(str, "gretapip6")) {
+ return system_add_gre_tunnel(name, "ip6gretap", link, tb, true);
+#ifdef IFLA_VTI_MAX
+ } else if (!strcmp(str, "vtiip")) {
+ return system_add_vti_tunnel(name, "vti", link, tb, false);
+ } else if (!strcmp(str, "vtiip6")) {
+ return system_add_vti_tunnel(name, "vti6", link, tb, true);
+#endif
+#endif
+ } else if (!strcmp(str, "ipip")) {
+ return system_add_proto_tunnel(name, IPPROTO_IPIP, link, tb);
+ }
+ else
+ return -EINVAL;
+
+ return 0;
+}