[systemd-commits] 17 commits - .gitignore Makefile.am src/libsystemd-network src/network src/systemd

Tom Gundersen tomegun at kemper.freedesktop.org
Fri Jun 13 08:25:42 PDT 2014


 .gitignore                                    |    1 
 Makefile.am                                   |   12 
 src/libsystemd-network/dhcp-network.c         |   16 
 src/libsystemd-network/dhcp-server-internal.h |   88 ++
 src/libsystemd-network/sd-dhcp-server.c       |  872 ++++++++++++++++++++++++++
 src/libsystemd-network/test-dhcp-server.c     |  239 +++++++
 src/network/networkd-link.c                   |  101 ++-
 src/network/networkd-network-gperf.gperf      |    1 
 src/network/networkd.h                        |    5 
 src/systemd/sd-dhcp-server.h                  |   46 +
 10 files changed, 1365 insertions(+), 16 deletions(-)

New commits:
commit dd43110f781a9245ec00531456fee68ed763a179
Author: Tom Gundersen <teg at jklm.no>
Date:   Wed Mar 5 08:13:30 2014 +0100

    networkd: add dhcp server support
    
    When enabled in [Network] it will set up a dhcp server on the interface, listening
    on one of its statically configured IPv4 addresses and with a fixed size pool of
    leases determined from it.
    
    Example:
    
    [Match]
    Name=ve-arch-tree
    
    [Network]
    Address=192.168.12.5/24
    DHCPServer=yes
    
    [Route]
    Gateway=192.168.12.5
    Destination=192.168.12.0/24
    
    In this case we will configure ve-arch-tree with the address 192.168.12.5 and
    hand out addresses in the range 192.168.12.6 - 192.168.12.38.
    
    In the future, we should (as suggested by Lennart) introduce a syntax to pick the
    server address automatically.

diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c
index 3653426..4b59af4 100644
--- a/src/network/networkd-link.c
+++ b/src/network/networkd-link.c
@@ -178,19 +178,6 @@ void link_drop(Link *link) {
         return;
 }
 
-static int link_enter_configured(Link *link) {
-        assert(link);
-        assert(link->state == LINK_STATE_SETTING_ROUTES);
-
-        log_info_link(link, "link configured");
-
-        link->state = LINK_STATE_CONFIGURED;
-
-        link_save(link);
-
-        return 0;
-}
-
 static void link_enter_unmanaged(Link *link) {
         assert(link);
 
@@ -231,6 +218,16 @@ static int link_stop_clients(Link *link) {
                 }
         }
 
+        if (link->network->dhcp_server) {
+                assert(link->dhcp_server);
+
+                k = sd_dhcp_server_stop(link->dhcp_server);
+                if (k < 0) {
+                        log_warning_link(link, "Could not stop DHCPv4 server: %s", strerror(-r));
+                        r = k;
+                }
+        }
+
         return r;
 }
 
@@ -249,6 +246,37 @@ static void link_enter_failed(Link *link) {
         link_save(link);
 }
 
+static int link_enter_configured(Link *link) {
+        int r;
+
+        assert(link);
+        assert(link->network);
+        assert(link->state == LINK_STATE_SETTING_ROUTES);
+
+
+        if (link->network->dhcp_server) {
+                log_debug_link(link, "offering DHCPv4 leases");
+
+                r = sd_dhcp_server_start(link->dhcp_server);
+                if (r < 0) {
+                        log_warning_link(link, "could not start DHCPv4 server "
+                                         "instance: %s", strerror(-r));
+
+                        link_enter_failed(link);
+
+                        return 0;
+                }
+        }
+
+        log_info_link(link, "link configured");
+
+        link->state = LINK_STATE_CONFIGURED;
+
+        link_save(link);
+
+        return 0;
+}
+
 static int route_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
         Link *link = userdata;
         int r;
@@ -1667,7 +1695,52 @@ static int link_configure(Link *link) {
                 }
         }
 
-        if (link_has_carrier(link->flags, link->kernel_operstate)) {
+        if (link->network->dhcp_server) {
+                Address *address;
+
+                r = sd_dhcp_server_new(&link->dhcp_server, link->ifindex);
+                if (r < 0)
+                        return r;
+
+                r = sd_dhcp_server_attach_event(link->dhcp_server, NULL, 0);
+                if (r < 0)
+                        return r;
+
+                LIST_FOREACH(addresses, address,
+                             link->network->static_addresses) {
+                        struct in_addr pool_start;
+
+                        if (address->family != AF_INET)
+                                continue;
+
+                        /* currently this is picked essentially at random */
+                        r = sd_dhcp_server_set_address(link->dhcp_server,
+                                                       &address->in_addr.in);
+                        if (r < 0)
+                                return r;
+
+                        /* offer 32 addresses starting from the address following the server address */
+                        pool_start.s_addr = htobe32(be32toh(address->in_addr.in.s_addr) + 1);
+                        r = sd_dhcp_server_set_lease_pool(link->dhcp_server,
+                                                          &pool_start, 32);
+
+                        break;
+                }
+
+                /* TODO:
+                r = sd_dhcp_server_set_router(link->dhcp_server,
+                                              &main_address->in_addr.in);
+                if (r < 0)
+                        return r;
+
+                r = sd_dhcp_server_set_prefixlen(link->dhcp_server,
+                                                 main_address->prefixlen);
+                if (r < 0)
+                        return r;
+                */
+        }
+
+        if (link_has_carrier(link->flags, link->operstate)) {
                 r = link_acquire_conf(link);
                 if (r < 0)
                         return r;
diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf
index 5038cb5..7ef467e 100644
--- a/src/network/networkd-network-gperf.gperf
+++ b/src/network/networkd-network-gperf.gperf
@@ -30,6 +30,7 @@ Network.Bond,                config_parse_netdev,                0,
 Network.VLAN,                config_parse_netdev,                0,                             offsetof(Network, vlans)
 Network.MACVLAN,             config_parse_netdev,                0,                             offsetof(Network, macvlans)
 Network.DHCP,                config_parse_bool,                  0,                             offsetof(Network, dhcp)
+Network.DHCPServer,          config_parse_bool,                  0,                             offsetof(Network, dhcp_server)
 Network.IPv4LL,              config_parse_bool,                  0,                             offsetof(Network, ipv4ll)
 Network.Address,             config_parse_address,               0,                             0
 Network.Gateway,             config_parse_gateway,               0,                             0
diff --git a/src/network/networkd.h b/src/network/networkd.h
index 6f77c77..87eadd1 100644
--- a/src/network/networkd.h
+++ b/src/network/networkd.h
@@ -27,6 +27,7 @@
 #include "sd-rtnl.h"
 #include "sd-bus.h"
 #include "sd-dhcp-client.h"
+#include "sd-dhcp-server.h"
 #include "sd-ipv4ll.h"
 #include "udev.h"
 
@@ -149,6 +150,8 @@ struct Network {
         bool dhcp_critical;
         bool ipv4ll;
 
+        bool dhcp_server;
+
         LIST_HEAD(Address, static_addresses);
         LIST_HEAD(Route, static_routes);
 
@@ -256,6 +259,8 @@ struct Link {
         char *lease_file;
         uint16_t original_mtu;
         sd_ipv4ll *ipv4ll;
+
+        sd_dhcp_server *dhcp_server;
 };
 
 struct Manager {

commit 500792d8180c9a11d65f107cdc79dea21b2964c4
Author: Tom Gundersen <teg at jklm.no>
Date:   Mon May 26 15:18:47 2014 +0200

    sd-dhcp-server: add RELEASE support

diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index cb014e2..4ce1054 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -544,7 +544,7 @@ static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
 
         if (be32toh(requested_ip) < be32toh(server->pool_start) ||
             be32toh(requested_ip) >= be32toh(server->pool_start) +
-                                                  + server->pool_size)
+                                             + server->pool_size)
                 return -EINVAL;
 
         return be32toh(requested_ip) - be32toh(server->pool_start);
@@ -743,6 +743,31 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
 
                 break;
         }
+        case DHCP_RELEASE: {
+                int pool_offset;
+
+                log_dhcp_server(server, "RELEASE (0x%x)",
+                                be32toh(req->message->xid));
+
+                if (!existing_lease)
+                        return 0;
+
+                if (existing_lease->address != req->message->ciaddr)
+                        return 0;
+
+                pool_offset = get_pool_offset(server, req->message->ciaddr);
+                if (pool_offset < 0)
+                        return 0;
+
+                if (server->bound_leases[pool_offset] == existing_lease) {
+                        server->bound_leases[pool_offset] = NULL;
+                        hashmap_remove(server->leases_by_client_id, existing_lease);
+                        dhcp_lease_free(existing_lease);
+
+                        return 1;
+                } else
+                        return 0;
+        }
         }
 
         return 0;

commit 5b34277c2015e32e51d10cfa076df2c7106b4537
Author: Tom Gundersen <teg at jklm.no>
Date:   Mon May 26 15:06:42 2014 +0200

    sd-dhcp-server: add dummy DECLINE support

diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 4a06833..cb014e2 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -623,6 +623,15 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
 
                 break;
         }
+        case DHCP_DECLINE:
+                log_dhcp_server(server, "DECLINE (0x%x)",
+                                be32toh(req->message->xid));
+
+                /* TODO: make sure we don't offer this address again */
+
+                return 1;
+
+                break;
         case DHCP_REQUEST:
         {
                 be32_t address;

commit 87322b3aee0dc649ff1ae7a403dcc9d7305baba2
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 22:07:53 2014 +0200

    sd-dhcp-server: track bound leases
    
    Make sure we don't hand out the same IP twice. We still don't
    handle lease expiry.

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index ce2e260..7fe7253 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -23,12 +23,25 @@
 #include "sd-event.h"
 #include "sd-dhcp-server.h"
 
+#include "hashmap.h"
 #include "refcnt.h"
 #include "util.h"
 #include "log.h"
 
 #include "dhcp-internal.h"
 
+typedef struct DHCPClientId {
+        size_t length;
+        uint8_t *data;
+} DHCPClientId;
+
+typedef struct DHCPLease {
+        DHCPClientId client_id;
+
+        be32_t address;
+        usec_t expiration;
+} DHCPLease;
+
 struct sd_dhcp_server {
         RefCount n_ref;
 
@@ -42,12 +55,11 @@ struct sd_dhcp_server {
         be32_t address;
         be32_t pool_start;
         size_t pool_size;
-};
+        size_t next_offer;
 
-typedef struct DHCPClientId {
-        size_t length;
-        uint8_t *data;
-} DHCPClientId;
+        Hashmap *leases_by_client_id;
+        DHCPLease **bound_leases;
+};
 
 typedef struct DHCPRequest {
         /* received message */
@@ -71,3 +83,6 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
 int dhcp_server_send_packet(sd_dhcp_server *server,
                             DHCPRequest *req, DHCPPacket *packet,
                             int type, size_t optoffset);
+
+unsigned long client_id_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]);
+int client_id_compare_func(const void *_a, const void *_b);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 4c43eeb..4a06833 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -23,6 +23,8 @@
 #include <sys/ioctl.h>
 #include <netinet/if_ether.h>
 
+#include "siphash24.h"
+
 #include "sd-dhcp-server.h"
 #include "dhcp-server-internal.h"
 #include "dhcp-internal.h"
@@ -37,6 +39,11 @@ int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *addres
         assert_return(size, -EINVAL);
         assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY);
         assert_return(!server->pool_size, -EBUSY);
+        assert_return(!server->bound_leases, -EBUSY);
+
+        server->bound_leases = new0(DHCPLease*, size);
+        if (!server->bound_leases)
+                return -ENOMEM;
 
         server->pool_start = address->s_addr;
         server->pool_size = size;
@@ -62,13 +69,63 @@ sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
         return server;
 }
 
+unsigned long client_id_hash_func(const void *p, const uint8_t hash_key[HASH_KEY_SIZE]) {
+        uint64_t u;
+        const DHCPClientId *id = p;
+
+        assert(id);
+        assert(id->length);
+        assert(id->data);
+
+        siphash24((uint8_t*) &u, id->data, id->length, hash_key);
+
+        return (unsigned long) u;
+}
+
+int client_id_compare_func(const void *_a, const void *_b) {
+        const DHCPClientId *a, *b;
+
+        a = _a;
+        b = _b;
+
+        assert(!a->length || a->data);
+        assert(!b->length || b->data);
+
+        if (a->length != b->length)
+                return a->length < b->length ? -1 : 1;
+
+        return memcmp(a->data, b->data, a->length);
+}
+
+static void dhcp_lease_free(DHCPLease *lease) {
+        if (!lease)
+                return;
+
+        free(lease->client_id.data);
+        free(lease);
+}
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPLease*, dhcp_lease_free);
+#define _cleanup_dhcp_lease_free_ _cleanup_(dhcp_lease_freep)
+
 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
         if (server && REFCNT_DEC(server->n_ref) <= 0) {
+                DHCPLease *lease;
+                Iterator i;
+
                 log_dhcp_server(server, "UNREF");
 
                 sd_dhcp_server_stop(server);
 
                 sd_event_unref(server->event);
+
+                HASHMAP_FOREACH(lease, server->leases_by_client_id, i) {
+                        hashmap_remove(server->leases_by_client_id, lease);
+                        dhcp_lease_free(lease);
+                }
+
+                hashmap_free(server->leases_by_client_id);
+                free(server->bound_leases);
                 free(server);
         }
 
@@ -90,6 +147,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
         server->fd = -1;
         server->address = htobe32(INADDR_ANY);
         server->index = ifindex;
+        server->leases_by_client_id = hashmap_new(client_id_hash_func, client_id_compare_func);
 
         *ret = server;
         server = NULL;
@@ -478,9 +536,24 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
         return 0;
 }
 
+static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) {
+        assert(server);
+
+        if (!server->pool_size)
+                return -EINVAL;
+
+        if (be32toh(requested_ip) < be32toh(server->pool_start) ||
+            be32toh(requested_ip) >= be32toh(server->pool_start) +
+                                                  + server->pool_size)
+                return -EINVAL;
+
+        return be32toh(requested_ip) - be32toh(server->pool_start);
+}
+
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                                size_t length) {
         _cleanup_dhcp_request_free_ DHCPRequest *req = NULL;
+        DHCPLease *existing_lease;
         int type, r;
 
         assert(server);
@@ -504,10 +577,13 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                 /* this only fails on critical errors */
                 return r;
 
+        existing_lease = hashmap_get(server->leases_by_client_id, &req->client_id);
+
         switch(type) {
         case DHCP_DISCOVER:
         {
-                be32_t address;
+                be32_t address = INADDR_ANY;
+                unsigned i;
 
                 log_dhcp_server(server, "DISCOVER (0x%x)",
                                 be32toh(req->message->xid));
@@ -516,9 +592,22 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                         /* no pool allocated */
                         return 0;
 
-                /* for now pick a random address from the pool */
-                address = htobe32(be32toh(server->pool_start) +
-                                      (random_u32() % server->pool_size));
+                /* for now pick a random free address from the pool */
+                if (existing_lease)
+                        address = existing_lease->address;
+                else {
+                        for (i = 0; i < server->pool_size; i++) {
+                                if (!server->bound_leases[server->next_offer]) {
+                                        address = htobe32(be32toh(server->pool_start) + server->next_offer);
+                                        break;
+                                } else
+                                        server->next_offer = (server->next_offer + 1) % server->pool_size;
+                        }
+                }
+
+                if (address == INADDR_ANY)
+                        /* no free addresses left */
+                        return 0;
 
                 r = server_send_offer(server, req, address);
                 if (r < 0) {
@@ -538,6 +627,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
         {
                 be32_t address;
                 bool init_reboot = false;
+                int pool_offset;
 
                 /* see RFC 2131, section 4.3.2 */
 
@@ -584,20 +674,48 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                         address = req->message->ciaddr;
                 }
 
-                /* for now we just verify that the address is from the pool, not
-                   whether or not it is taken */
-                if (htobe32(req->requested_ip) >= htobe32(server->pool_start) &&
-                    htobe32(req->requested_ip) < htobe32(server->pool_start) +
-                                                  + server->pool_size) {
+                pool_offset = get_pool_offset(server, address);
+
+                /* verify that the requested address is from the pool, and either
+                   owned by the current client or free */
+                if (pool_offset >= 0 &&
+                    server->bound_leases[pool_offset] == existing_lease) {
+                        DHCPLease *lease;
+                        usec_t time_now;
+
+                        if (!existing_lease) {
+                                lease = new0(DHCPLease, 1);
+                                lease->address = req->requested_ip;
+                                lease->client_id.data = memdup(req->client_id.data,
+                                                               req->client_id.length);
+                                if (!lease->client_id.data)
+                                        return -ENOMEM;
+                                lease->client_id.length = req->client_id.length;
+                        } else
+                                lease = existing_lease;
+
+                        r = sd_event_now(server->event, CLOCK_MONOTONIC, &time_now);
+                        if (r < 0)
+                                time_now = now(CLOCK_MONOTONIC);
+                        lease->expiration = req->lifetime * USEC_PER_SEC + time_now;
+
                         r = server_send_ack(server, req, address);
                         if (r < 0) {
                                 /* this only fails on critical errors */
                                 log_dhcp_server(server, "could not send ack: %s",
                                                 strerror(-r));
+
+                                if (!existing_lease)
+                                        dhcp_lease_free(lease);
+
                                 return r;
                         } else {
                                 log_dhcp_server(server, "ACK (0x%x)",
                                                 be32toh(req->message->xid));
+
+                                server->bound_leases[pool_offset] = lease;
+                                hashmap_put(server->leases_by_client_id, &lease->client_id, lease);
+
                                 return DHCP_ACK;
                         }
                 } else if (init_reboot) {
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index f738b3b..6b3b4d0 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -90,6 +90,11 @@ static void test_message_handler(void) {
                         uint8_t length;
                         be32_t address;
                 } _packed_ option_server_id;
+                struct {
+                        uint8_t code;
+                        uint8_t length;
+                        uint8_t id[7];
+                } _packed_ option_client_id;
                 uint8_t end;
         } _packed_ test = {
                 .message.op = BOOTREQUEST,
@@ -156,14 +161,67 @@ static void test_message_handler(void) {
         test.option_server_id.address = htobe32(INADDR_LOOPBACK);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+
         test.option_server_id.address = htobe32(0x12345678);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
         test.option_server_id.address = htobe32(INADDR_LOOPBACK);
+        test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+
+        test.option_client_id.code = DHCP_OPTION_CLIENT_IDENTIFIER;
+        test.option_client_id.length = 7;
+        test.option_client_id.id[0] = 0x01;
+        test.option_client_id.id[1] = 'A';
+        test.option_client_id.id[2] = 'B';
+        test.option_client_id.id[3] = 'C';
+        test.option_client_id.id[4] = 'D';
+        test.option_client_id.id[5] = 'E';
+        test.option_client_id.id[6] = 'F';
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
 }
 
+static void test_client_id_hash(void) {
+        DHCPClientId a = {
+                .length = 4,
+        }, b = {
+                .length = 4,
+        };
+        uint8_t hash_key[HASH_KEY_SIZE] = {
+                '0', '1', '2', '3', '4', '5', '6', '7',
+                '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
+        };
+
+        a.data = (uint8_t*)strdup("abcd");
+        b.data = (uint8_t*)strdup("abcd");
+
+        assert_se(client_id_compare_func(&a, &b) == 0);
+        assert_se(client_id_hash_func(&a, hash_key) == client_id_hash_func(&b, hash_key));
+        a.length = 3;
+        assert_se(client_id_compare_func(&a, &b) != 0);
+        a.length = 4;
+        assert_se(client_id_compare_func(&a, &b) == 0);
+        assert_se(client_id_hash_func(&a, hash_key) == client_id_hash_func(&b, hash_key));
+
+        b.length = 3;
+        assert_se(client_id_compare_func(&a, &b) != 0);
+        b.length = 4;
+        assert_se(client_id_compare_func(&a, &b) == 0);
+        assert_se(client_id_hash_func(&a, hash_key) == client_id_hash_func(&b, hash_key));
+
+        free(b.data);
+        b.data = (uint8_t*)strdup("abce");
+        assert_se(client_id_compare_func(&a, &b) != 0);
+
+        free(a.data);
+        free(b.data);
+}
+
 int main(int argc, char *argv[]) {
         _cleanup_event_unref_ sd_event *e;
 
@@ -175,6 +233,7 @@ int main(int argc, char *argv[]) {
 
         test_basic(e);
         test_message_handler();
+        test_client_id_hash();
 
         return 0;
 }

commit c7d9ffe6d629cb5b34dd749e4a88b190b11a0f48
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 21:47:38 2014 +0200

    sd-dhcp-server: add support for clients requesting lease lifetime

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index cd480e7..ce2e260 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -58,6 +58,7 @@ typedef struct DHCPRequest {
         size_t max_optlen;
         be32_t server_id;
         be32_t requested_ip;
+        int lifetime;
 } DHCPRequest;
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 75f4316..4c43eeb 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -337,8 +337,7 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t ad
 
         packet->dhcp.yiaddr = address;
 
-        /* for one minute */
-        lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
+        lease_time = htobe32(req->lifetime);
         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
         if (r < 0)
@@ -363,8 +362,7 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t addr
 
         packet->dhcp.yiaddr = address;
 
-        /* for ten seconds */
-        lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
+        lease_time = htobe32(req->lifetime);
         r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
                                DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
         if (r < 0)
@@ -400,6 +398,11 @@ static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
         assert(req);
 
         switch(code) {
+        case DHCP_OPTION_IP_ADDRESS_LEASE_TIME:
+                if (len == 4)
+                        req->lifetime = be32toh(*(be32_t*)option);
+
+                break;
         case DHCP_OPTION_REQUESTED_IP_ADDRESS:
                 if (len == 4)
                         req->requested_ip = *(be32_t*)option;
@@ -469,6 +472,9 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) {
         if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE)
                 req->max_optlen = DHCP_MIN_OPTIONS_SIZE;
 
+        if (!req->lifetime)
+                req->lifetime = DHCP_DEFAULT_LEASE_TIME;
+
         return 0;
 }
 

commit bd57b45029ff25067704c9538e79f31e71c10045
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 20:39:02 2014 +0200

    sd-dhcp-server: add basic NAK support

diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index e170cfa..75f4316 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -303,7 +303,7 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
         assert(server);
         assert(ret);
         assert(_optoffset);
-        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK));
+        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK));
 
         packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
         if (!packet)
@@ -377,6 +377,22 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t addr
         return 0;
 }
 
+static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) {
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t offset;
+        int r;
+
+        r = server_message_init(server, &packet, DHCP_NAK, &offset, req);
+        if (r < 0)
+                return r;
+
+        r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
                          void *user_data) {
         DHCPRequest *req = user_data;
@@ -515,6 +531,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
         case DHCP_REQUEST:
         {
                 be32_t address;
+                bool init_reboot = false;
 
                 /* see RFC 2131, section 4.3.2 */
 
@@ -546,8 +563,9 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                                 /* this MUST be zero */
                                 return 0;
 
-                        /* TODO: check if requested IP is correct, NAK if not */
+                        /* TODO: check more carefully if IP is correct */
                         address = req->requested_ip;
+                        init_reboot = true;
                 } else {
                         log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
                                         be32toh(req->message->xid));
@@ -576,8 +594,19 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                                                 be32toh(req->message->xid));
                                 return DHCP_ACK;
                         }
-                } else
-                        return 0;
+                } else if (init_reboot) {
+                        r = server_send_nak(server, req);
+                        if (r < 0) {
+                                /* this only fails on critical errors */
+                                log_dhcp_server(server, "could not send nak: %s",
+                                                strerror(-r));
+                                return r;
+                        } else {
+                                log_dhcp_server(server, "NAK (0x%x)",
+                                                be32toh(req->message->xid));
+                                return DHCP_NAK;
+                        }
+                }
 
                 break;
         }
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index 10fc236..f738b3b 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -150,7 +150,7 @@ static void test_message_handler(void) {
         test.option_requested_ip.code = DHCP_OPTION_REQUESTED_IP_ADDRESS;
         test.option_requested_ip.length = 4;
         test.option_requested_ip.address = htobe32(0x12345678);
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_NAK);
         test.option_server_id.code = DHCP_OPTION_SERVER_IDENTIFIER;
         test.option_server_id.length = 4;
         test.option_server_id.address = htobe32(INADDR_LOOPBACK);
@@ -159,8 +159,8 @@ static void test_message_handler(void) {
         test.option_server_id.address = htobe32(0x12345678);
         test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
-        test.option_server_id.address = htobe32(INADDR_LOOPBACK + 3);
-        test.option_requested_ip.address = htobe32(0x12345678);
+        test.option_server_id.address = htobe32(INADDR_LOOPBACK);
+        test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30);
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
 }
 

commit 2dead8129f7b6fe644e17e1dc1739bebacfe1364
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 18:28:03 2014 +0200

    sd-dhcp-server: add basic REQUEST/ACK support

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 381304e..cd480e7 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -40,6 +40,8 @@ struct sd_dhcp_server {
 
         int index;
         be32_t address;
+        be32_t pool_start;
+        size_t pool_size;
 };
 
 typedef struct DHCPClientId {
@@ -55,6 +57,7 @@ typedef struct DHCPRequest {
         DHCPClientId client_id;
         size_t max_optlen;
         be32_t server_id;
+        be32_t requested_ip;
 } DHCPRequest;
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 07715c5..e170cfa 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -29,6 +29,21 @@
 
 #define DHCP_DEFAULT_LEASE_TIME         60
 
+int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *address,
+                                  size_t size) {
+        assert_return(server, -EINVAL);
+        assert_return(address, -EINVAL);
+        assert_return(address->s_addr, -EINVAL);
+        assert_return(size, -EINVAL);
+        assert_return(server->pool_start == htobe32(INADDR_ANY), -EBUSY);
+        assert_return(!server->pool_size, -EBUSY);
+
+        server->pool_start = address->s_addr;
+        server->pool_size = size;
+
+        return 0;
+}
+
 int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
         assert_return(server, -EINVAL);
         assert_return(address, -EINVAL);
@@ -288,7 +303,7 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
         assert(server);
         assert(ret);
         assert(_optoffset);
-        assert(type == DHCP_OFFER);
+        assert(IN_SET(type, DHCP_OFFER, DHCP_ACK));
 
         packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
         if (!packet)
@@ -310,7 +325,7 @@ static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
         return 0;
 }
 
-static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
+static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
         _cleanup_free_ DHCPPacket *packet = NULL;
         size_t offset;
         be32_t lease_time;
@@ -320,8 +335,7 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
         if (r < 0)
                 return r;
 
-        /* for now offer a random IP */
-        packet->dhcp.yiaddr = random_u32();
+        packet->dhcp.yiaddr = address;
 
         /* for one minute */
         lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
@@ -337,6 +351,32 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
         return 0;
 }
 
+static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, be32_t address) {
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t offset;
+        be32_t lease_time;
+        int r;
+
+        r = server_message_init(server, &packet, DHCP_ACK, &offset, req);
+        if (r < 0)
+                return r;
+
+        packet->dhcp.yiaddr = address;
+
+        /* for ten seconds */
+        lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                               DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
+        if (r < 0)
+                return r;
+
+        r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
                          void *user_data) {
         DHCPRequest *req = user_data;
@@ -344,6 +384,11 @@ static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
         assert(req);
 
         switch(code) {
+        case DHCP_OPTION_REQUESTED_IP_ADDRESS:
+                if (len == 4)
+                        req->requested_ip = *(be32_t*)option;
+
+                break;
         case DHCP_OPTION_SERVER_IDENTIFIER:
                 if (len == 4)
                         req->server_id = *(be32_t*)option;
@@ -439,10 +484,21 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
 
         switch(type) {
         case DHCP_DISCOVER:
+        {
+                be32_t address;
+
                 log_dhcp_server(server, "DISCOVER (0x%x)",
                                 be32toh(req->message->xid));
 
-                r = server_send_offer(server, req);
+                if (!server->pool_size)
+                        /* no pool allocated */
+                        return 0;
+
+                /* for now pick a random address from the pool */
+                address = htobe32(be32toh(server->pool_start) +
+                                      (random_u32() % server->pool_size));
+
+                r = server_send_offer(server, req, address);
                 if (r < 0) {
                         /* this only fails on critical errors */
                         log_dhcp_server(server, "could not send offer: %s",
@@ -456,6 +512,76 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
 
                 break;
         }
+        case DHCP_REQUEST:
+        {
+                be32_t address;
+
+                /* see RFC 2131, section 4.3.2 */
+
+                if (req->server_id) {
+                        log_dhcp_server(server, "REQUEST (selecting) (0x%x)",
+                                        be32toh(req->message->xid));
+
+                        /* SELECTING */
+                        if (req->server_id != server->address)
+                                /* client did not pick us */
+                                return 0;
+
+                        if (req->message->ciaddr)
+                                /* this MUST be zero */
+                                return 0;
+
+                        if (!req->requested_ip)
+                                /* this must be filled in with the yiaddr
+                                   from the chosen OFFER */
+                                return 0;
+
+                        address = req->requested_ip;
+                } else if (req->requested_ip) {
+                        log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)",
+                                        be32toh(req->message->xid));
+
+                        /* INIT-REBOOT */
+                        if (req->message->ciaddr)
+                                /* this MUST be zero */
+                                return 0;
+
+                        /* TODO: check if requested IP is correct, NAK if not */
+                        address = req->requested_ip;
+                } else {
+                        log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)",
+                                        be32toh(req->message->xid));
+
+                        /* REBINDING / RENEWING */
+                        if (!req->message->ciaddr)
+                                /* this MUST be filled in with clients IP address */
+                                return 0;
+
+                        address = req->message->ciaddr;
+                }
+
+                /* for now we just verify that the address is from the pool, not
+                   whether or not it is taken */
+                if (htobe32(req->requested_ip) >= htobe32(server->pool_start) &&
+                    htobe32(req->requested_ip) < htobe32(server->pool_start) +
+                                                  + server->pool_size) {
+                        r = server_send_ack(server, req, address);
+                        if (r < 0) {
+                                /* this only fails on critical errors */
+                                log_dhcp_server(server, "could not send ack: %s",
+                                                strerror(-r));
+                                return r;
+                        } else {
+                                log_dhcp_server(server, "ACK (0x%x)",
+                                                be32toh(req->message->xid));
+                                return DHCP_ACK;
+                        }
+                } else
+                        return 0;
+
+                break;
+        }
+        }
 
         return 0;
 }
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index a252e70..10fc236 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -59,6 +59,11 @@ static void test_basic(sd_event *event) {
         assert_se(sd_dhcp_server_set_address(server, &address_lo) >= 0);
         assert_se(sd_dhcp_server_set_address(server, &address_lo) == -EBUSY);
 
+        assert_se(sd_dhcp_server_set_lease_pool(server, &address_any, 1) == -EINVAL);
+        assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 0) == -EINVAL);
+        assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 1) >= 0);
+        assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 1) == -EBUSY);
+
         assert_se(sd_dhcp_server_start(server) >= 0);
         assert_se(sd_dhcp_server_start(server) == -EBUSY);
         assert_se(sd_dhcp_server_stop(server) >= 0);
@@ -75,11 +80,23 @@ static void test_message_handler(void) {
                         uint8_t length;
                         uint8_t type;
                 } _packed_ option_type;
+                struct {
+                        uint8_t code;
+                        uint8_t length;
+                        be32_t address;
+                } _packed_ option_requested_ip;
+                struct {
+                        uint8_t code;
+                        uint8_t length;
+                        be32_t address;
+                } _packed_ option_server_id;
                 uint8_t end;
         } _packed_ test = {
                 .message.op = BOOTREQUEST,
                 .message.htype = ARPHRD_ETHER,
                 .message.hlen = ETHER_ADDR_LEN,
+                .message.xid = htobe32(0x12345678),
+                .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' },
                 .option_type.code = DHCP_OPTION_MESSAGE_TYPE,
                 .option_type.length = 1,
                 .option_type.type = DHCP_DISCOVER,
@@ -94,6 +111,8 @@ static void test_message_handler(void) {
         assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
         assert_se(sd_dhcp_server_start(server) >= 0);
 
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        assert_se(sd_dhcp_server_set_lease_pool(server, &address_lo, 10) >= 0);
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.end = 0;
@@ -125,6 +144,24 @@ static void test_message_handler(void) {
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
         test.message.hlen = ETHER_ADDR_LEN;
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
+
+        test.option_type.type = DHCP_REQUEST;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.option_requested_ip.code = DHCP_OPTION_REQUESTED_IP_ADDRESS;
+        test.option_requested_ip.length = 4;
+        test.option_requested_ip.address = htobe32(0x12345678);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.option_server_id.code = DHCP_OPTION_SERVER_IDENTIFIER;
+        test.option_server_id.length = 4;
+        test.option_server_id.address = htobe32(INADDR_LOOPBACK);
+        test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK);
+        test.option_server_id.address = htobe32(0x12345678);
+        test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.option_server_id.address = htobe32(INADDR_LOOPBACK + 3);
+        test.option_requested_ip.address = htobe32(0x12345678);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
 }
 
 int main(int argc, char *argv[]) {
diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h
index 5edeffc..cd0ff72 100644
--- a/src/systemd/sd-dhcp-server.h
+++ b/src/systemd/sd-dhcp-server.h
@@ -42,4 +42,5 @@ int sd_dhcp_server_start(sd_dhcp_server *server);
 int sd_dhcp_server_stop(sd_dhcp_server *server);
 
 int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address);
+int sd_dhcp_server_set_lease_pool(sd_dhcp_server *server, struct in_addr *start, size_t size);
 #endif

commit 4dc355680460fdc8e0d590d8572dff1b6a257d88
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 17:31:17 2014 +0200

    sd-dhcp-server: add basic DISCOVER/OFFER support

diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 994c7be..07715c5 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -27,6 +27,8 @@
 #include "dhcp-server-internal.h"
 #include "dhcp-internal.h"
 
+#define DHCP_DEFAULT_LEASE_TIME         60
+
 int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
         assert_return(server, -EINVAL);
         assert_return(address, -EINVAL);
@@ -277,6 +279,64 @@ int dhcp_server_send_packet(sd_dhcp_server *server,
                                                     sizeof(DHCPPacket) + optoffset);
 }
 
+static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret,
+                               uint8_t type, size_t *_optoffset, DHCPRequest *req) {
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t optoffset;
+        int r;
+
+        assert(server);
+        assert(ret);
+        assert(_optoffset);
+        assert(type == DHCP_OFFER);
+
+        packet = malloc0(sizeof(DHCPPacket) + req->max_optlen);
+        if (!packet)
+                return -ENOMEM;
+
+        r = dhcp_message_init(&packet->dhcp, BOOTREPLY, be32toh(req->message->xid),
+                              type, req->max_optlen, &optoffset);
+        if (r < 0)
+                return r;
+
+        packet->dhcp.flags = req->message->flags;
+        packet->dhcp.giaddr = req->message->giaddr;
+        memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN);
+
+        *_optoffset = optoffset;
+        *ret = packet;
+        packet = NULL;
+
+        return 0;
+}
+
+static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req) {
+        _cleanup_free_ DHCPPacket *packet = NULL;
+        size_t offset;
+        be32_t lease_time;
+        int r;
+
+        r = server_message_init(server, &packet, DHCP_OFFER, &offset, req);
+        if (r < 0)
+                return r;
+
+        /* for now offer a random IP */
+        packet->dhcp.yiaddr = random_u32();
+
+        /* for one minute */
+        lease_time = htobe32(DHCP_DEFAULT_LEASE_TIME);
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0,
+                               DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time);
+        if (r < 0)
+                return r;
+
+        r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
                          void *user_data) {
         DHCPRequest *req = user_data;
@@ -377,9 +437,27 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                 /* this only fails on critical errors */
                 return r;
 
-        log_dhcp_server(server, "received message of type %d", type);
+        switch(type) {
+        case DHCP_DISCOVER:
+                log_dhcp_server(server, "DISCOVER (0x%x)",
+                                be32toh(req->message->xid));
+
+                r = server_send_offer(server, req);
+                if (r < 0) {
+                        /* this only fails on critical errors */
+                        log_dhcp_server(server, "could not send offer: %s",
+                                        strerror(-r));
+                        return r;
+                } else {
+                        log_dhcp_server(server, "OFFER (0x%x)",
+                                        be32toh(req->message->xid));
+                        return DHCP_OFFER;
+                }
 
-        return 1;
+                break;
+        }
+
+        return 0;
 }
 
 static int server_receive_message(sd_event_source *s, int fd,
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index f0f9cfe..a252e70 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -94,13 +94,13 @@ static void test_message_handler(void) {
         assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
         assert_se(sd_dhcp_server_start(server) >= 0);
 
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.end = 0;
         /* TODO, shouldn't this fail? */
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
         test.end = DHCP_OPTION_END;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.option_type.code = 0;
         test.option_type.length = 0;
@@ -109,22 +109,22 @@ static void test_message_handler(void) {
         test.option_type.code = DHCP_OPTION_MESSAGE_TYPE;
         test.option_type.length = 1;
         test.option_type.type = DHCP_DISCOVER;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.message.op = 0;
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
         test.message.op = BOOTREQUEST;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.message.htype = 0;
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
         test.message.htype = ARPHRD_ETHER;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 
         test.message.hlen = 0;
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
         test.message.hlen = ETHER_ADDR_LEN;
-        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER);
 }
 
 int main(int argc, char *argv[]) {

commit 969b009d9416806911b9b52e7e7bc619c0c1a931
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 17:20:57 2014 +0200

    sd-dhcp-server: add support for sending messages

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 58a9877..381304e 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -64,3 +64,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
 
 int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
                                size_t length);
+int dhcp_server_send_packet(sd_dhcp_server *server,
+                            DHCPRequest *req, DHCPPacket *packet,
+                            int type, size_t optoffset);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 37f8158..994c7be 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -127,6 +127,156 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
         return 0;
 }
 
+static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, DHCPPacket *packet,
+                                        size_t len) {
+        union sockaddr_union link = {
+                .ll.sll_family = AF_PACKET,
+                .ll.sll_protocol = htons(ETH_P_IP),
+                .ll.sll_ifindex = server->index,
+                .ll.sll_halen = ETH_ALEN,
+        };
+        int r;
+
+        assert(server);
+        assert(server->index > 0);
+        assert(server->address);
+        assert(packet);
+        assert(len > sizeof(DHCPPacket));
+
+        memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN);
+
+        dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER,
+                                      packet->dhcp.yiaddr, DHCP_PORT_CLIENT, len);
+
+        r = dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination,
+                                DHCPMessage *message, size_t len) {
+        union sockaddr_union dest = {
+                .in.sin_family = AF_INET,
+                .in.sin_port = htobe16(DHCP_PORT_CLIENT),
+                .in.sin_addr.s_addr = destination,
+        };
+        struct iovec iov = {
+                .iov_base = message,
+                .iov_len = len,
+        };
+        uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {};
+        struct msghdr msg = {
+                .msg_name = &dest,
+                .msg_namelen = sizeof(dest.in),
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+                .msg_control = cmsgbuf,
+                .msg_controllen = sizeof(cmsgbuf),
+        };
+        struct cmsghdr *cmsg;
+        struct in_pktinfo *pktinfo;
+        int r;
+
+        assert(server);
+        assert(server->fd > 0);
+        assert(message);
+        assert(len > sizeof(DHCPMessage));
+
+        cmsg = CMSG_FIRSTHDR(&msg);
+        assert(cmsg);
+
+        cmsg->cmsg_level = IPPROTO_IP;
+        cmsg->cmsg_type = IP_PKTINFO;
+        cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+
+        /* we attach source interface and address info to the message
+           rather than binding the socket. This will be mostly useful
+           when we gain support for arbitrary number of server addresses
+         */
+        pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg);
+        assert(pktinfo);
+
+        pktinfo->ipi_ifindex = server->index;
+        pktinfo->ipi_spec_dst.s_addr = server->address;
+
+        r = sendmsg(server->fd, &msg, 0);
+        if (r < 0)
+                return -errno;
+
+        return 0;
+}
+
+static bool requested_broadcast(DHCPRequest *req) {
+        assert(req);
+
+        return req->message->flags & htobe16(0x8000);
+}
+
+int dhcp_server_send_packet(sd_dhcp_server *server,
+                            DHCPRequest *req, DHCPPacket *packet,
+                            int type, size_t optoffset) {
+        be32_t destination = INADDR_ANY;
+        int r;
+
+        assert(server);
+        assert(req);
+        assert(req->max_optlen);
+        assert(optoffset <= req->max_optlen);
+        assert(packet);
+
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+                               DHCP_OPTION_SERVER_IDENTIFIER,
+                               4, &server->address);
+        if (r < 0)
+                return r;
+
+        r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0,
+                               DHCP_OPTION_END, 0, NULL);
+        if (r < 0)
+                return r;
+
+        /* RFC 2131 Section 4.1
+
+           If the ’giaddr’ field in a DHCP message from a client is non-zero,
+           the server sends any return messages to the ’DHCP server’ port on the
+           BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’
+           field is zero and the ’ciaddr’ field is nonzero, then the server
+           unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’.
+           If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is
+           set, then the server broadcasts DHCPOFFER and DHCPACK messages to
+           0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and
+           ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK
+           messages to the client’s hardware address and ’yiaddr’ address. In
+           all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK
+           messages to 0xffffffff.
+
+           Section 4.3.2
+
+           If ’giaddr’ is set in the DHCPREQUEST message, the client is on a
+           different subnet. The server MUST set the broadcast bit in the
+           DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the
+           client, because the client may not have a correct network address
+           or subnet mask, and the client may not be answering ARP requests.
+         */
+        if (req->message->giaddr) {
+                destination = req->message->giaddr;
+                if (type == DHCP_NAK)
+                        packet->dhcp.flags = htobe16(0x8000);
+        } else if (req->message->ciaddr && type != DHCP_NAK)
+                destination = req->message->ciaddr;
+
+        if (destination || requested_broadcast(req) || type == DHCP_NAK)
+                return dhcp_server_send_udp(server, destination, &packet->dhcp,
+                                            sizeof(DHCPMessage) + optoffset);
+        else
+                /* we cannot send UDP packet to specific MAC address when the address is
+                   not yet configured, so must fall back to raw packets */
+                return dhcp_server_send_unicast_raw(server, packet,
+                                                    sizeof(DHCPPacket) + optoffset);
+}
+
 static int parse_request(uint8_t code, uint8_t len, const uint8_t *option,
                          void *user_data) {
         DHCPRequest *req = user_data;

commit 20af7091de0cdf92bf299addfc3f96c3ef805bd8
Author: Tom Gundersen <teg at jklm.no>
Date:   Sun May 25 00:29:13 2014 +0200

    sd-dhcp-server: add support for setting the server address

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 6c2f2b4..58a9877 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -39,6 +39,7 @@ struct sd_dhcp_server {
         int fd_raw;
 
         int index;
+        be32_t address;
 };
 
 typedef struct DHCPClientId {
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index ecdc15d..37f8158 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -27,6 +27,17 @@
 #include "dhcp-server-internal.h"
 #include "dhcp-internal.h"
 
+int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address) {
+        assert_return(server, -EINVAL);
+        assert_return(address, -EINVAL);
+        assert_return(address->s_addr, -EINVAL);
+        assert_return(server->address == htobe32(INADDR_ANY), -EBUSY);
+
+        server->address = address->s_addr;
+
+        return 0;
+}
+
 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
         if (server)
                 assert_se(REFCNT_INC(server->n_ref) >= 2);
@@ -60,6 +71,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
         server->n_ref = REFCNT_INIT;
         server->fd_raw = -1;
         server->fd = -1;
+        server->address = htobe32(INADDR_ANY);
         server->index = ifindex;
 
         *ret = server;
@@ -281,6 +293,7 @@ int sd_dhcp_server_start(sd_dhcp_server *server) {
         assert_return(!server->receive_message, -EBUSY);
         assert_return(server->fd_raw == -1, -EBUSY);
         assert_return(server->fd == -1, -EBUSY);
+        assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH);
 
         r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0);
         if (r < 0) {
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index dd0f29a..f0f9cfe 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -32,6 +32,12 @@
 
 static void test_basic(sd_event *event) {
         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
+        struct in_addr address_lo = {
+                .s_addr = htonl(INADDR_LOOPBACK),
+        };
+        struct in_addr address_any = {
+                .s_addr = htonl(INADDR_ANY),
+        };
 
         /* attach to loopback interface */
         assert_se(sd_dhcp_server_new(&server, 1) >= 0);
@@ -48,6 +54,11 @@ static void test_basic(sd_event *event) {
         assert_se(sd_dhcp_server_ref(server) == server);
         assert_se(!sd_dhcp_server_unref(server));
 
+        assert_se(sd_dhcp_server_start(server) == -EUNATCH);
+        assert_se(sd_dhcp_server_set_address(server, &address_any) == -EINVAL);
+        assert_se(sd_dhcp_server_set_address(server, &address_lo) >= 0);
+        assert_se(sd_dhcp_server_set_address(server, &address_lo) == -EBUSY);
+
         assert_se(sd_dhcp_server_start(server) >= 0);
         assert_se(sd_dhcp_server_start(server) == -EBUSY);
         assert_se(sd_dhcp_server_stop(server) >= 0);
@@ -74,8 +85,14 @@ static void test_message_handler(void) {
                 .option_type.type = DHCP_DISCOVER,
                 .end = DHCP_OPTION_END,
         };
+        struct in_addr address_lo = {
+                .s_addr = htonl(INADDR_LOOPBACK),
+        };
 
         assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+        assert_se(sd_dhcp_server_set_address(server, &address_lo) >= 0);
+        assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+        assert_se(sd_dhcp_server_start(server) >= 0);
 
         assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
 
diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h
index ab63294..5edeffc 100644
--- a/src/systemd/sd-dhcp-server.h
+++ b/src/systemd/sd-dhcp-server.h
@@ -41,4 +41,5 @@ sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client);
 int sd_dhcp_server_start(sd_dhcp_server *server);
 int sd_dhcp_server_stop(sd_dhcp_server *server);
 
+int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address);
 #endif

commit 8de4a226c71ef43e652274b33b5d19211a44ac7b
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 23:03:49 2014 +0200

    sd-dhcp-server: bind to raw socket for sending
    
    We would like to use the UDP socket, but we cannot as we need to specify
    the MAC address manually.

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 3d49cba..6c2f2b4 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -36,6 +36,7 @@ struct sd_dhcp_server {
         int event_priority;
         sd_event_source *receive_message;
         int fd;
+        int fd_raw;
 
         int index;
 };
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index b1f2fa0..ecdc15d 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -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");
@@ -277,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);

commit 816e2e7af96886e4a43194042ef61ba9fec2c77d
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 22:14:32 2014 +0200

    sd-dhcp-server: add basic message parsing
    
    Parse the maximum message size the client can accept and the client id, falling back to
    sane defaults if they are not set.

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 63883fa..3d49cba 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -40,6 +40,21 @@ struct sd_dhcp_server {
         int index;
 };
 
+typedef struct DHCPClientId {
+        size_t length;
+        uint8_t *data;
+} DHCPClientId;
+
+typedef struct DHCPRequest {
+        /* received message */
+        DHCPMessage *message;
+
+        /* options */
+        DHCPClientId client_id;
+        size_t max_optlen;
+        be32_t server_id;
+} DHCPRequest;
+
 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
 #define _cleanup_dhcp_server_unref_ _cleanup_(sd_dhcp_server_unrefp)
 
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 57fb09a..b1f2fa0 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -113,9 +113,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 +200,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;

commit be077570f779664ed87b50f60608df9fbe258821
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 21:04:27 2014 +0200

    sd-dhcp-server: add basic message handling and verification

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 6484dd3..63883fa 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -27,6 +27,8 @@
 #include "util.h"
 #include "log.h"
 
+#include "dhcp-internal.h"
+
 struct sd_dhcp_server {
         RefCount n_ref;
 
@@ -42,3 +44,6 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
 #define _cleanup_dhcp_server_unref_ _cleanup_(sd_dhcp_server_unrefp)
 
 #define log_dhcp_server(client, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "DHCP SERVER: " fmt, ##__VA_ARGS__)
+
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
+                               size_t length);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index e4396a0..57fb09a 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -21,6 +21,7 @@
 ***/
 
 #include <sys/ioctl.h>
+#include <netinet/if_ether.h>
 
 #include "sd-dhcp-server.h"
 #include "dhcp-server-internal.h"
@@ -112,9 +113,30 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
         return 0;
 }
 
+int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message,
+                               size_t length) {
+        int type;
+
+        assert(server);
+        assert(message);
+
+        if (message->op != BOOTREQUEST ||
+            message->htype != ARPHRD_ETHER ||
+            message->hlen != ETHER_ADDR_LEN)
+                return 0;
+
+        type = dhcp_option_parse(message, length, NULL, NULL);
+        if (type < 0)
+                return 0;
+
+        log_dhcp_server(server, "received message of type %d", type);
+
+        return 1;
+}
+
 static int server_receive_message(sd_event_source *s, int fd,
                                   uint32_t revents, void *userdata) {
-        _cleanup_free_ uint8_t *message = NULL;
+        _cleanup_free_ DHCPMessage *message = NULL;
         uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
         sd_dhcp_server *server = userdata;
         struct iovec iov = {};
@@ -145,6 +167,8 @@ static int server_receive_message(sd_event_source *s, int fd,
         len = recvmsg(fd, &msg, 0);
         if (len < buflen)
                 return 0;
+        else if ((size_t)len < sizeof(DHCPMessage))
+                return 0;
 
         for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
                 if (cmsg->cmsg_level == IPPROTO_IP &&
@@ -160,9 +184,7 @@ static int server_receive_message(sd_event_source *s, int fd,
                 }
         }
 
-        log_dhcp_server(server, "received message");
-
-        return 1;
+        return dhcp_server_handle_message(server, message, (size_t)len);
 }
 
 int sd_dhcp_server_start(sd_dhcp_server *server) {
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index 2feb124..dd0f29a 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -20,6 +20,7 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <netinet/if_ether.h>
 #include <assert.h>
 #include <errno.h>
 
@@ -54,6 +55,61 @@ static void test_basic(sd_event *event) {
         assert_se(sd_dhcp_server_start(server) >= 0);
 }
 
+static void test_message_handler(void) {
+        _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
+        struct {
+                DHCPMessage message;
+                struct {
+                        uint8_t code;
+                        uint8_t length;
+                        uint8_t type;
+                } _packed_ option_type;
+                uint8_t end;
+        } _packed_ test = {
+                .message.op = BOOTREQUEST,
+                .message.htype = ARPHRD_ETHER,
+                .message.hlen = ETHER_ADDR_LEN,
+                .option_type.code = DHCP_OPTION_MESSAGE_TYPE,
+                .option_type.length = 1,
+                .option_type.type = DHCP_DISCOVER,
+                .end = DHCP_OPTION_END,
+        };
+
+        assert_se(sd_dhcp_server_new(&server, 1) >= 0);
+
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+
+        test.end = 0;
+        /* TODO, shouldn't this fail? */
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+        test.end = DHCP_OPTION_END;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+
+        test.option_type.code = 0;
+        test.option_type.length = 0;
+        test.option_type.type = 0;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.option_type.code = DHCP_OPTION_MESSAGE_TYPE;
+        test.option_type.length = 1;
+        test.option_type.type = DHCP_DISCOVER;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+
+        test.message.op = 0;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.message.op = BOOTREQUEST;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+
+        test.message.htype = 0;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.message.htype = ARPHRD_ETHER;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+
+        test.message.hlen = 0;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0);
+        test.message.hlen = ETHER_ADDR_LEN;
+        assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 1);
+}
+
 int main(int argc, char *argv[]) {
         _cleanup_event_unref_ sd_event *e;
 
@@ -64,6 +120,7 @@ int main(int argc, char *argv[]) {
         assert_se(sd_event_new(&e) >= 0);
 
         test_basic(e);
+        test_message_handler();
 
         return 0;
 }

commit 3a864fe4a894745ac61f1ecabd7cadf04139a284
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 19:38:17 2014 +0200

    sd-dhcp-server: bind to a given interface
    
    We will (at least at first), restrict our focus to running the server
    on at most one interface.

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index 8191ef7..6484dd3 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -34,6 +34,8 @@ struct sd_dhcp_server {
         int event_priority;
         sd_event_source *receive_message;
         int fd;
+
+        int index;
 };
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index a9768f8..e4396a0 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -46,10 +46,11 @@ sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
         return NULL;
 }
 
-int sd_dhcp_server_new(sd_dhcp_server **ret) {
+int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) {
         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
 
         assert_return(ret, -EINVAL);
+        assert_return(ifindex > 0, -EINVAL);
 
         server = new0(sd_dhcp_server, 1);
         if (!server)
@@ -57,6 +58,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret) {
 
         server->n_ref = REFCNT_INIT;
         server->fd = -1;
+        server->index = ifindex;
 
         *ret = server;
         server = NULL;
@@ -113,12 +115,16 @@ int sd_dhcp_server_stop(sd_dhcp_server *server) {
 static int server_receive_message(sd_event_source *s, int fd,
                                   uint32_t revents, void *userdata) {
         _cleanup_free_ uint8_t *message = NULL;
+        uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))];
         sd_dhcp_server *server = userdata;
         struct iovec iov = {};
         struct msghdr msg = {
                 .msg_iov = &iov,
                 .msg_iovlen = 1,
+                .msg_control = cmsgbuf,
+                .msg_controllen = sizeof(cmsgbuf),
         };
+        struct cmsghdr *cmsg;
         int buflen = 0, len, r;
 
         assert(server);
@@ -140,6 +146,20 @@ static int server_receive_message(sd_event_source *s, int fd,
         if (len < buflen)
                 return 0;
 
+        for (cmsg = CMSG_FIRSTHDR(&msg); cmsg; cmsg = CMSG_NXTHDR(&msg, cmsg)) {
+                if (cmsg->cmsg_level == IPPROTO_IP &&
+                    cmsg->cmsg_type == IP_PKTINFO &&
+                    cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) {
+                        struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg);
+
+                        /* TODO figure out if this can be done as a filter on the socket, like for IPv6 */
+                        if (server->index != info->ipi_ifindex)
+                                return 0;
+
+                        break;
+                }
+        }
+
         log_dhcp_server(server, "received message");
 
         return 1;
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index 80d2184..2feb124 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -32,7 +32,8 @@
 static void test_basic(sd_event *event) {
         _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
 
-        assert_se(sd_dhcp_server_new(&server) >= 0);
+        /* attach to loopback interface */
+        assert_se(sd_dhcp_server_new(&server, 1) >= 0);
         assert_se(server);
 
         assert_se(sd_dhcp_server_attach_event(server, event, 0) >= 0);
diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h
index 47962e5..ab63294 100644
--- a/src/systemd/sd-dhcp-server.h
+++ b/src/systemd/sd-dhcp-server.h
@@ -32,7 +32,7 @@ typedef struct sd_dhcp_server sd_dhcp_server;
 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server);
 sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server);
 
-int sd_dhcp_server_new(sd_dhcp_server **ret);
+int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex);
 
 int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int priority);
 int sd_dhcp_server_detach_event(sd_dhcp_server *client);

commit ff734080aa02cd70b13bc0fdeec4a5886166163a
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 19:27:20 2014 +0200

    sd-dhcp-server: add basic functionality for starting/stopping server
    
    Bind to UDP socket and listen for messages, discarding anything we receive.

diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
index e09b359..8191ef7 100644
--- a/src/libsystemd-network/dhcp-server-internal.h
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -32,6 +32,8 @@ struct sd_dhcp_server {
 
         sd_event *event;
         int event_priority;
+        sd_event_source *receive_message;
+        int fd;
 };
 
 DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
index 885d68d..a9768f8 100644
--- a/src/libsystemd-network/sd-dhcp-server.c
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -20,8 +20,11 @@
   along with systemd; If not, see <http://www.gnu.org/licenses/>.
 ***/
 
+#include <sys/ioctl.h>
+
 #include "sd-dhcp-server.h"
 #include "dhcp-server-internal.h"
+#include "dhcp-internal.h"
 
 sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
         if (server)
@@ -34,6 +37,8 @@ sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
         if (server && REFCNT_DEC(server->n_ref) <= 0) {
                 log_dhcp_server(server, "UNREF");
 
+                sd_dhcp_server_stop(server);
+
                 sd_event_unref(server->event);
                 free(server);
         }
@@ -51,6 +56,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret) {
                 return -ENOMEM;
 
         server->n_ref = REFCNT_INIT;
+        server->fd = -1;
 
         *ret = server;
         server = NULL;
@@ -90,3 +96,86 @@ sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
 
         return server->event;
 }
+
+int sd_dhcp_server_stop(sd_dhcp_server *server) {
+        assert_return(server, -EINVAL);
+
+        server->receive_message =
+                sd_event_source_unref(server->receive_message);
+
+        server->fd = safe_close(server->fd);
+
+        log_dhcp_server(server, "STOPPED");
+
+        return 0;
+}
+
+static int server_receive_message(sd_event_source *s, int fd,
+                                  uint32_t revents, void *userdata) {
+        _cleanup_free_ uint8_t *message = NULL;
+        sd_dhcp_server *server = userdata;
+        struct iovec iov = {};
+        struct msghdr msg = {
+                .msg_iov = &iov,
+                .msg_iovlen = 1,
+        };
+        int buflen = 0, len, r;
+
+        assert(server);
+
+        r = ioctl(fd, FIONREAD, &buflen);
+        if (r < 0)
+                return r;
+        if (buflen < 0)
+                return -EIO;
+
+        message = malloc0(buflen);
+        if (!message)
+                return -ENOMEM;
+
+        iov.iov_base = message;
+        iov.iov_len = buflen;
+
+        len = recvmsg(fd, &msg, 0);
+        if (len < buflen)
+                return 0;
+
+        log_dhcp_server(server, "received message");
+
+        return 1;
+}
+
+int sd_dhcp_server_start(sd_dhcp_server *server) {
+        int r;
+
+        assert_return(server, -EINVAL);
+        assert_return(server->event, -EINVAL);
+        assert_return(!server->receive_message, -EBUSY);
+        assert_return(server->fd == -1, -EBUSY);
+
+        r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER);
+        if (r < 0) {
+                sd_dhcp_server_stop(server);
+                return r;
+        }
+        server->fd = r;
+
+        r = sd_event_add_io(server->event, &server->receive_message,
+                            server->fd, EPOLLIN,
+                            server_receive_message, server);
+        if (r < 0) {
+                sd_dhcp_server_stop(server);
+                return r;
+        }
+
+        r = sd_event_source_set_priority(server->receive_message,
+                                         server->event_priority);
+        if (r < 0) {
+                sd_dhcp_server_stop(server);
+                return r;
+        }
+
+        log_dhcp_server(server, "STARTED");
+
+        return 0;
+}
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
index bd0913d..80d2184 100644
--- a/src/libsystemd-network/test-dhcp-server.c
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -45,6 +45,12 @@ static void test_basic(sd_event *event) {
 
         assert_se(sd_dhcp_server_ref(server) == server);
         assert_se(!sd_dhcp_server_unref(server));
+
+        assert_se(sd_dhcp_server_start(server) >= 0);
+        assert_se(sd_dhcp_server_start(server) == -EBUSY);
+        assert_se(sd_dhcp_server_stop(server) >= 0);
+        assert_se(sd_dhcp_server_stop(server) >= 0);
+        assert_se(sd_dhcp_server_start(server) >= 0);
 }
 
 int main(int argc, char *argv[]) {
diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h
index ab3e707..47962e5 100644
--- a/src/systemd/sd-dhcp-server.h
+++ b/src/systemd/sd-dhcp-server.h
@@ -38,4 +38,7 @@ int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int pri
 int sd_dhcp_server_detach_event(sd_dhcp_server *client);
 sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client);
 
+int sd_dhcp_server_start(sd_dhcp_server *server);
+int sd_dhcp_server_stop(sd_dhcp_server *server);
+
 #endif

commit b44cd8821087f2afebf85fec5b588f5720a9415c
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 18:48:41 2014 +0200

    sd-dhcp-server: add basic functionality for creating/destroying server instance

diff --git a/.gitignore b/.gitignore
index bcebb96..ed29762 100644
--- a/.gitignore
+++ b/.gitignore
@@ -141,6 +141,7 @@
 /test-date
 /test-device-nodes
 /test-dhcp-client
+/test-dhcp-server
 /test-dhcp-option
 /test-resolve
 /test-ellipsize
diff --git a/Makefile.am b/Makefile.am
index 53082cf..2ceee2a 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2553,15 +2553,18 @@ libsystemd_network_la_CFLAGS = \
 libsystemd_network_la_SOURCES = \
 	src/systemd/sd-network.h \
 	src/systemd/sd-dhcp-client.h \
+	src/systemd/sd-dhcp-server.h \
 	src/systemd/sd-dhcp-lease.h \
 	src/systemd/sd-ipv4ll.h \
 	src/network/sd-network.c \
 	src/network/network-util.h \
 	src/libsystemd-network/sd-dhcp-client.c \
+	src/libsystemd-network/sd-dhcp-server.c \
 	src/libsystemd-network/dhcp-network.c \
 	src/libsystemd-network/dhcp-option.c \
 	src/libsystemd-network/dhcp-packet.c \
 	src/libsystemd-network/dhcp-internal.h \
+	src/libsystemd-network/dhcp-server-internal.h \
 	src/libsystemd-network/dhcp-protocol.h \
 	src/libsystemd-network/dhcp-lease-internal.h \
 	src/libsystemd-network/sd-dhcp-lease.c \
@@ -2601,6 +2604,14 @@ test_dhcp_client_LDADD = \
 	libsystemd-internal.la \
 	libsystemd-shared.la
 
+test_dhcp_server_SOURCES = \
+	src/libsystemd-network/test-dhcp-server.c
+
+test_dhcp_server_LDADD = \
+	libsystemd-network.la \
+	libsystemd-internal.la \
+	libsystemd-shared.la
+
 test_ipv4ll_SOURCES = \
 	src/systemd/sd-ipv4ll.h \
 	src/libsystemd-network/ipv4ll-internal.h \
@@ -2615,6 +2626,7 @@ test_ipv4ll_LDADD = \
 tests += \
 	test-dhcp-option \
 	test-dhcp-client \
+	test-dhcp-server \
 	test-ipv4ll
 
 # ------------------------------------------------------------------------------
diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c
index 6fac80e..266bf13 100644
--- a/src/libsystemd-network/dhcp-network.c
+++ b/src/libsystemd-network/dhcp-network.c
@@ -117,6 +117,7 @@ int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) {
         r = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
         if (r < 0)
                 return -errno;
+
         if (address == INADDR_ANY) {
                 int on = 1;
 
diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h
new file mode 100644
index 0000000..e09b359
--- /dev/null
+++ b/src/libsystemd-network/dhcp-server-internal.h
@@ -0,0 +1,40 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright (C) 2014 Tom Gundersen
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-event.h"
+#include "sd-dhcp-server.h"
+
+#include "refcnt.h"
+#include "util.h"
+#include "log.h"
+
+struct sd_dhcp_server {
+        RefCount n_ref;
+
+        sd_event *event;
+        int event_priority;
+};
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_server*, sd_dhcp_server_unref);
+#define _cleanup_dhcp_server_unref_ _cleanup_(sd_dhcp_server_unrefp)
+
+#define log_dhcp_server(client, fmt, ...) log_meta(LOG_DEBUG, __FILE__, __LINE__, __func__, "DHCP SERVER: " fmt, ##__VA_ARGS__)
diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c
new file mode 100644
index 0000000..885d68d
--- /dev/null
+++ b/src/libsystemd-network/sd-dhcp-server.c
@@ -0,0 +1,92 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright (C) 2014 Tom Gundersen
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-dhcp-server.h"
+#include "dhcp-server-internal.h"
+
+sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) {
+        if (server)
+                assert_se(REFCNT_INC(server->n_ref) >= 2);
+
+        return server;
+}
+
+sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) {
+        if (server && REFCNT_DEC(server->n_ref) <= 0) {
+                log_dhcp_server(server, "UNREF");
+
+                sd_event_unref(server->event);
+                free(server);
+        }
+
+        return NULL;
+}
+
+int sd_dhcp_server_new(sd_dhcp_server **ret) {
+        _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
+
+        assert_return(ret, -EINVAL);
+
+        server = new0(sd_dhcp_server, 1);
+        if (!server)
+                return -ENOMEM;
+
+        server->n_ref = REFCNT_INIT;
+
+        *ret = server;
+        server = NULL;
+
+        return 0;
+}
+
+int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int priority) {
+        int r;
+
+        assert_return(server, -EINVAL);
+        assert_return(!server->event, -EBUSY);
+
+        if (event)
+                server->event = sd_event_ref(event);
+        else {
+                r = sd_event_default(&server->event);
+                if (r < 0)
+                        return r;
+        }
+
+        server->event_priority = priority;
+
+        return 0;
+}
+
+int sd_dhcp_server_detach_event(sd_dhcp_server *server) {
+        assert_return(server, -EINVAL);
+
+        server->event = sd_event_unref(server->event);
+
+        return 0;
+}
+
+sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) {
+        assert_return(server, NULL);
+
+        return server->event;
+}
diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c
new file mode 100644
index 0000000..bd0913d
--- /dev/null
+++ b/src/libsystemd-network/test-dhcp-server.c
@@ -0,0 +1,62 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright (C) 2014 Tom Gundersen
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <assert.h>
+#include <errno.h>
+
+#include "sd-event.h"
+#include "event-util.h"
+
+#include "sd-dhcp-server.h"
+#include "dhcp-server-internal.h"
+
+static void test_basic(sd_event *event) {
+        _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL;
+
+        assert_se(sd_dhcp_server_new(&server) >= 0);
+        assert_se(server);
+
+        assert_se(sd_dhcp_server_attach_event(server, event, 0) >= 0);
+        assert_se(sd_dhcp_server_attach_event(server, event, 0) == -EBUSY);
+        assert_se(sd_dhcp_server_get_event(server) == event);
+        assert_se(sd_dhcp_server_detach_event(server) >= 0);
+        assert_se(!sd_dhcp_server_get_event(server));
+        assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0);
+        assert_se(sd_dhcp_server_attach_event(server, NULL, 0) == -EBUSY);
+
+        assert_se(sd_dhcp_server_ref(server) == server);
+        assert_se(!sd_dhcp_server_unref(server));
+}
+
+int main(int argc, char *argv[]) {
+        _cleanup_event_unref_ sd_event *e;
+
+        log_set_max_level(LOG_DEBUG);
+        log_parse_environment();
+        log_open();
+
+        assert_se(sd_event_new(&e) >= 0);
+
+        test_basic(e);
+
+        return 0;
+}
diff --git a/src/systemd/sd-dhcp-server.h b/src/systemd/sd-dhcp-server.h
new file mode 100644
index 0000000..ab3e707
--- /dev/null
+++ b/src/systemd/sd-dhcp-server.h
@@ -0,0 +1,41 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+#ifndef foosddhcpserverhfoo
+#define foosddhcpserverhfoo
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2013 Intel Corporation. All rights reserved.
+  Copyright (C) 2014 Tom Gundersen
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/in.h>
+
+#include "sd-event.h"
+
+typedef struct sd_dhcp_server sd_dhcp_server;
+
+sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server);
+sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server);
+
+int sd_dhcp_server_new(sd_dhcp_server **ret);
+
+int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int priority);
+int sd_dhcp_server_detach_event(sd_dhcp_server *client);
+sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client);
+
+#endif

commit fef0e0f3b2ad24ed825f04fe1fe60742958f3567
Author: Tom Gundersen <teg at jklm.no>
Date:   Sat May 24 15:48:47 2014 +0200

    dhcp-network: allow UDP socket to listen on any address
    
    For this to work nicely we need to use REUSEADDR so that more than one socket
    can be open at the same time. Also, we request the ifindex to be appended
    to incoming messages, so we know whence it came.

diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c
index dd26ef3..6fac80e 100644
--- a/src/libsystemd-network/dhcp-network.c
+++ b/src/libsystemd-network/dhcp-network.c
@@ -68,7 +68,7 @@ int dhcp_network_bind_raw_socket(int index, union sockaddr_union *link,
             .filter = filter
         };
         _cleanup_close_ int s = -1;
-        int r, one = 1;
+        int r, on = 1;
 
         assert(index > 0);
         assert(link);
@@ -77,7 +77,7 @@ int dhcp_network_bind_raw_socket(int index, union sockaddr_union *link,
         if (s < 0)
                 return -errno;
 
-        r = setsockopt (s, SOL_PACKET, PACKET_AUXDATA, &one, sizeof(one));
+        r = setsockopt (s, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on));
         if (r < 0)
                 return -errno;
 
@@ -117,6 +117,17 @@ int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) {
         r = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
         if (r < 0)
                 return -errno;
+        if (address == INADDR_ANY) {
+                int on = 1;
+
+                r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
+                if (r < 0)
+                        return -errno;
+
+                r = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
+                if (r < 0)
+                        return -errno;
+        }
 
         r = bind(s, &src.sa, sizeof(src.in));
         if (r < 0)



More information about the systemd-commits mailing list