ubus: fix invalid ipv6-prefix json
[project/odhcpd.git] / src / dhcpv4.c
index 826a1f8..3002c1e 100644 (file)
 #include "dhcpv4.h"
 #include "dhcpv6.h"
 
+static void dhcpv4_netevent_cb(unsigned long event, struct netevent_handler_info *info);
 static int setup_dhcpv4_addresses(struct interface *iface);
 static void update_static_assignments(struct interface *iface);
 static void valid_until_cb(struct uloop_timeout *event);
+static void handle_addrlist_change(struct interface *iface);
 static void free_dhcpv4_assignment(struct dhcpv4_assignment *a);
 static void dhcpv4_fr_start(struct dhcpv4_assignment *a);
 static void dhcpv4_fr_stop(struct dhcpv4_assignment *a);
 static void handle_dhcpv4(void *addr, void *data, size_t len,
                struct interface *iface, void *dest_addr);
 static struct dhcpv4_assignment* dhcpv4_lease(struct interface *iface,
-               enum dhcpv4_msg msg, const uint8_t *mac, struct in_addr reqaddr,
+               enum dhcpv4_msg msg, const uint8_t *mac, const uint32_t reqaddr,
                uint32_t *leasetime, const char *hostname, const size_t hostname_len,
                const bool accept_fr_nonce, bool *incl_fr_opt, uint32_t *fr_serverid);
 
+static struct netevent_handler dhcpv4_netevent_handler = { .cb = dhcpv4_netevent_cb, };
 static struct uloop_timeout valid_until_timeout = {.cb = valid_until_cb};
 static uint32_t serial = 0;
 
@@ -58,14 +61,18 @@ struct odhcpd_ref_ip {
 };
 
 /* Create socket and register events */
-int init_dhcpv4(void)
+int dhcpv4_init(void)
 {
        uloop_timeout_set(&valid_until_timeout, 1000);
+       netlink_add_netevent_handler(&dhcpv4_netevent_handler);
+
        return 0;
 }
 
-int setup_dhcpv4_interface(struct interface *iface, bool enable)
+int dhcpv4_setup_interface(struct interface *iface, bool enable)
 {
+       int ret = 0;
+
        if (iface->dhcpv4_event.uloop.fd > 0) {
                uloop_fd_delete(&iface->dhcpv4_event.uloop);
                close(iface->dhcpv4_event.uloop.fd);
@@ -73,57 +80,117 @@ int setup_dhcpv4_interface(struct interface *iface, bool enable)
        }
 
        if (iface->dhcpv4 && enable) {
+               struct sockaddr_in bind_addr = {AF_INET, htons(DHCPV4_SERVER_PORT),
+                                       {INADDR_ANY}, {0}};
+               int val = 1;
+
                if (!iface->dhcpv4_assignments.next)
                        INIT_LIST_HEAD(&iface->dhcpv4_assignments);
 
                if (!iface->dhcpv4_fr_ips.next)
                        INIT_LIST_HEAD(&iface->dhcpv4_fr_ips);
 
-               int sock = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
-               if (sock < 0) {
-                       syslog(LOG_ERR, "Failed to create DHCPv4 server socket: %s",
-                                       strerror(errno));
-                       return -1;
+               iface->dhcpv4_event.uloop.fd = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+               if (iface->dhcpv4_event.uloop.fd < 0) {
+                       syslog(LOG_ERR, "socket(AF_INET): %m");
+                       ret = -1;
+                       goto out;
                }
 
                /* Basic IPv4 configuration */
-               int val = 1;
-               setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val));
-               setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &val, sizeof(val));
-               setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &val, sizeof(val));
+               if (setsockopt(iface->dhcpv4_event.uloop.fd, SOL_SOCKET, SO_REUSEADDR,
+                                       &val, sizeof(val)) < 0) {
+                       syslog(LOG_ERR, "setsockopt(SO_REUSEADDR): %m");
+                       ret = -1;
+                       goto out;
+               }
+
+               if (setsockopt(iface->dhcpv4_event.uloop.fd, SOL_SOCKET, SO_BROADCAST,
+                                       &val, sizeof(val)) < 0) {
+                       syslog(LOG_ERR, "setsockopt(SO_BROADCAST): %m");
+                       ret = -1;
+                       goto out;
+               }
+
+               if (setsockopt(iface->dhcpv4_event.uloop.fd, IPPROTO_IP, IP_PKTINFO,
+                                       &val, sizeof(val)) < 0) {
+                       syslog(LOG_ERR, "setsockopt(IP_PKTINFO): %m");
+                       ret = -1;
+                       goto out;
+               }
 
                val = IPTOS_PREC_INTERNETCONTROL;
-               setsockopt(sock, IPPROTO_IP, IP_TOS, &val, sizeof(val));
+               if (setsockopt(iface->dhcpv4_event.uloop.fd, IPPROTO_IP, IP_TOS,
+                                       &val, sizeof(val)) < 0) {
+                       syslog(LOG_ERR, "setsockopt(IP_TOS): %m");
+                       ret = -1;
+                       goto out;
+               }
 
                val = IP_PMTUDISC_DONT;
-               setsockopt(sock, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val));
+               if (setsockopt(iface->dhcpv4_event.uloop.fd, IPPROTO_IP, IP_MTU_DISCOVER,
+                                       &val, sizeof(val)) < 0) {
+                       syslog(LOG_ERR, "setsockopt(IP_MTU_DISCOVER): %m");
+                       ret = -1;
+                       goto out;
+               }
 
-               setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE,
-                               iface->ifname, strlen(iface->ifname));
+               if (setsockopt(iface->dhcpv4_event.uloop.fd, SOL_SOCKET, SO_BINDTODEVICE,
+                                       iface->ifname, strlen(iface->ifname)) < 0) {
+                       syslog(LOG_ERR, "setsockopt(SO_BINDTODEVICE): %m");
+                       ret = -1;
+                       goto out;
+               }
 
-               struct sockaddr_in bind_addr = {AF_INET, htons(DHCPV4_SERVER_PORT),
-                                       {INADDR_ANY}, {0}};
+               if (bind(iface->dhcpv4_event.uloop.fd, (struct sockaddr*)&bind_addr,
+                                       sizeof(bind_addr)) < 0) {
+                       syslog(LOG_ERR, "bind(): %m");
+                       ret = -1;
+                       goto out;
+               }
 
-               if (bind(sock, (struct sockaddr*)&bind_addr, sizeof(bind_addr))) {
-                       syslog(LOG_ERR, "Failed to open DHCPv4 server socket: %s",
-                                       strerror(errno));
-                       return -1;
+               if (setup_dhcpv4_addresses(iface) < 0) {
+                       ret = -1;
+                       goto out;
                }
 
-               iface->dhcpv4_event.uloop.fd = sock;
                iface->dhcpv4_event.handle_dgram = handle_dhcpv4;
                odhcpd_register(&iface->dhcpv4_event);
 
-               if (setup_dhcpv4_addresses(iface) < 0)
-                       return -1;
-
                update_static_assignments(iface);
        } else if (iface->dhcpv4_assignments.next) {
                while (!list_empty(&iface->dhcpv4_assignments))
                        free_dhcpv4_assignment(list_first_entry(&iface->dhcpv4_assignments,
                                                        struct dhcpv4_assignment, head));
        }
-       return 0;
+
+out:
+       if (ret < 0 && iface->dhcpv4_event.uloop.fd > 0) {
+               close(iface->dhcpv4_event.uloop.fd);
+               iface->dhcpv4_event.uloop.fd = -1;
+       }
+
+       return ret;
+}
+
+
+static void dhcpv4_netevent_cb(unsigned long event, struct netevent_handler_info *info)
+{
+       struct interface *iface = info->iface;
+
+       if (!iface || iface->dhcpv4 == MODE_DISABLED)
+               return;
+
+       switch (event) {
+       case NETEV_IFINDEX_CHANGE:
+               dhcpv4_setup_interface(iface, true);
+               break;
+       case NETEV_ADDRLIST_CHANGE:
+               handle_addrlist_change(iface);
+               break;
+       default:
+               break;
+       }
 }
 
 static struct dhcpv4_assignment *find_assignment_by_hwaddr(struct interface *iface, const uint8_t *hwaddr)
@@ -293,7 +360,7 @@ static void decr_ref_cnt_ip(struct odhcpd_ref_ip **ptr, struct interface *iface)
        struct odhcpd_ref_ip *ip = *ptr;
 
        if (--ip->ref_cnt == 0) {
-               odhcpd_setup_addr(&ip->addr, iface, false, false);
+               netlink_setup_addr(&ip->addr, iface->ifindex, false, false);
 
                list_del(&ip->head);
                free(ip);
@@ -344,17 +411,14 @@ static void valid_until_cb(struct uloop_timeout *event)
        uloop_timeout_set(event, 1000);
 }
 
-void dhcpv4_addr_update(struct interface *iface)
+static void handle_addrlist_change(struct interface *iface)
 {
-       if (iface->dhcpv4 == MODE_DISABLED)
-               return;
-
        struct odhcpd_ipaddr ip;
        struct odhcpd_ref_ip *a;
        struct dhcpv4_assignment *c;
        uint32_t mask = iface->dhcpv4_mask.s_addr;
 
-       memset(&ip, sizeof(ip), 0);
+       memset(&ip, 0, sizeof(ip));
        ip.addr.in = iface->dhcpv4_local;
        ip.prefix = odhcpd_netmask2bitlen(false, &iface->dhcpv4_mask);
        ip.broadcast = iface->dhcpv4_bcast;
@@ -373,7 +437,7 @@ void dhcpv4_addr_update(struct interface *iface)
 
        a = list_first_entry(&iface->dhcpv4_fr_ips, struct odhcpd_ref_ip, head);
 
-       if (odhcpd_setup_addr(&a->addr, iface, false, true)) {
+       if (netlink_setup_addr(&a->addr, iface->ifindex, false, true)) {
                syslog(LOG_ERR, "Failed to add ip address");
                return;
        }
@@ -430,7 +494,9 @@ static void dhcpv4_put(struct dhcpv4_message *msg, uint8_t **cookie,
                uint8_t type, uint8_t len, const void *data)
 {
        uint8_t *c = *cookie;
-       if (*cookie + 2 + len > (uint8_t*)&msg[1])
+       uint8_t *end = (uint8_t *)msg + sizeof(*msg);
+
+       if (*cookie + 2 + len > end)
                return;
 
        *c++ = type;
@@ -602,7 +668,7 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
        uint8_t reqmsg = DHCPV4_MSG_REQUEST;
        uint8_t msg = DHCPV4_MSG_ACK;
 
-       struct in_addr reqaddr = {INADDR_ANY};
+       uint32_t reqaddr = INADDR_ANY;
        uint32_t leasetime = 0;
        size_t hostname_len = 0;
        char hostname[256];
@@ -666,7 +732,7 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
        } else if (reqmsg == DHCPV4_MSG_DISCOVER)
                msg = DHCPV4_MSG_OFFER;
        else if (reqmsg == DHCPV4_MSG_REQUEST &&
-                       ((reqaddr.s_addr && reqaddr.s_addr != lease->addr) ||
+                       ((reqaddr && reqaddr != lease->addr) ||
                         (req->ciaddr.s_addr && req->ciaddr.s_addr != lease->addr))) {
                msg = DHCPV4_MSG_NAK;
                /*
@@ -701,6 +767,11 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                        req->chaddr[0],req->chaddr[1],req->chaddr[2],
                        req->chaddr[3],req->chaddr[4],req->chaddr[5]);
 
+#ifdef WITH_UBUS
+       if (reqmsg == DHCPV4_MSG_RELEASE)
+               ubus_bcast_dhcp_event("dhcp.release", req->chaddr, req->hlen,
+                                       &req->ciaddr, hostname, iface->ifname);
+#endif
        if (reqmsg == DHCPV4_MSG_DECLINE || reqmsg == DHCPV4_MSG_RELEASE)
                return;
 
@@ -754,8 +825,10 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
                }
        }
 
-       struct ifreq ifr = {.ifr_name = ""};
-       strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name));
+       struct ifreq ifr;
+
+       memset(&ifr, 0, sizeof(ifr));
+       strncpy(ifr.ifr_name, iface->ifname, sizeof(ifr.ifr_name) - 1);
 
        if (!ioctl(sock, SIOCGIFMTU, &ifr)) {
                uint16_t mtu = htons(ifr.ifr_mtu);
@@ -850,6 +923,12 @@ static void handle_dhcpv4(void *addr, void *data, size_t len,
 
        sendto(sock, &reply, sizeof(reply), MSG_DONTWAIT,
                        (struct sockaddr*)&dest, sizeof(dest));
+
+#ifdef WITH_UBUS
+       if (msg == DHCPV4_MSG_ACK)
+               ubus_bcast_dhcp_event("dhcp.ack", req->chaddr, req->hlen, &reply.yiaddr,
+                                       hostname, iface->ifname);
+#endif
 }
 
 static bool dhcpv4_assign(struct interface *iface,
@@ -914,22 +993,14 @@ static bool dhcpv4_assign(struct interface *iface,
 
 
 static struct dhcpv4_assignment* dhcpv4_lease(struct interface *iface,
-               enum dhcpv4_msg msg, const uint8_t *mac, struct in_addr reqaddr,
+               enum dhcpv4_msg msg, const uint8_t *mac, const uint32_t reqaddr,
                uint32_t *leasetime, const char *hostname, const size_t hostname_len,
                const bool accept_fr_nonce, bool *incl_fr_opt, uint32_t *fr_serverid)
 {
+       struct dhcpv4_assignment *a = find_assignment_by_hwaddr(iface, mac);
        struct dhcpv4_assignment *lease = NULL;
        time_t now = odhcpd_time();
 
-       struct dhcpv4_assignment *c, *a = NULL;
-       list_for_each_entry(c, &iface->dhcpv4_assignments, head) {
-               if (!memcmp(c->hwaddr, mac, 6)) {
-                       a = c;
-                       if (c->addr == reqaddr.s_addr)
-                               break;
-               }
-       }
-
        if (a && (a->flags & OAF_BOUND) && a->fr_ip) {
                *fr_serverid = a->fr_ip->addr.addr.in.s_addr;
                dhcpv4_fr_stop(a);
@@ -951,7 +1022,7 @@ static struct dhcpv4_assignment* dhcpv4_lease(struct interface *iface,
                                /* Don't consider new assignment as infinite */
                                a->valid_until = now;
 
-                               assigned = dhcpv4_assign(iface, a, reqaddr.s_addr);
+                               assigned = dhcpv4_assign(iface, a, reqaddr);
                                if (assigned) {
                                        a->iface = iface;
                                        list_add(&a->head, &iface->dhcpv4_assignments);
@@ -961,7 +1032,7 @@ static struct dhcpv4_assignment* dhcpv4_lease(struct interface *iface,
                                (iface->dhcpv4_start_ip.s_addr & iface->dhcpv4_mask.s_addr)) {
                        list_del(&a->head);
 
-                       assigned = dhcpv4_assign(iface, a, reqaddr.s_addr);
+                       assigned = dhcpv4_assign(iface, a, reqaddr);
                        if (assigned)
                                list_add(&a->head, &iface->dhcpv4_assignments);
                }