From: Tom Gundersen Date: Wed, 1 Jan 2014 14:16:34 +0000 (+0100) Subject: networkd: add DHCPv4 support X-Git-Tag: v209~582 X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=commitdiff_plain;h=f5be560181d092c5f52a2b819aedcd48220f36ab networkd: add DHCPv4 support This adds basic DHCPv4 support. Link-sense is enabled unconditionally, but the plan is to make that configurable. I tested this in a VM with lots of NICs and over wifi in the various coffee shops I found this Christmas, but more testing would definitely be appreciated. --- diff --git a/Makefile.am b/Makefile.am index 5f31daa07..44b69171a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4056,6 +4056,7 @@ systemd_networkd_LDADD = \ libsystemd-bus-internal.la \ libsystemd-id128-internal.la \ libsystemd-rtnl.la \ + libsystemd-dhcp.la \ libsystemd-shared.la nodist_systemunit_DATA += \ @@ -4081,6 +4082,7 @@ test_network_LDADD = \ libsystemd-id128-internal.la \ libsystemd-daemon-internal.la \ libsystemd-rtnl.la \ + libsystemd-dhcp.la \ libsystemd-shared.la tests += \ diff --git a/src/network/networkd-gperf.gperf b/src/network/networkd-gperf.gperf index f710df6bf..f1594d56c 100644 --- a/src/network/networkd-gperf.gperf +++ b/src/network/networkd-gperf.gperf @@ -22,6 +22,7 @@ Match.Type, config_parse_string, 0, offsetof(Networ Match.Name, config_parse_ifname, 0, offsetof(Network, match_name) Network.Description, config_parse_string, 0, offsetof(Network, description) Network.Bridge, config_parse_bridge, 0, offsetof(Network, bridge) +Network.DHCP, config_parse_bool, 0, offsetof(Network, dhcp) Network.Address, config_parse_address, 0, 0 Network.Gateway, config_parse_gateway, 0, 0 Address.Address, config_parse_address, 0, 0 diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index ea94966f1..3d01625d7 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -73,6 +73,15 @@ void link_free(Link *link) { assert(link->manager); + if (link->dhcp) + sd_dhcp_client_free(link->dhcp); + + route_free(link->dhcp_route); + link->dhcp_route = NULL; + + address_free(link->dhcp_address); + link->dhcp_address = NULL; + hashmap_remove(link->manager->links, &link->ifindex); free(link->ifname); @@ -140,10 +149,12 @@ static int route_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { Link *link = userdata; int r; - assert(link->rtnl_messages > 0); - assert(link->state == LINK_STATE_SETTING_ROUTES || link->state == LINK_STATE_FAILED); + assert(link->route_messages > 0); + assert(link->state == LINK_STATE_SETTING_ADDRESSES || + link->state == LINK_STATE_SETTING_ROUTES || + link->state == LINK_STATE_FAILED); - link->rtnl_messages --; + link->route_messages --; if (link->state == LINK_STATE_FAILED) return 1; @@ -153,7 +164,9 @@ static int route_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { log_warning("Could not set route on interface '%s': %s", link->ifname, strerror(-r)); - if (link->rtnl_messages == 0) { + /* we might have received an old reply after moving back to SETTING_ADDRESSES, + * ignore it */ + if (link->route_messages == 0 && link->state == LINK_STATE_SETTING_ROUTES) { log_info("Routes set for link '%s'", link->ifname); link_enter_configured(link); } @@ -167,12 +180,11 @@ static int link_enter_set_routes(Link *link) { assert(link); assert(link->network); - assert(link->rtnl_messages == 0); assert(link->state == LINK_STATE_SETTING_ADDRESSES); link->state = LINK_STATE_SETTING_ROUTES; - if (!link->network->static_routes) + if (!link->network->static_routes && !link->dhcp_route) return link_enter_configured(link); LIST_FOREACH(static_routes, route, link->network->static_routes) { @@ -183,7 +195,18 @@ static int link_enter_set_routes(Link *link) { return r; } - link->rtnl_messages ++; + link->route_messages ++; + } + + if (link->dhcp_route) { + r = route_configure(link->dhcp_route, link, &route_handler); + if (r < 0) { + log_warning("Could not set routes for link '%s'", link->ifname); + link_enter_failed(link); + return r; + } + + link->route_messages ++; } return 0; @@ -193,10 +216,13 @@ static int address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { Link *link = userdata; int r; - assert(link->rtnl_messages > 0); + assert(m); + assert(link); + assert(link->ifname); + assert(link->addr_messages > 0); assert(link->state == LINK_STATE_SETTING_ADDRESSES || link->state == LINK_STATE_FAILED); - link->rtnl_messages --; + link->addr_messages --; if (link->state == LINK_STATE_FAILED) return 1; @@ -206,7 +232,7 @@ static int address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { log_warning("Could not set address on interface '%s': %s", link->ifname, strerror(-r)); - if (link->rtnl_messages == 0) { + if (link->addr_messages == 0) { log_info("Addresses set for link '%s'", link->ifname); link_enter_set_routes(link); } @@ -220,12 +246,11 @@ static int link_enter_set_addresses(Link *link) { assert(link); assert(link->network); - assert(link->state == LINK_STATE_JOINING_BRIDGE); - assert(link->rtnl_messages == 0); + assert(link->state != _LINK_STATE_INVALID); link->state = LINK_STATE_SETTING_ADDRESSES; - if (!link->network->static_addresses) + if (!link->network->static_addresses && !link->dhcp_address) return link_enter_set_routes(link); LIST_FOREACH(static_addresses, address, link->network->static_addresses) { @@ -236,7 +261,18 @@ static int link_enter_set_addresses(Link *link) { return r; } - link->rtnl_messages ++; + link->addr_messages ++; + } + + if (link->dhcp_address) { + r = address_configure(link->dhcp_address, link, &address_handler); + if (r < 0) { + log_warning("Could not set addresses for link '%s'", link->ifname); + link_enter_failed(link); + return r; + } + + link->addr_messages ++; } return 0; @@ -290,6 +326,7 @@ static int link_bridge_joined(Link *link) { assert(link); assert(link->state == LINK_STATE_JOINING_BRIDGE); + assert(link->network); r = link_up(link); if (r < 0) { @@ -297,10 +334,11 @@ static int link_bridge_joined(Link *link) { return r; } - r = link_enter_set_addresses(link); - if (r < 0) { - link_enter_failed(link); - return r; + if (!link->network->dhcp) { + r = link_enter_set_addresses(link); + if (r < 0) + link_enter_failed(link); + return r; } return 0; @@ -411,6 +449,157 @@ int link_configure(Link *link) { return 0; } +static int address_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) { + Link *link = userdata; + int r; + + assert(m); + assert(link); + assert(link->ifname); + + if (link->state == LINK_STATE_FAILED) + return 1; + + r = sd_rtnl_message_get_errno(m); + if (r < 0 && r != -EEXIST) + log_warning("Could not drop address from interface '%s': %s", + link->ifname, strerror(-r)); + + return 1; +} + +static void dhcp_handler(sd_dhcp_client *client, int event, void *userdata) { + Link *link = userdata; + struct in_addr address; + struct in_addr netmask; + struct in_addr gateway; + int prefixlen; + int r; + + if (link->state == LINK_STATE_FAILED) + return; + + if (event < 0) { + log_warning("DHCP error: %s", strerror(-event)); + link_enter_failed(link); + return; + } + + if (event == DHCP_EVENT_NO_LEASE) + log_info("IP address in use."); + + if (event == DHCP_EVENT_IP_CHANGE || event == DHCP_EVENT_EXPIRED || + event == DHCP_EVENT_STOP) { + address_drop(link->dhcp_address, link, address_drop_handler); + + address_free(link->dhcp_address); + link->dhcp_address = NULL; + + route_free(link->dhcp_route); + link->dhcp_route = NULL; + } + + r = sd_dhcp_client_get_address(client, &address); + if (r < 0) { + log_warning("DHCP error: no address"); + link_enter_failed(link); + return; + } + + r = sd_dhcp_client_get_netmask(client, &netmask); + if (r < 0) { + log_warning("DHCP error: no netmask"); + link_enter_failed(link); + return; + } + + prefixlen = sd_dhcp_client_prefixlen(&netmask); + if (prefixlen < 0) { + log_warning("DHCP error: no prefixlen"); + link_enter_failed(link); + return; + } + + r = sd_dhcp_client_get_router(client, &gateway); + if (r < 0) { + log_warning("DHCP error: no router"); + link_enter_failed(link); + return; + } + + if (event == DHCP_EVENT_IP_CHANGE || event == DHCP_EVENT_IP_ACQUIRE) { + _cleanup_address_free_ Address *addr = NULL; + _cleanup_route_free_ Route *rt = NULL; + + log_info("Received config over DHCPv4"); + + r = address_new_dynamic(&addr); + if (r < 0) { + log_error("Could not allocate address"); + link_enter_failed(link); + return; + } + + addr->family = AF_INET; + addr->in_addr.in = address; + addr->prefixlen = prefixlen; + addr->netmask = netmask; + + r = route_new_dynamic(&rt); + if (r < 0) { + log_error("Could not allocate route"); + link_enter_failed(link); + return; + } + + rt->family = AF_INET; + rt->in_addr.in = gateway; + + link->dhcp_address = addr; + link->dhcp_route = rt; + addr = NULL; + rt = NULL; + + link_enter_set_addresses(link); + } + + return; +} + +static int link_acquire_conf(Link *link) { + int r; + + assert(link); + assert(link->network); + assert(link->network->dhcp); + assert(link->manager); + assert(link->manager->event); + + if (!link->dhcp) { + link->dhcp = sd_dhcp_client_new(link->manager->event); + if (!link->dhcp) + return -ENOMEM; + + r = sd_dhcp_client_set_index(link->dhcp, link->ifindex); + if (r < 0) + return r; + + r = sd_dhcp_client_set_mac(link->dhcp, &link->mac); + if (r < 0) + return r; + + r = sd_dhcp_client_set_callback(link->dhcp, dhcp_handler, link); + if (r < 0) + return r; + } + + r = sd_dhcp_client_start(link->dhcp); + if (r < 0) + return r; + + return 0; +} + int link_update(Link *link, sd_rtnl_message *m) { unsigned flags; int r; @@ -429,11 +618,28 @@ int link_update(Link *link, sd_rtnl_message *m) { else if (!(link->flags & IFF_UP) && flags & IFF_UP) log_info("Interface '%s' is up", link->ifname); - if (link->flags & IFF_LOWER_UP && !(flags & IFF_LOWER_UP)) + if (link->flags & IFF_LOWER_UP && !(flags & IFF_LOWER_UP)) { log_info("Interface '%s' is disconnected", link->ifname); - else if (!(link->flags & IFF_LOWER_UP) && flags & IFF_LOWER_UP) + + if (link->network->dhcp) { + r = sd_dhcp_client_stop(link->dhcp); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + } else if (!(link->flags & IFF_LOWER_UP) && flags & IFF_LOWER_UP) { log_info("Interface '%s' is connected", link->ifname); + if (link->network->dhcp) { + r = link_acquire_conf(link); + if (r < 0) { + link_enter_failed(link); + return r; + } + } + } + link->flags = flags; return 0; diff --git a/src/network/networkd.h b/src/network/networkd.h index 547533fba..cad81d9b7 100644 --- a/src/network/networkd.h +++ b/src/network/networkd.h @@ -26,6 +26,7 @@ #include "sd-event.h" #include "sd-rtnl.h" +#include "sd-dhcp-client.h" #include "udev.h" #include "rtnl-util.h" @@ -84,6 +85,7 @@ struct Network { char *description; Bridge *bridge; + bool dhcp; LIST_HEAD(Address, static_addresses); LIST_HEAD(Route, static_routes); @@ -153,9 +155,15 @@ struct Link { Network *network; + Route *dhcp_route; + Address *dhcp_address; + LinkState state; - unsigned rtnl_messages; + unsigned addr_messages; + unsigned route_messages; + + sd_dhcp_client *dhcp; }; struct Manager {