treewide: rework IPv4 address logic
authorHans Dedecker <dedeckeh@gmail.com>
Mon, 3 Jul 2017 14:39:21 +0000 (16:39 +0200)
committerHans Dedecker <dedeckeh@gmail.com>
Mon, 7 Aug 2017 13:27:20 +0000 (15:27 +0200)
Align IPv4 address logic with IPv6 by caching per interface the assigned
IPv4 addresses. This allows to get rid of different ioctl calls in the
DHCPv4 logic to retrieve the IPv4 address and netmask in use by an
interface.

Signed-off-by: Hans Dedecker <dedeckeh@gmail.com>
src/config.c
src/dhcpv4.c
src/ndp.c
src/odhcpd.c
src/odhcpd.h

index 19b901c..8bf2011 100644 (file)
@@ -240,6 +240,7 @@ static void close_interface(struct interface *iface)
        setup_dhcpv4_interface(iface, false);
 
        clean_interface(iface);
+       free(iface->addr4);
        free(iface->ia_addr);
        free(iface->ifname);
        free(iface);
@@ -450,6 +451,11 @@ int config_parse_interface(void *data, size_t len, const char *name, bool overwr
 
                if (len > 0)
                        iface->ia_addr_len = len;
+
+               len = odhcpd_get_interface_addresses(iface->ifindex,
+                                               false, &iface->addr4);
+               if (len > 0)
+                       iface->addr4_len = len;
        }
 
        iface->inuse = true;
index e1f0df6..976e1c9 100644 (file)
@@ -95,42 +95,36 @@ int setup_dhcpv4_interface(struct interface *iface, bool enable)
                        return -1;
                }
 
-               /* Create a range if not specified */
-               struct ifreq ifreq;
-               strncpy(ifreq.ifr_name, iface->ifname, sizeof(ifreq.ifr_name));
+               uint32_t mask = iface->addr4[0].prefix ? htonl(~((1 << (32 - iface->addr4[0].prefix)) - 1)) : 0;
 
-               struct sockaddr_in *saddr = (struct sockaddr_in*)&ifreq.ifr_addr;
-               struct sockaddr_in *smask = (struct sockaddr_in*)&ifreq.ifr_netmask;
+               /* Create a range if not specified */
                if (!(iface->dhcpv4_start.s_addr & htonl(0xffff0000)) &&
                                !(iface->dhcpv4_end.s_addr & htonl(0xffff0000)) &&
-                               !ioctl(sock, SIOCGIFADDR, &ifreq)) {
-                       struct in_addr addr = saddr->sin_addr;
-
-                       ioctl(sock, SIOCGIFNETMASK, &ifreq);
-                       struct in_addr mask = smask->sin_addr;
+                               iface->addr4_len) {
+                       struct in_addr *addr = &iface->addr4[0].addr.in;
 
                        uint32_t start = ntohl(iface->dhcpv4_start.s_addr);
                        uint32_t end = ntohl(iface->dhcpv4_end.s_addr);
 
                        if (start && end && start < end &&
-                                       start > ntohl(addr.s_addr & ~mask.s_addr) &&
-                                       (start & ntohl(~mask.s_addr)) == start &&
-                                       (end & ntohl(~mask.s_addr)) == end) {
+                                       start > ntohl(addr->s_addr & ~mask) &&
+                                       (start & ntohl(~mask)) == start &&
+                                       (end & ntohl(~mask)) == end) {
                                iface->dhcpv4_start.s_addr = htonl(start) |
-                                               (addr.s_addr & mask.s_addr);
+                                               (addr->s_addr & mask);
                                iface->dhcpv4_end.s_addr = htonl(end) |
-                                               (addr.s_addr & mask.s_addr);
-                       } else if (ntohl(mask.s_addr) <= 0xfffffff0) {
-                               start = addr.s_addr & mask.s_addr;
-                               end = addr.s_addr & mask.s_addr;
+                                               (addr->s_addr & mask);
+                       } else if (ntohl(mask) <= 0xfffffff0) {
+                               start = addr->s_addr & mask;
+                               end = addr->s_addr & mask;
 
-                               if (ntohl(mask.s_addr) <= 0xffffff00) {
+                               if (ntohl(mask) <= 0xffffff00) {
                                        iface->dhcpv4_start.s_addr = start | htonl(100);
                                        iface->dhcpv4_end.s_addr = end | htonl(250);
-                               } else if (ntohl(mask.s_addr) <= 0xffffffc0) {
+                               } else if (ntohl(mask) <= 0xffffffc0) {
                                        iface->dhcpv4_start.s_addr = start | htonl(10);
                                        iface->dhcpv4_end.s_addr = end | htonl(60);
-                               } else if (ntohl(mask.s_addr) <= 0xffffffe0) {
+                               } else if (ntohl(mask) <= 0xffffffe0) {
                                        iface->dhcpv4_start.s_addr = start | htonl(10);
                                        iface->dhcpv4_end.s_addr = end | htonl(30);
                                } else {
@@ -186,8 +180,8 @@ int setup_dhcpv4_interface(struct interface *iface, bool enable)
                // Clean invalid assignments
                struct dhcpv4_assignment *a, *n;
                list_for_each_entry_safe(a, n, &iface->dhcpv4_assignments, head) {
-                       if ((htonl(a->addr) & smask->sin_addr.s_addr) !=
-                                       (iface->dhcpv4_start.s_addr & smask->sin_addr.s_addr))
+                       if ((htonl(a->addr) & mask) !=
+                                       (iface->dhcpv4_start.s_addr & mask))
                                free_dhcpv4_assignment(a);
                }
 
@@ -261,35 +255,26 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                        req->op != DHCPV4_BOOTREQUEST || req->hlen != 6)
                return;
 
-       int sock = iface->dhcpv4_event.uloop.fd;
-       struct sockaddr_in ifaddr;
-       struct sockaddr_in ifnetmask;
 
        syslog(LOG_NOTICE, "Got DHCPv4 request");
 
-       struct ifreq ifreq;
-       memcpy(ifreq.ifr_name, iface->ifname, sizeof(ifreq.ifr_name));
-       if (ioctl(sock, SIOCGIFADDR, &ifreq)) {
-               syslog(LOG_WARNING, "DHCPv4 failed to detect address: %s", strerror(errno));
+       if (!iface->addr4_len) {
+               syslog(LOG_WARNING, "DHCPv4 no address found on %s", iface->name);
                return;
        }
 
-       memcpy(&ifaddr, &ifreq.ifr_addr, sizeof(ifaddr));
-       if (ioctl(sock, SIOCGIFNETMASK, &ifreq))
-               return;
-
-       memcpy(&ifnetmask, &ifreq.ifr_netmask, sizeof(ifnetmask));
-       uint32_t network = ifaddr.sin_addr.s_addr & ifnetmask.sin_addr.s_addr;
+       struct in_addr *ifaddr = &iface->addr4[0].addr.in;
+       struct in_addr *ifbroadcast = &iface->addr4[0].broadcast;
+       uint32_t mask = iface->addr4[0].prefix ? htonl(~((1 << (32 - iface->addr4[0].prefix)) - 1)) : 0;
+       uint32_t network = iface->addr4[0].addr.in.s_addr & mask;
+       int sock = iface->dhcpv4_event.uloop.fd;
 
-       if ((iface->dhcpv4_start.s_addr & ifnetmask.sin_addr.s_addr) != network ||
-                       (iface->dhcpv4_end.s_addr & ifnetmask.sin_addr.s_addr) != network) {
+       if ((iface->dhcpv4_start.s_addr & mask) != network ||
+                       (iface->dhcpv4_end.s_addr & mask) != network) {
                syslog(LOG_WARNING, "DHCPv4 range out of assigned network");
                return;
        }
 
-       struct ifreq ifr = {.ifr_name = ""};
-       strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
-
        struct dhcpv4_message reply = {
                .op = DHCPV4_BOOTREPLY,
                .htype = 1,
@@ -300,7 +285,7 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                .flags = req->flags,
                .ciaddr = {INADDR_ANY},
                .giaddr = req->giaddr,
-               .siaddr = ifaddr.sin_addr,
+               .siaddr = *ifaddr,
        };
        memcpy(reply.chaddr, req->chaddr, sizeof(reply.chaddr));
 
@@ -331,7 +316,7 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                } else if (opt->type == DHCPV4_OPT_IPADDRESS && opt->len == 4)
                        memcpy(&reqaddr, opt->data, 4);
                else if (opt->type == DHCPV4_OPT_SERVERID && opt->len == 4) {
-                       if (memcmp(opt->data, &ifaddr.sin_addr, 4))
+                       if (memcmp(opt->data, ifaddr, 4))
                                return;
                } else if (iface->filter_class && opt->type == DHCPV4_OPT_USER_CLASS) {
                        uint8_t *c = opt->data, *cend = &opt->data[opt->len];
@@ -385,7 +370,7 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                return;
 
        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_MESSAGE, 1, &msg);
-       dhcpv4_put(&reply, &cookie, DHCPV4_OPT_SERVERID, 4, &ifaddr.sin_addr);
+       dhcpv4_put(&reply, &cookie, DHCPV4_OPT_SERVERID, 4, ifaddr);
 
        if (lease) {
                uint32_t val;
@@ -403,18 +388,19 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_REBIND, 4, &val);
                }
 
-               dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NETMASK, 4, &ifnetmask.sin_addr);
+               dhcpv4_put(&reply, &cookie, DHCPV4_OPT_NETMASK, 4, &mask);
 
                if (lease->hostname)
                        dhcpv4_put(&reply, &cookie, DHCPV4_OPT_HOSTNAME,
                                        strlen(lease->hostname), lease->hostname);
 
-               if (!ioctl(sock, SIOCGIFBRDADDR, &ifr)) {
-                       struct sockaddr_in *ina = (struct sockaddr_in*)&ifr.ifr_broadaddr;
-                       dhcpv4_put(&reply, &cookie, DHCPV4_OPT_BROADCAST, 4, &ina->sin_addr);
-               }
+               if (ifbroadcast->s_addr != INADDR_ANY)
+                       dhcpv4_put(&reply, &cookie, DHCPV4_OPT_BROADCAST, 4, ifbroadcast);
        }
 
+       struct ifreq ifr = {.ifr_name = ""};
+       strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
+
        if (!ioctl(sock, SIOCGIFMTU, &ifr)) {
                uint16_t mtu = htons(ifr.ifr_mtu);
                dhcpv4_put(&reply, &cookie, DHCPV4_OPT_MTU, 2, &mtu);
@@ -433,14 +419,14 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
        }
 
        if (iface->dhcpv4_router_cnt == 0)
-               dhcpv4_put(&reply, &cookie, DHCPV4_OPT_ROUTER, 4, &ifaddr.sin_addr);
+               dhcpv4_put(&reply, &cookie, DHCPV4_OPT_ROUTER, 4, ifaddr);
        else
                dhcpv4_put(&reply, &cookie, DHCPV4_OPT_ROUTER,
                                4 * iface->dhcpv4_router_cnt, iface->dhcpv4_router);
 
 
        if (iface->dhcpv4_dns_cnt == 0)
-               dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER, 4, &ifaddr.sin_addr);
+               dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER, 4, ifaddr);
        else
                dhcpv4_put(&reply, &cookie, DHCPV4_OPT_DNSSERVER,
                                4 * iface->dhcpv4_dns_cnt, iface->dhcpv4_dns);
index 2363a9d..8378ebf 100644 (file)
--- a/src/ndp.c
+++ b/src/ndp.c
@@ -92,9 +92,10 @@ int init_ndp(void)
        nl_socket_modify_cb(rtnl_event.sock, NL_CB_VALID, NL_CB_CUSTOM,
                        cb_rtnl_valid, NULL);
 
-       // Receive IPv6 address, IPv6 routes and neighbor events
-       if (nl_socket_add_memberships(rtnl_event.sock, RTNLGRP_IPV6_IFADDR,
-                               RTNLGRP_IPV6_ROUTE, RTNLGRP_NEIGH, 0))
+       // Receive IPv4 address, IPv6 address, IPv6 routes and neighbor events
+       if (nl_socket_add_memberships(rtnl_event.sock, RTNLGRP_IPV4_IFADDR,
+                               RTNLGRP_IPV6_IFADDR, RTNLGRP_IPV6_ROUTE,
+                               RTNLGRP_NEIGH, 0))
                goto err;
 
        odhcpd_register(&rtnl_event.ev);
@@ -324,6 +325,25 @@ static void setup_route(struct in6_addr *addr, struct interface *iface, bool add
 }
 
 // Check address update
+static void check_addr_updates(struct interface *iface)
+{
+       struct odhcpd_ipaddr *addr = NULL;
+       ssize_t len = odhcpd_get_interface_addresses(iface->ifindex, false, &addr);
+
+       if (len < 0)
+               return;
+
+       bool change = len != (ssize_t)iface->addr4_len;
+       for (ssize_t i = 0; !change && i < len; ++i)
+               if (addr[i].addr.in.s_addr != iface->ia_addr[i].addr.in.s_addr)
+                       change = true;
+
+       free(iface->addr4);
+       iface->addr4 = addr;
+       iface->addr4_len = len;
+}
+
+// Check v6 address update
 static void check_addr6_updates(struct interface *iface)
 {
        struct odhcpd_ipaddr *addr = NULL;
@@ -389,7 +409,7 @@ static void handle_rtnl_event(struct odhcpd_event *e)
 static int cb_rtnl_valid(struct nl_msg *msg, _unused void *arg)
 {
        struct nlmsghdr *hdr = nlmsg_hdr(msg);
-       struct in6_addr *addr = NULL;
+       struct in6_addr *addr6 = NULL;
        struct interface *iface = NULL;
        bool add = false;
        char ipbuf[INET6_ADDRSTRLEN];
@@ -418,7 +438,8 @@ static int cb_rtnl_valid(struct nl_msg *msg, _unused void *arg)
                struct nlattr *nla[__IFA_MAX];
 
                if (!nlmsg_valid_hdr(hdr, sizeof(*ifa)) ||
-                               ifa->ifa_family != AF_INET6)
+                               (ifa->ifa_family != AF_INET6 &&
+                                ifa->ifa_family != AF_INET))
                        return NL_SKIP;
 
                iface = odhcpd_get_interface_by_index(ifa->ifa_index);
@@ -426,28 +447,42 @@ static int cb_rtnl_valid(struct nl_msg *msg, _unused void *arg)
                        return NL_SKIP;
 
                nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
-               if (!nla[IFA_ADDRESS])
-                       return NL_SKIP;
 
-               addr = nla_data(nla[IFA_ADDRESS]);
-               if (!addr || IN6_IS_ADDR_LINKLOCAL(addr) ||
-                               IN6_IS_ADDR_MULTICAST(addr))
-                       return NL_SKIP;
+               if (ifa->ifa_family == AF_INET6) {
+                       if (!nla[IFA_ADDRESS])
+                               return NL_SKIP;
 
-               inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
-               syslog(LOG_DEBUG, "Netlink %s %s%%%s", add ? "newaddr" : "deladdr",
-                       ipbuf, iface->ifname);
+                       addr6 = nla_data(nla[IFA_ADDRESS]);
+                       if (!addr6 || IN6_IS_ADDR_LINKLOCAL(addr6) ||
+                                       IN6_IS_ADDR_MULTICAST(addr6))
+                               return NL_SKIP;
 
-               check_addr6_updates(iface);
+                       inet_ntop(AF_INET6, addr6, ipbuf, sizeof(ipbuf));
+                       syslog(LOG_DEBUG, "Netlink %s %s%%%s", add ? "newaddr" : "deladdr",
+                               ipbuf, iface->ifname);
 
-               if (iface->ndp != RELAYD_RELAY)
-                       break;
+                       check_addr6_updates(iface);
 
-               /* handle the relay logic below */
-               setup_addr_for_relaying(addr, iface, add);
+                       if (iface->ndp != RELAYD_RELAY)
+                               break;
 
-               if (!add)
-                       dump_neigh_table(false);
+                       /* handle the relay logic below */
+                       setup_addr_for_relaying(addr6, iface, add);
+
+                       if (!add)
+                               dump_neigh_table(false);
+               } else {
+                       if (!nla[IFA_LOCAL])
+                               return NL_SKIP;
+
+                       struct in_addr *addr = nla_data(nla[IFA_ADDRESS]);
+
+                       inet_ntop(AF_INET, addr, ipbuf, sizeof(ipbuf));
+                       syslog(LOG_DEBUG, "Netlink %s %s%%%s", add ? "newaddr" : "deladdr",
+                               ipbuf, iface->ifname);
+
+                       check_addr_updates(iface);
+               }
                break;
        }
 
@@ -470,20 +505,20 @@ static int cb_rtnl_valid(struct nl_msg *msg, _unused void *arg)
                if (!nla[NDA_DST])
                        return NL_SKIP;
 
-               addr = nla_data(nla[NDA_DST]);
-               if (!addr || IN6_IS_ADDR_LINKLOCAL(addr) ||
-                               IN6_IS_ADDR_MULTICAST(addr))
+               addr6 = nla_data(nla[NDA_DST]);
+               if (!addr6 || IN6_IS_ADDR_LINKLOCAL(addr6) ||
+                               IN6_IS_ADDR_MULTICAST(addr6))
                        return NL_SKIP;
 
-               inet_ntop(AF_INET6, addr, ipbuf, sizeof(ipbuf));
-               syslog(LOG_DEBUG, "Netlink %s %s%%%s", add ? "newneigh" : "delneigh",
+               inet_ntop(AF_INET6, addr6, ipbuf, sizeof(ipbuf));
+               syslog(LOG_DEBUG, "Netlink %s %s%%%s", true ? "newneigh" : "delneigh",
                        ipbuf, iface->ifname);
 
                if (ndm->ndm_flags & NTF_PROXY) {
                        /* Dump and flush proxy entries */
                        if (hdr->nlmsg_type == RTM_NEWNEIGH) {
-                               odhcpd_setup_proxy_neigh(addr, iface, false);
-                               setup_route(addr, iface, false);
+                               odhcpd_setup_proxy_neigh(addr6, iface, false);
+                               setup_route(addr6, iface, false);
                                dump_neigh_table(false);
                        }
 
@@ -495,8 +530,8 @@ static int cb_rtnl_valid(struct nl_msg *msg, _unused void *arg)
                                 NUD_PERMANENT | NUD_NOARP)))
                        return NL_OK;
 
-               setup_addr_for_relaying(addr, iface, add);
-               setup_route(addr, iface, add);
+               setup_addr_for_relaying(addr6, iface, add);
+               setup_route(addr6, iface, add);
 
                if (!add)
                        dump_neigh_table(false);
index 6f38d5c..5b739d0 100644 (file)
@@ -234,7 +234,7 @@ static int cb_valid_handler(struct nl_msg *msg, void *arg)
        struct odhcpd_ipaddr *addrs = *(ctxt->addrs);
        struct nlmsghdr *hdr = nlmsg_hdr(msg);
        struct ifaddrmsg *ifa;
-       struct nlattr *nla[__IFA_MAX];
+       struct nlattr *nla[__IFA_MAX], *nla_addr = NULL;
 
        if (hdr->nlmsg_type != RTM_NEWADDR)
                return NL_SKIP;
@@ -246,7 +246,22 @@ static int cb_valid_handler(struct nl_msg *msg, void *arg)
                return NL_SKIP;
 
        nlmsg_parse(hdr, sizeof(*ifa), nla, __IFA_MAX - 1, NULL);
-       if (!nla[IFA_ADDRESS])
+
+       switch (ifa->ifa_family) {
+       case AF_INET6:
+               if (nla[IFA_ADDRESS])
+                       nla_addr = nla[IFA_ADDRESS];
+               break;
+
+       case AF_INET:
+               if (nla[IFA_LOCAL])
+                       nla_addr = nla[IFA_LOCAL];
+               break;
+
+       default:
+               break;
+       }
+       if (!nla_addr)
                return NL_SKIP;
 
        addrs = realloc(addrs, sizeof(*addrs)*(ctxt->ret + 1));
@@ -256,9 +271,13 @@ static int cb_valid_handler(struct nl_msg *msg, void *arg)
        memset(&addrs[ctxt->ret], 0, sizeof(addrs[ctxt->ret]));
        addrs[ctxt->ret].prefix = ifa->ifa_prefixlen;
 
-       nla_memcpy(&addrs[ctxt->ret].addr, nla[IFA_ADDRESS],
+       nla_memcpy(&addrs[ctxt->ret].addr, nla_addr,
                        sizeof(addrs[ctxt->ret].addr));
 
+       if (nla[IFA_BROADCAST])
+               nla_memcpy(&addrs[ctxt->ret].broadcast, nla[IFA_BROADCAST],
+                               sizeof(addrs[ctxt->ret].broadcast));
+
        if (nla[IFA_CACHEINFO]) {
                struct ifa_cacheinfo *ifc = nla_data(nla[IFA_CACHEINFO]);
 
@@ -295,8 +314,15 @@ static int cb_error_handler(_unused struct sockaddr_nl *nla, struct nlmsgerr *er
        return NL_STOP;
 }
 
-// compare prefixes
-static int prefixcmp(const void *va, const void *vb)
+static int prefix_cmp(const void *va, const void *vb)
+{
+       const struct odhcpd_ipaddr *a = va, *b = vb;
+       return (ntohl(a->addr.in.s_addr) < ntohl(b->addr.in.s_addr)) ? 1 :
+               (ntohl(a->addr.in.s_addr) > ntohl(b->addr.in.s_addr)) ? -1 : 0;
+}
+
+// compare IPv6 prefixes
+static int prefix6_cmp(const void *va, const void *vb)
 {
        const struct odhcpd_ipaddr *a = va, *b = vb;
        uint32_t a_pref = IN6_IS_ADDR_ULA(&a->addr.in6) ? 1 : a->preferred;
@@ -353,7 +379,7 @@ ssize_t odhcpd_get_interface_addresses(int ifindex, bool v6, struct odhcpd_ipadd
        time_t now = odhcpd_time();
        struct odhcpd_ipaddr *addr = *addrs;
 
-       qsort(addr, ctxt.ret, sizeof(*addr), prefixcmp);
+       qsort(addr, ctxt.ret, sizeof(*addr), v6 ? prefix6_cmp : prefix_cmp);
 
        for (ssize_t i = 0; i < ctxt.ret; ++i) {
                if (addr[i].preferred < UINT32_MAX - now)
index a03baf0..b0801a4 100644 (file)
@@ -76,11 +76,14 @@ union if_addr {
 struct odhcpd_ipaddr {
        union if_addr addr;
        uint8_t prefix;
+       uint32_t preferred;
+       uint32_t valid;
 
        /* ipv6 only */
        uint8_t dprefix;
-       uint32_t preferred;
-       uint32_t valid;
+
+       /* ipv4 only */
+       struct in_addr broadcast;
 };
 
 enum odhcpd_mode {
@@ -127,6 +130,8 @@ struct interface {
        // Runtime data
        struct uloop_timeout timer_rs;
        struct list_head ia_assignments;
+       struct odhcpd_ipaddr *addr4;
+       size_t addr4_len;
        struct odhcpd_ipaddr *ia_addr;
        size_t ia_addr_len;