From d7f7f002e3d168aedb5f2bc92180f5966482d7d9 Mon Sep 17 00:00:00 2001 From: Steven Barth Date: Tue, 8 Jan 2013 18:52:32 +0100 Subject: [PATCH] Initial IPv6 prefix support Signed-off-by: Steven Barth --- config.c | 14 +++ dummy/netifd-proto.sh | 15 +++ interface-ip.c | 237 ++++++++++++++++++++++++++++++++++++++++++ interface-ip.h | 33 ++++++ interface.h | 3 + proto.c | 113 +++++++++++++++++++- system-dummy.c | 8 +- system-linux.c | 65 +++++++++++- system.h | 2 + ubus.c | 57 ++++++++++ 10 files changed, 541 insertions(+), 6 deletions(-) diff --git a/config.c b/config.c index 98c46b3..556daf8 100644 --- a/config.c +++ b/config.c @@ -435,6 +435,19 @@ config_init_routes(void) interface_ip_update_complete(&iface->config_ip); } +static void +config_init_globals(void) +{ + struct uci_section *globals = uci_lookup_section( + uci_ctx, uci_network, "globals"); + if (!globals) + return; + + const char *ula_prefix = uci_lookup_option_string( + uci_ctx, globals, "ula_prefix"); + interface_ip_set_ula_prefix(ula_prefix); +} + void config_init_all(void) { @@ -452,6 +465,7 @@ config_init_all(void) config_init_devices(); config_init_interfaces(); config_init_routes(); + config_init_globals(); config_init = false; device_unlock(); diff --git a/dummy/netifd-proto.sh b/dummy/netifd-proto.sh index dba6cc4..323322b 100755 --- a/dummy/netifd-proto.sh +++ b/dummy/netifd-proto.sh @@ -58,6 +58,7 @@ proto_init_update() { PROTO_IP6ADDR= PROTO_ROUTE= PROTO_ROUTE6= + PROTO_PREFIX6= PROTO_DNS= PROTO_DNS_SEARCH= json_init @@ -141,6 +142,19 @@ proto_add_ipv6_route() { append PROTO_ROUTE6 "$target/$mask/$gw" } +proto_add_ipv6_prefix() { + local prefix="$1" + local valid="$2" + local preferred="$3" + + if [ -z "$valid" ]; then + append PROTO_PREFIX6 "$prefix" + else + [ -z "$preferred" ] && preferred="$valid" + append PROTO_PREFIX6 "$prefix,$valid,$preferred" + fi +} + _proto_push_ipv4_addr() { local str="$1" local address mask broadcast ptp @@ -221,6 +235,7 @@ proto_send_update() { _proto_push_array "ip6addr" "$PROTO_IP6ADDR" _proto_push_ipv6_addr _proto_push_array "routes" "$PROTO_ROUTE" _proto_push_route _proto_push_array "routes6" "$PROTO_ROUTE6" _proto_push_route + _proto_push_array "ip6prefix" "$PROTO_PREFIX6" _proto_push_string _proto_push_array "dns" "$PROTO_DNS" _proto_push_string _proto_push_array "dns_search" "$PROTO_DNS_SEARCH" _proto_push_string _proto_notify "$interface" diff --git a/interface-ip.c b/interface-ip.c index f62d670..8831d19 100644 --- a/interface-ip.c +++ b/interface-ip.c @@ -50,6 +50,11 @@ const struct config_param_list route_attr_list = { .params = route_attr, }; + +struct list_head prefixes = LIST_HEAD_INIT(prefixes); +static struct device_prefix *ula_prefix = NULL; + + static void clear_if_addr(union if_addr *a, int mask) { @@ -264,6 +269,19 @@ route_cmp(const void *k1, const void *k2, void *ptr) offsetof(struct device_route, flags)); } +static int +prefix_cmp(const void *k1, const void *k2, void *ptr) +{ + return memcmp(k1, k2, sizeof(struct device_prefix) - + offsetof(struct device_prefix, addr)); +} + +static int +prefix_assignment_cmp(const void *k1, const void *k2, void *ptr) +{ + return strcmp((const char*)k1, (const char*)k2); +} + static void interface_handle_subnet_route(struct interface *iface, struct device_addr *addr, bool add) { @@ -424,6 +442,221 @@ interface_update_host_route(struct vlist_tree *tree, system_add_route(dev, route_new); } + +static void +interface_set_prefix_address(struct interface *iface, bool add, + struct device_prefix_assignment *assignment) +{ + struct interface *uplink = assignment->prefix->iface; + if (!iface->l3_dev.dev) + return; + + struct device *l3_downlink = iface->l3_dev.dev; + + struct device_addr addr; + memset(&addr, 0, sizeof(addr)); + addr.addr.in6 = assignment->addr; + addr.mask = assignment->length; + addr.flags = DEVADDR_INET6; + addr.preferred_until = assignment->prefix->preferred_until; + addr.valid_until = assignment->prefix->valid_until; + + if (!add) { + if (assignment->enabled) + system_del_address(l3_downlink, &addr); + } else { + system_add_address(l3_downlink, &addr); + + if (uplink && uplink->l3_dev.dev) { + int mtu = system_update_ipv6_mtu( + uplink->l3_dev.dev, 0); + if (mtu > 0) + system_update_ipv6_mtu(l3_downlink, mtu); + } + } + assignment->enabled = add; +} + + +static void +interface_update_prefix_assignments(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct device_prefix_assignment *old, *new; + old = container_of(node_old, struct device_prefix_assignment, node); + new = container_of(node_new, struct device_prefix_assignment, node); + + // Assignments persist across interface reloads etc. + // so use indirection to avoid dangling pointers + struct interface *iface = vlist_find(&interfaces, + (node_new) ? new->name : old->name, iface, node); + + if (node_old && node_new) { + new->addr = old->addr; + new->length = old->length; + } else if (node_old) { + if (iface) + interface_set_prefix_address(iface, false, old); + free(old->name); + free(old); + } else if (node_new) { + struct device_prefix *prefix = new->prefix; + uint64_t want = 1ULL << (64 - new->length); + prefix->avail &= ~(want - 1); + prefix->avail -= want; + + // Invert assignment + uint64_t assigned = ~prefix->avail; + assigned &= (1ULL << (64 - prefix->length)) - 1; + assigned &= ~(want - 1); + + // Assignment + new->addr = prefix->addr; + new->addr.s6_addr32[0] |= + htonl(assigned >> 32); + new->addr.s6_addr32[1] |= + htonl(assigned & 0xffffffffU); + new->addr.s6_addr[15] += 1; + } + + if (node_new && (iface->state == IFS_UP || iface->state == IFS_SETUP)) + interface_set_prefix_address(iface, true, new); +} + + +void +interface_ip_set_prefix_assignment(struct device_prefix *prefix, + struct interface *iface, uint8_t length) +{ + if (!length || length > 64) { + struct device_prefix_assignment *assignment = vlist_find( + prefix->assignments, &iface, assignment, node); + if (assignment) + interface_set_prefix_address(iface, false, assignment); + } else { + uint8_t length = iface->proto_ip.assignment_length; + uint64_t want = 1ULL << (64 - length); + if (prefix->avail < want && prefix->avail > 0) { + do { + want = 1ULL << (64 - ++length); + } while (want > prefix->avail); + } + + if (prefix->avail < want) + return; + + // Assignment + struct device_prefix_assignment *assignment = calloc(1, sizeof(*assignment)); + assignment->prefix = prefix; + assignment->length = length; + assignment->name = strdup(iface->name); + + vlist_add(prefix->assignments, &assignment->node, assignment->name); + } +} + +static void +interface_update_prefix(struct vlist_tree *tree, + struct vlist_node *node_new, + struct vlist_node *node_old) +{ + struct device_prefix *prefix_old, *prefix_new; + prefix_old = container_of(node_old, struct device_prefix, node); + prefix_new = container_of(node_new, struct device_prefix, node); + + struct device_route route; + memset(&route, 0, sizeof(route)); + route.flags = DEVADDR_INET6; + route.metric = INT32_MAX; + route.mask = (node_new) ? prefix_new->length : prefix_old->length; + route.addr.in6 = (node_new) ? prefix_new->addr : prefix_old->addr; + + if (node_old && node_new) { + prefix_new->avail = prefix_old->avail; + prefix_new->assignments = prefix_old->assignments; + prefix_old->assignments = NULL; + + // Update all assignments + struct device_prefix_assignment *assignment; + struct vlist_tree *assignments = prefix_new->assignments; + vlist_for_each_element(assignments, assignment, node) + assignments->update(assignments, + &assignment->node, &assignment->node); + } else if (node_new) { + prefix_new->avail = 1ULL << (64 - prefix_new->length); + prefix_new->assignments = calloc(1, sizeof(*prefix_new->assignments)); + vlist_init(prefix_new->assignments, prefix_assignment_cmp, + interface_update_prefix_assignments); + + // Create initial assignments for interfaces + struct interface *iface; + vlist_for_each_element(&interfaces, iface, node) + interface_ip_set_prefix_assignment(prefix_new, iface, + iface->proto_ip.assignment_length); + + list_add(&prefix_new->head, &prefixes); + + // Set null-route to avoid routing loops + system_add_route(NULL, &route); + } + + if (node_old) { + // Remove null-route + system_del_route(NULL, &route); + + list_del(&prefix_old->head); + + if (prefix_old->assignments) { + vlist_flush_all(prefix_old->assignments); + free(prefix_old->assignments); + } + free(prefix_old); + } +} + +void +interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr, + uint8_t length, time_t valid_until, time_t preferred_until) +{ + struct device_prefix *prefix = calloc(1, sizeof(*prefix)); + prefix->length = length; + prefix->addr = *addr; + prefix->preferred_until = preferred_until; + prefix->valid_until = valid_until; + prefix->iface = iface; + + if (iface) + vlist_add(&iface->proto_ip.prefix, &prefix->node, &prefix->addr); + else + interface_update_prefix(NULL, &prefix->node, NULL); +} + +void +interface_ip_set_ula_prefix(const char *prefix) +{ + char buf[INET6_ADDRSTRLEN + 4] = {0}, *saveptr; + strncpy(buf, prefix, sizeof(buf) - 1); + char *prefixaddr = strtok_r(buf, "/", &saveptr); + + struct in6_addr addr; + if (!prefixaddr || inet_pton(AF_INET6, prefixaddr, &addr) < 1) + return; + + int length; + char *prefixlen = strtok_r(NULL, ",", &saveptr); + if (!prefixlen || (length = atoi(prefixlen)) < 1 || length > 64) + return; + + if (ula_prefix && (!IN6_ARE_ADDR_EQUAL(&addr, &ula_prefix->addr) || + ula_prefix->length != length)) { + interface_update_prefix(NULL, NULL, &ula_prefix->node); + ula_prefix = NULL; + } + + interface_ip_add_device_prefix(NULL, &addr, length, 0, 0); +} + void interface_add_dns_server(struct interface_ip_settings *ip, const char *str) { @@ -608,6 +841,7 @@ interface_ip_update_start(struct interface_ip_settings *ip) } vlist_update(&ip->route); vlist_update(&ip->addr); + vlist_update(&ip->prefix); } void @@ -617,6 +851,7 @@ interface_ip_update_complete(struct interface_ip_settings *ip) vlist_simple_flush(&ip->dns_search); vlist_flush(&ip->route); vlist_flush(&ip->addr); + vlist_flush(&ip->prefix); interface_write_resolv_conf(); } @@ -629,6 +864,7 @@ interface_ip_flush(struct interface_ip_settings *ip) vlist_simple_flush_all(&ip->dns_search); vlist_flush_all(&ip->route); vlist_flush_all(&ip->addr); + vlist_flush_all(&ip->prefix); } static void @@ -640,6 +876,7 @@ __interface_ip_init(struct interface_ip_settings *ip, struct interface *iface) vlist_simple_init(&ip->dns_servers, struct dns_server, node); vlist_init(&ip->route, route_cmp, interface_update_proto_route); vlist_init(&ip->addr, addr_cmp, interface_update_proto_addr); + vlist_init(&ip->prefix, prefix_cmp, interface_update_prefix); } void diff --git a/interface-ip.h b/interface-ip.h index 1a82ad6..054ed40 100644 --- a/interface-ip.h +++ b/interface-ip.h @@ -40,6 +40,28 @@ union if_addr { struct in6_addr in6; }; +struct device_prefix { + struct vlist_node node; + struct list_head head; + struct vlist_tree *assignments; + struct interface *iface; + uint64_t avail; + time_t valid_until; + time_t preferred_until; + + struct in6_addr addr; + uint8_t length; +}; + +struct device_prefix_assignment { + struct vlist_node node; + struct device_prefix *prefix; + struct in6_addr addr; + bool enabled; + uint8_t length; + char *name; +}; + struct device_addr { struct vlist_node node; bool enabled; @@ -48,6 +70,10 @@ struct device_addr { uint32_t broadcast; uint32_t point_to_point; + /* ipv6 only */ + time_t valid_until; + time_t preferred_until; + /* must be last */ enum device_addr_flags flags; unsigned int mask; @@ -83,6 +109,7 @@ struct dns_search_domain { }; extern const struct config_param_list route_attr_list; +extern struct list_head prefixes; void interface_ip_init(struct interface *iface); void interface_add_dns_server(struct interface_ip_settings *ip, const char *str); @@ -100,4 +127,10 @@ void interface_ip_update_metric(struct interface_ip_settings *ip, int metric); struct interface *interface_ip_add_target_route(union if_addr *addr, bool v6); +void interface_ip_set_prefix_assignment(struct device_prefix *prefix, + struct interface *iface, uint8_t length); +void interface_ip_add_device_prefix(struct interface *iface, struct in6_addr *addr, + uint8_t length, time_t valid_until, time_t preferred_until); +void interface_ip_set_ula_prefix(const char *prefix); + #endif diff --git a/interface.h b/interface.h index efd3560..b8c9cad 100644 --- a/interface.h +++ b/interface.h @@ -59,9 +59,11 @@ struct interface_ip_settings { bool enabled; bool no_defaultroute; bool no_dns; + uint8_t assignment_length; struct vlist_tree addr; struct vlist_tree route; + struct vlist_tree prefix; struct vlist_simple_tree dns_servers; struct vlist_simple_tree dns_search; @@ -125,6 +127,7 @@ struct interface { struct ubus_object ubus; }; + extern struct vlist_tree interfaces; extern const struct config_param_list interface_attr_list; diff --git a/proto.c b/proto.c index c72a005..40e7fc8 100644 --- a/proto.c +++ b/proto.c @@ -19,6 +19,7 @@ #include #include "netifd.h" +#include "system.h" #include "interface.h" #include "interface-ip.h" #include "proto.h" @@ -32,6 +33,8 @@ enum { OPT_BROADCAST, OPT_GATEWAY, OPT_IP6GW, + OPT_IP6PREFIX, + OPT_IP6ASSIGN, __OPT_MAX, }; @@ -42,11 +45,14 @@ static const struct blobmsg_policy proto_ip_attributes[__OPT_MAX] = { [OPT_BROADCAST] = { .name = "broadcast", .type = BLOBMSG_TYPE_STRING }, [OPT_GATEWAY] = { .name = "gateway", .type = BLOBMSG_TYPE_STRING }, [OPT_IP6GW] = { .name = "ip6gw", .type = BLOBMSG_TYPE_STRING }, + [OPT_IP6PREFIX] = { .name = "ip6prefix", .type = BLOBMSG_TYPE_ARRAY }, + [OPT_IP6ASSIGN] = { .name = "ip6assign", .type = BLOBMSG_TYPE_INT32 }, }; static const union config_param_info proto_ip_attr_info[__OPT_MAX] = { [OPT_IPADDR] = { .type = BLOBMSG_TYPE_STRING }, [OPT_IP6ADDR] = { .type = BLOBMSG_TYPE_STRING }, + [OPT_IP6PREFIX] = { .type = BLOBMSG_TYPE_STRING }, }; const struct config_param_list proto_ip_attr = { @@ -221,6 +227,89 @@ parse_gateway_option(struct interface *iface, struct blob_attr *attr, bool v6) return true; } +static bool +parse_ip6assign_option(struct interface *iface, struct blob_attr *attr) +{ + uint8_t oldval = iface->proto_ip.assignment_length; + uint8_t newval = blobmsg_get_u32(attr); + + struct device_prefix *prefix; + list_for_each_entry(prefix, &prefixes, head) { + if (oldval && oldval != newval) + interface_ip_set_prefix_assignment(prefix, iface, 0); + + if (newval && newval <= 64) + interface_ip_set_prefix_assignment(prefix, iface, newval); + } + + iface->proto_ip.assignment_length = newval; + return true; +} + +static bool +parse_prefix_option(struct interface *iface, const char *str, size_t len) +{ + char buf[128] = {0}, *saveptr; + if (len > sizeof(buf)) + return false; + + memcpy(buf, str, len); + char *addrstr = strtok_r(buf, "/", &saveptr); + if (!addrstr) + return false; + + char *lengthstr = strtok_r(NULL, ",", &saveptr); + if (!lengthstr) + return false; + + char *prefstr = strtok_r(NULL, ",", &saveptr); + char *validstr = (!prefstr) ? NULL : strtok_r(NULL, ",", &saveptr); + + uint32_t pref = (!prefstr) ? 0 : strtoul(prefstr, NULL, 10); + uint32_t valid = (!validstr) ? 0 : strtoul(validstr, NULL, 10); + + uint8_t length = strtoul(lengthstr, NULL, 10); + if (length < 1 || length > 64) + return false; + + struct in6_addr addr; + if (inet_pton(AF_INET6, addrstr, &addr) < 1) + return false; + + time_t now = system_get_rtime(); + time_t preferred_until = 0; + if (prefstr && pref != 0xffffffffU) + preferred_until = pref + now; + + time_t valid_until = 0; + if (validstr && valid != 0xffffffffU) + valid_until = valid + now; + + interface_ip_add_device_prefix(iface, &addr, length, + valid_until, preferred_until); + return true; +} + +static int +parse_prefix_list(struct interface *iface, struct blob_attr *attr) +{ + struct blob_attr *cur; + int n_addr = 0; + int rem; + + blobmsg_for_each_attr(cur, attr, rem) { + if (blobmsg_type(cur) != BLOBMSG_TYPE_STRING) + return -1; + + n_addr++; + if (!parse_prefix_option(iface, blobmsg_data(cur), + blobmsg_data_len(cur))) + return -1; + } + + return n_addr; +} + int proto_apply_static_ip_settings(struct interface *iface, struct blob_attr *attr) { @@ -256,10 +345,16 @@ proto_apply_static_ip_settings(struct interface *iface, struct blob_attr *attr) n_v6 = parse_static_address_option(iface, cur, true, netmask, false, 0); + if ((cur = tb[OPT_IP6PREFIX])) + if (parse_prefix_list(iface, cur) < 0) + goto out; + +/* TODO: Clarify if (!n_v4 && !n_v6) { error = "NO_ADDRESS"; goto error; } +*/ if (n_v4 < 0 || n_v6 < 0) goto out; @@ -274,6 +369,10 @@ proto_apply_static_ip_settings(struct interface *iface, struct blob_attr *attr) goto out; } + if ((cur = tb[OPT_IP6ASSIGN])) + if (!parse_ip6assign_option(iface, cur)) + goto out; + return 0; error: @@ -287,7 +386,7 @@ proto_apply_ip_settings(struct interface *iface, struct blob_attr *attr, bool ex { struct blob_attr *tb[__OPT_MAX]; struct blob_attr *cur; - const char *error; +// const char *error; int n_v4 = 0, n_v6 = 0; blobmsg_parse(proto_ip_attributes, __OPT_MAX, tb, blob_data(attr), blob_len(attr)); @@ -298,10 +397,16 @@ proto_apply_ip_settings(struct interface *iface, struct blob_attr *attr, bool ex if ((cur = tb[OPT_IP6ADDR])) n_v6 = parse_address_list(iface, cur, true, ext); + if ((cur = tb[OPT_IP6PREFIX])) + if (parse_prefix_list(iface, cur) < 0) + goto out; + +/* TODO: clarify if (!n_v4 && !n_v6) { error = "NO_ADDRESS"; goto error; } +*/ if (n_v4 < 0 || n_v6 < 0) goto out; @@ -316,10 +421,16 @@ proto_apply_ip_settings(struct interface *iface, struct blob_attr *attr, bool ex goto out; } + if ((cur = tb[OPT_IP6ASSIGN])) + if (!parse_ip6assign_option(iface, cur)) + goto out; + return 0; +/* TODO: clarify error: interface_add_error(iface, "proto", error, NULL, 0); +*/ out: return -1; } diff --git a/system-dummy.c b/system-dummy.c index 4609358..14c7a97 100644 --- a/system-dummy.c +++ b/system-dummy.c @@ -166,7 +166,8 @@ static int system_route_msg(struct device *dev, struct device_route *route, cons else gw[0] = 0; - sprintf(devstr, " dev %s", dev->ifname); + if (dev) + sprintf(devstr, " dev %s", dev->ifname); if (route->metric > 0) sprintf(devstr, " metric %d", route->metric); @@ -209,3 +210,8 @@ int system_add_ip_tunnel(const char *name, struct blob_attr *attr) { return 0; } + +int system_update_ipv6_mtu(struct device *dev, int mtu) +{ + return 0; +} diff --git a/system-linux.c b/system-linux.c index 3175d92..8345e5d 100644 --- a/system-linux.c +++ b/system-linux.c @@ -867,6 +867,7 @@ static int system_addr(struct device *dev, struct device_addr *addr, int cmd) { bool v4 = ((addr->flags & DEVADDR_FAMILY) == DEVADDR_INET4); int alen = v4 ? 4 : 16; + unsigned int flags = 0; struct ifaddrmsg ifa = { .ifa_family = (alen == 4) ? AF_INET : AF_INET6, .ifa_prefixlen = addr->mask, @@ -874,8 +875,10 @@ static int system_addr(struct device *dev, struct device_addr *addr, int cmd) }; struct nl_msg *msg; + if (cmd == RTM_NEWADDR) + flags |= NLM_F_CREATE | NLM_F_REPLACE; - msg = nlmsg_alloc_simple(cmd, 0); + msg = nlmsg_alloc_simple(cmd, flags); if (!msg) return -1; @@ -886,6 +889,27 @@ static int system_addr(struct device *dev, struct device_addr *addr, int cmd) nla_put_u32(msg, IFA_BROADCAST, addr->broadcast); if (addr->point_to_point) nla_put_u32(msg, IFA_ADDRESS, addr->point_to_point); + } else { + time_t now = system_get_rtime(); + struct ifa_cacheinfo cinfo = {0xffffffffU, 0xffffffffU, 0, 0}; + + if (addr->preferred_until) { + int preferred = addr->preferred_until - now; + if (preferred < 0) + preferred = 0; + + cinfo.ifa_prefered = preferred; + } + + if (addr->valid_until) { + int valid = addr->valid_until - now; + if (valid <= 0) + return -1; + + cinfo.ifa_valid = valid; + } + + nla_put(msg, IFA_CACHEINFO, sizeof(cinfo), &cinfo); } return system_rtnl_call(msg); @@ -906,7 +930,6 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) int alen = ((route->flags & DEVADDR_FAMILY) == DEVADDR_INET4) ? 4 : 16; bool have_gw; unsigned int flags = 0; - int ifindex = dev->ifindex; if (alen == 4) have_gw = !!route->nexthop.in.s_addr; @@ -929,9 +952,15 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) }; struct nl_msg *msg; - if (cmd == RTM_NEWROUTE) + if (cmd == RTM_NEWROUTE) { flags |= NLM_F_CREATE | NLM_F_REPLACE; + if (!dev) { // Add null-route + rtm.rtm_scope = RT_SCOPE_UNIVERSE; + rtm.rtm_type = RTN_UNREACHABLE; + } + } + msg = nlmsg_alloc_simple(cmd, flags); if (!msg) return -1; @@ -947,7 +976,8 @@ static int system_rt(struct device *dev, struct device_route *route, int cmd) if (have_gw) nla_put(msg, RTA_GATEWAY, alen, &route->nexthop); - nla_put_u32(msg, RTA_OIF, ifindex); + if (dev) + nla_put_u32(msg, RTA_OIF, dev->ifindex); return system_rtnl_call(msg); } @@ -1025,6 +1055,33 @@ int system_del_ip_tunnel(const char *name) return tunnel_ioctl(name, SIOCDELTUNNEL, &p); } +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; +} + static int parse_ipaddr(struct blob_attr *attr, __be32 *addr) { if (!attr) diff --git a/system.h b/system.h index bc1b932..acfaa37 100644 --- a/system.h +++ b/system.h @@ -101,4 +101,6 @@ time_t system_get_rtime(void); void system_fd_set_cloexec(int fd); +int system_update_ipv6_mtu(struct device *device, int mtu); + #endif diff --git a/ubus.c b/ubus.c index 2b92c78..e549d9c 100644 --- a/ubus.c +++ b/ubus.c @@ -446,6 +446,59 @@ interface_ip_dump_route_list(struct interface_ip_settings *ip, bool enabled) } } + +static void +interface_ip_dump_prefix_list(struct interface_ip_settings *ip) +{ + struct device_prefix *prefix; + char *buf; + void *a, *c; + const int buflen = INET6_ADDRSTRLEN; + + vlist_for_each_element(&ip->prefix, prefix, node) { + a = blobmsg_open_table(&b, NULL); + + buf = blobmsg_alloc_string_buffer(&b, "address", buflen); + inet_ntop(AF_INET6, &prefix->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", prefix->length); + + time_t now = system_get_rtime(); + if (prefix->preferred_until) { + int preferred = prefix->preferred_until - now; + if (preferred < 0) + preferred = 0; + blobmsg_add_u32(&b, "preferred", preferred); + } + + if (prefix->valid_until) { + int valid = prefix->valid_until - now; + if (valid < 0) + valid = 0; + blobmsg_add_u32(&b, "valid", valid); + } + + c = blobmsg_open_table(&b, "assigned"); + struct device_prefix_assignment *assign; + vlist_for_each_element(prefix->assignments, assign, node) { + void *d = blobmsg_open_table(&b, assign->name); + + buf = blobmsg_alloc_string_buffer(&b, "address", buflen); + inet_ntop(AF_INET6, &assign->addr, buf, buflen); + blobmsg_add_string_buffer(&b); + + blobmsg_add_u32(&b, "mask", assign->length); + + blobmsg_close_table(&b, d); + } + blobmsg_close_table(&b, c); + + blobmsg_close_table(&b, a); + } +} + + static void interface_ip_dump_dns_server_list(struct interface_ip_settings *ip, bool enabled) @@ -520,6 +573,10 @@ netifd_handle_status(struct ubus_context *ctx, struct ubus_object *obj, interface_ip_dump_address_list(&iface->config_ip, true, true); interface_ip_dump_address_list(&iface->proto_ip, true, true); blobmsg_close_array(&b, a); + a = blobmsg_open_array(&b, "ipv6-prefix"); + interface_ip_dump_prefix_list(&iface->config_ip); + interface_ip_dump_prefix_list(&iface->proto_ip); + blobmsg_close_array(&b, a); a = blobmsg_open_array(&b, "route"); interface_ip_dump_route_list(&iface->config_ip, true); interface_ip_dump_route_list(&iface->proto_ip, true); -- 2.25.1