chiark / gitweb /
sd-dhcp-server: bind to raw socket for sending
[elogind.git] / src / libsystemd-network / sd-dhcp-server.c
index 57fb09aca0373430872f801a42350e706cbd4ae4..ecdc15dff733c1789a7ede402948691941c6b197 100644 (file)
@@ -58,6 +58,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
                 return -ENOMEM;
 
         server->n_ref = REFCNT_INIT;
+        server->fd_raw = -1;
         server->fd = -1;
         server->index = ifindex;
 
@@ -106,6 +107,7 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
         server->receive_message =
                 sd_event_source_unref(server->receive_message);
 
+        server->fd_raw = safe_close(server->fd_raw);
         server->fd = safe_close(server->fd);
 
         log_dhcp_server(server, "STOPPED");
@@ -113,9 +115,84 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
         return 0;
 }
 
+static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
+                         void *user_data) {
+        DHCPRequest *req = user_data;
+
+        assert(req);
+
+        switch(code) {
+        case DHCP_OPTION_SERVER_IDENTIFIER:
+                if (len == 4)
+                        req->server_id = *(be32_t*)option;
+
+                break;
+        case DHCP_OPTION_CLIENT_IDENTIFIER:
+                if (len >= 2) {
+                        uint8_t *data;
+
+                        data = memdup(option, len);
+                        if (!data)
+                                return -ENOMEM;
+
+                        free(req->client_id.data);
+                        req->client_id.data = data;
+                        req->client_id.length = len;
+                }
+
+                break;
+        case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE:
+                if (len == 2)
+                        req->max_optlen = be16toh(*(be16_t*)option) -
+                                          - sizeof(DHCPPacket);
+
+                break;
+        }
+
+        return 0;
+}
+
+static void dhcp_request_free(DHCPRequest *req) {
+        if (!req)
+                return;
+
+        free(req->client_id.data);
+        free(req);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free);
+#define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep)
+
+static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
+        assert(req);
+        assert(message);
+
+        req->message = message;
+
+        /* set client id based on mac address if client did not send an explicit one */
+        if (!req->client_id.data) {
+                uint8_t *data;
+
+                data = new0(uint8_t, ETH_ALEN + 1);
+                if (!data)
+                        return -ENOMEM;
+
+                req->client_id.length = ETH_ALEN + 1;
+                req->client_id.data = data;
+                req->client_id.data[0] = 0x01;
+                memcpy(&req->client_id.data[1], &message->chaddr, ETH_ALEN);
+        }
+
+        if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
+                req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
+
+        return 0;
+}
+
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                                size_t length) {
-        int type;
+        _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
+        int type, r;
 
         assert(server);
         assert(message);
@@ -125,10 +202,19 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
             message->hlen != ETHER_ADDR_LEN)
                 return 0;
 
-        type = dhcp_option_parse(message, length, NULL, NULL);
+        req = new0(DHCPRequest, 1);
+        if (!req)
+                return -ENOMEM;
+
+        type = dhcp_option_parse(message, length, parse_request, req);
         if (type < 0)
                 return 0;
 
+        r = ensure_sane_request(req, message);
+        if (r < 0)
+                /* this only fails on critical errors */
+                return r;
+
         log_dhcp_server(server, "received message of type %d", type);
 
         return 1;
@@ -193,8 +279,17 @@ int sd_dhcp_server_start(sd_dhcp_server *server) {
         assert_return(server, -EINVAL);
         assert_return(server->event, -EINVAL);
         assert_return(!server->receive_message, -EBUSY);
+        assert_return(server->fd_raw == -1, -EBUSY);
         assert_return(server->fd == -1, -EBUSY);
 
+        r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
+        if (r < 0) {
+                r = -errno;
+                sd_dhcp_server_stop(server);
+                return r;
+        }
+        server->fd_raw = r;
+
         r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
         if (r < 0) {
                 sd_dhcp_server_stop(server);