+ r = sd_dhcp_lease_get_router(link->dhcp_lease, &gateway);
+ if (r < 0 && r != -ENOENT) {
+ log_warning_link(link, "DHCP error: could not get gateway: %s",
+ strerror(-r));
+ return r;
+ }
+
+ if (r >= 0) {
+ r = route_new_dynamic(&route);
+ if (r < 0) {
+ log_error_link(link, "Could not allocate route: %s",
+ strerror(-r));
+ return r;
+ }
+
+ r = route_new_dynamic(&route_gw);
+ if (r < 0) {
+ log_error_link(link, "Could not allocate route: %s",
+ strerror(-r));
+ return r;
+ }
+
+ /* The dhcp netmask may mask out the gateway. Add an explicit
+ * route for the gw host so that we can route no matter the
+ * netmask or existing kernel route tables. */
+ route_gw->family = AF_INET;
+ route_gw->dst_addr.in = gateway;
+ route_gw->dst_prefixlen = 32;
+ route_gw->scope = RT_SCOPE_LINK;
+ route_gw->metrics = DHCP_ROUTE_METRIC;
+
+ r = route_configure(route_gw, link, &route_handler);
+ if (r < 0) {
+ log_warning_link(link,
+ "could not set host route: %s", strerror(-r));
+ return r;
+ }
+
+ link->route_messages ++;
+
+ route->family = AF_INET;
+ route->in_addr.in = gateway;
+ route->metrics = DHCP_ROUTE_METRIC;
+
+ r = route_configure(route, link, &route_handler);
+ if (r < 0) {
+ log_warning_link(link,
+ "could not set routes: %s", strerror(-r));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->route_messages ++;
+ }
+
+ if (link->network->dhcp_routes)
+ link_set_dhcp_routes(link);
+ }
+
+ if (link->route_messages == 0) {
+ link_enter_configured(link);
+ }
+
+ return 0;
+}
+
+static int route_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_rtnl_message_get_errno(m);
+ if (r < 0 && r != -ESRCH)
+ log_struct_link(LOG_WARNING, link,
+ "MESSAGE=%-*s: could not drop route: %s",
+ IFNAMSIZ,
+ link->ifname, strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+
+ return 1;
+}
+
+static int link_get_address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(rtnl);
+ assert(m);
+ assert(link);
+ assert(link->manager);
+
+ for (; m; m = sd_rtnl_message_next(m)) {
+ r = sd_rtnl_message_get_errno(m);
+ if (r < 0) {
+ log_debug_link(link, "getting address failed: %s", strerror(-r));
+ continue;
+ }
+
+ r = link_rtnl_process_address(rtnl, m, link->manager);
+ if (r < 0)
+ log_warning_link(link, "could not process address: %s", strerror(-r));
+ }
+
+ return 1;
+}
+
+static int address_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(rtnl);
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+ assert(link->addr_messages > 0);
+ assert(IN_SET(link->state, LINK_STATE_SETTING_ADDRESSES,
+ LINK_STATE_FAILED, LINK_STATE_LINGER));
+
+ link->addr_messages --;
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_rtnl_message_get_errno(m);
+ if (r < 0 && r != -EEXIST)
+ log_struct_link(LOG_WARNING, link,
+ "MESSAGE=%-*s: could not set address: %s",
+ IFNAMSIZ,
+ link->ifname, strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+ else if (r >= 0) {
+ /* calling handler directly so take a ref */
+ link_ref(link);
+ link_get_address_handler(rtnl, m, link);
+ }
+
+ if (link->addr_messages == 0) {
+ log_debug_link(link, "addresses set");
+ link_enter_set_routes(link);
+ }
+
+ return 1;
+}
+
+static int link_enter_set_addresses(Link *link) {
+ Address *ad;
+ int r;
+ uint32_t lifetime = CACHE_INFO_INFINITY_LIFE_TIME;
+
+ assert(link);
+ assert(link->network);
+ assert(link->state != _LINK_STATE_INVALID);
+
+ link->state = LINK_STATE_SETTING_ADDRESSES;
+
+ if (!link->network->static_addresses && !link->dhcp_lease &&
+ (!link->ipv4ll || ipv4ll_is_bound(link->ipv4ll) == false))
+ return link_enter_set_routes(link);
+
+ log_debug_link(link, "setting addresses");
+
+ LIST_FOREACH(addresses, ad, link->network->static_addresses) {
+ r = address_configure(ad, link, &address_handler);
+ if (r < 0) {
+ log_warning_link(link,
+ "could not set addresses: %s", strerror(-r));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->addr_messages ++;
+ }
+
+ if (link->ipv4ll && !link->dhcp_lease) {
+ _cleanup_address_free_ Address *ll_addr = NULL;
+ struct in_addr addr;
+
+ r = sd_ipv4ll_get_address(link->ipv4ll, &addr);
+ if (r < 0 && r != -ENOENT) {
+ log_warning_link(link, "IPV4LL error: no address: %s",
+ strerror(-r));
+ return r;
+ }
+
+ if (r != -ENOENT) {
+ r = address_new_dynamic(&ll_addr);
+ if (r < 0) {
+ log_error_link(link, "Could not allocate address: %s", strerror(-r));
+ return r;
+ }
+
+ ll_addr->family = AF_INET;
+ ll_addr->in_addr.in = addr;
+ ll_addr->prefixlen = 16;
+ ll_addr->broadcast.s_addr = ll_addr->in_addr.in.s_addr | htonl(0xfffffffflu >> ll_addr->prefixlen);
+ ll_addr->scope = RT_SCOPE_LINK;
+
+ r = address_configure(ll_addr, link, &address_handler);
+ if (r < 0) {
+ log_warning_link(link,
+ "could not set addresses: %s", strerror(-r));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->addr_messages ++;
+ }
+ }
+
+ if (link->dhcp_lease) {
+ _cleanup_address_free_ Address *address = NULL;
+ struct in_addr addr;
+ struct in_addr netmask;
+ unsigned prefixlen;
+
+ r = sd_dhcp_lease_get_address(link->dhcp_lease, &addr);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no address: %s",
+ strerror(-r));
+ return r;
+ }
+
+ if (!link->network->dhcp_critical) {
+ r = sd_dhcp_lease_get_lifetime(link->dhcp_lease,
+ &lifetime);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no lifetime: %s",
+ strerror(-r));
+ return r;
+ }
+ }
+
+ r = sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no netmask: %s",
+ strerror(-r));
+ return r;
+ }
+
+ prefixlen = net_netmask_to_prefixlen(&netmask);
+
+ r = address_new_dynamic(&address);
+ if (r < 0) {
+ log_error_link(link, "Could not allocate address: %s",
+ strerror(-r));
+ return r;
+ }
+
+ address->family = AF_INET;
+ address->in_addr.in = addr;
+ address->cinfo.ifa_prefered = lifetime;
+ address->cinfo.ifa_valid = lifetime;
+ address->prefixlen = prefixlen;
+ address->broadcast.s_addr = addr.s_addr | ~netmask.s_addr;
+
+ /* use update rather than configure so that we will update the lifetime
+ of an existing address if it has already been configured */
+ r = address_update(address, link, &address_handler);
+ if (r < 0) {
+ log_warning_link(link,
+ "could not set addresses: %s", strerror(-r));
+ link_enter_failed(link);
+ return r;
+ }
+
+ link->addr_messages ++;
+ }
+
+ return 0;
+}
+
+static int address_update_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_rtnl_message_get_errno(m);
+ if (r < 0 && r != -ENOENT)
+ log_struct_link(LOG_WARNING, link,
+ "MESSAGE=%-*s: could not update address: %s",
+ IFNAMSIZ,
+ link->ifname, strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+
+ return 1;
+}
+
+static int address_drop_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_rtnl_message_get_errno(m);
+ if (r < 0 && r != -EADDRNOTAVAIL)
+ log_struct_link(LOG_WARNING, link,
+ "MESSAGE=%-*s: could not drop address: %s",
+ IFNAMSIZ,
+ link->ifname, strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+
+ return 1;
+}
+
+static int set_hostname_handler(sd_bus *bus, sd_bus_message *m, void *userdata, sd_bus_error *ret_error) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(link);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_bus_message_get_errno(m);
+ if (r < 0)
+ log_warning_link(link, "Could not set hostname: %s", strerror(-r));
+
+ return 1;
+}
+
+static int link_set_hostname(Link *link, const char *hostname) {
+ _cleanup_bus_message_unref_ sd_bus_message *m = NULL;
+ int r = 0;
+
+ assert(link);
+ assert(link->manager);
+ assert(hostname);
+
+ log_debug_link(link, "Setting transient hostname: '%s'", hostname);
+
+ if (!link->manager->bus) { /* TODO: replace by assert when we can rely on kdbus */
+ log_info_link(link, "Not connected to system bus, ignoring transient hostname.");
+ return 0;
+ }
+
+ r = sd_bus_message_new_method_call(
+ link->manager->bus,
+ &m,
+ "org.freedesktop.hostname1",
+ "/org/freedesktop/hostname1",
+ "org.freedesktop.hostname1",
+ "SetHostname");
+ if (r < 0)
+ return r;
+
+ r = sd_bus_message_append(m, "sb", hostname, false);
+ if (r < 0)
+ return r;
+
+ r = sd_bus_call_async(link->manager->bus, NULL, m, set_hostname_handler, link, 0);
+ if (r < 0) {
+ log_error_link(link, "Could not set transient hostname: %s", strerror(-r));
+ return r;
+ }
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int set_mtu_handler(sd_rtnl *rtnl, sd_rtnl_message *m, void *userdata) {
+ _cleanup_link_unref_ Link *link = userdata;
+ int r;
+
+ assert(m);
+ assert(link);
+ assert(link->ifname);
+
+ if (IN_SET(link->state, LINK_STATE_FAILED, LINK_STATE_LINGER))
+ return 1;
+
+ r = sd_rtnl_message_get_errno(m);
+ if (r < 0)
+ log_struct_link(LOG_WARNING, link,
+ "MESSAGE=%-*s: could not set MTU: %s",
+ IFNAMSIZ, link->ifname, strerror(-r),
+ "ERRNO=%d", -r,
+ NULL);
+
+ return 1;
+}
+
+static int link_set_mtu(Link *link, uint32_t mtu) {
+ _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL;
+ int r;
+
+ assert(link);
+ assert(link->manager);
+ assert(link->manager->rtnl);
+
+ log_debug_link(link, "setting MTU: %" PRIu32, mtu);
+
+ r = sd_rtnl_message_new_link(link->manager->rtnl, &req,
+ RTM_SETLINK, link->ifindex);
+ if (r < 0) {
+ log_error_link(link, "Could not allocate RTM_SETLINK message");
+ return r;
+ }
+
+ r = sd_rtnl_message_append_u32(req, IFLA_MTU, mtu);
+ if (r < 0) {
+ log_error_link(link, "Could not append MTU: %s", strerror(-r));
+ return r;
+ }
+
+ r = sd_rtnl_call_async(link->manager->rtnl, req, set_mtu_handler, link, 0, NULL);
+ if (r < 0) {
+ log_error_link(link,
+ "Could not send rtnetlink message: %s", strerror(-r));
+ return r;
+ }
+
+ link_ref(link);
+
+ return 0;
+}
+
+static int dhcp_lease_lost(Link *link) {
+ _cleanup_address_free_ Address *address = NULL;
+ struct in_addr addr;
+ struct in_addr netmask;
+ struct in_addr gateway;
+ unsigned prefixlen;
+ int r;
+
+ assert(link);
+ assert(link->dhcp_lease);
+
+ log_warning_link(link, "DHCP lease lost");
+
+ if (link->network->dhcp_routes) {
+ struct sd_dhcp_route *routes;
+ int n, i;
+
+ n = sd_dhcp_lease_get_routes(link->dhcp_lease, &routes);
+ if (n >= 0) {
+ for (i = 0; i < n; i++) {
+ _cleanup_route_free_ Route *route = NULL;
+
+ r = route_new_dynamic(&route);
+ if (r >= 0) {
+ route->family = AF_INET;
+ route->in_addr.in = routes[i].gw_addr;
+ route->dst_addr.in = routes[i].dst_addr;
+ route->dst_prefixlen = routes[i].dst_prefixlen;
+
+ route_drop(route, link, &route_drop_handler);
+ }
+ }
+ }
+ }
+
+ r = address_new_dynamic(&address);
+ if (r >= 0) {
+ r = sd_dhcp_lease_get_router(link->dhcp_lease, &gateway);
+ if (r >= 0) {
+ _cleanup_route_free_ Route *route_gw = NULL;
+ _cleanup_route_free_ Route *route = NULL;
+
+ r = route_new_dynamic(&route_gw);
+ if (r >= 0) {
+ route_gw->family = AF_INET;
+ route_gw->dst_addr.in = gateway;
+ route_gw->dst_prefixlen = 32;
+ route_gw->scope = RT_SCOPE_LINK;
+
+ route_drop(route_gw, link, &route_drop_handler);
+ }
+
+ r = route_new_dynamic(&route);
+ if (r >= 0) {
+ route->family = AF_INET;
+ route->in_addr.in = gateway;
+
+ route_drop(route, link, &route_drop_handler);
+ }
+ }
+
+ sd_dhcp_lease_get_address(link->dhcp_lease, &addr);
+ sd_dhcp_lease_get_netmask(link->dhcp_lease, &netmask);
+ prefixlen = net_netmask_to_prefixlen(&netmask);
+
+ address->family = AF_INET;
+ address->in_addr.in = addr;
+ address->prefixlen = prefixlen;
+
+ address_drop(address, link, &address_drop_handler);
+ }
+
+ if (link->network->dhcp_mtu) {
+ uint16_t mtu;
+
+ r = sd_dhcp_lease_get_mtu(link->dhcp_lease, &mtu);
+ if (r >= 0 && link->original_mtu != mtu) {
+ r = link_set_mtu(link, link->original_mtu);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: could not reset MTU");
+ link_enter_failed(link);
+ return r;
+ }
+ }
+ }
+
+ if (link->network->dhcp_hostname) {
+ const char *hostname = NULL;
+
+ r = sd_dhcp_lease_get_hostname(link->dhcp_lease, &hostname);
+ if (r >= 0 && hostname) {
+ r = link_set_hostname(link, "");
+ if (r < 0)
+ log_error_link(link, "Failed to reset transient hostname");
+ }
+ }
+
+ link->dhcp_lease = sd_dhcp_lease_unref(link->dhcp_lease);
+
+ return 0;
+}
+
+static int dhcp_lease_renew(sd_dhcp_client *client, Link *link) {
+ sd_dhcp_lease *lease;
+ int r;
+
+ r = sd_dhcp_client_get_lease(client, &lease);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no lease %s",
+ strerror(-r));
+ return r;
+ }
+
+ sd_dhcp_lease_unref(link->dhcp_lease);
+ link->dhcp_lease = lease;
+
+ link_enter_set_addresses(link);
+
+ return 0;
+}
+
+static int dhcp_lease_acquired(sd_dhcp_client *client, Link *link) {
+ sd_dhcp_lease *lease;
+ struct in_addr address;
+ struct in_addr netmask;
+ struct in_addr gateway;
+ unsigned prefixlen;
+ int r;
+
+ assert(client);
+ assert(link);
+
+ r = sd_dhcp_client_get_lease(client, &lease);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no lease: %s",
+ strerror(-r));
+ return r;
+ }
+
+ r = sd_dhcp_lease_get_address(lease, &address);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no address: %s",
+ strerror(-r));
+ return r;
+ }
+
+ r = sd_dhcp_lease_get_netmask(lease, &netmask);
+ if (r < 0) {
+ log_warning_link(link, "DHCP error: no netmask: %s",
+ strerror(-r));
+ return r;
+ }
+
+ prefixlen = net_netmask_to_prefixlen(&netmask);
+
+ r = sd_dhcp_lease_get_router(lease, &gateway);
+ if (r < 0 && r != -ENOENT) {
+ log_warning_link(link, "DHCP error: could not get gateway: %s",
+ strerror(-r));