diff --git a/man/systemd.network.xml b/man/systemd.network.xml index 8b0f7363df..bdffcf5518 100644 --- a/man/systemd.network.xml +++ b/man/systemd.network.xml @@ -997,6 +997,27 @@ Defaults to false. + + Anonymize= + + Takes a boolean argument. When true, the options sent to the DHCP server will + follow the RFC 7844 + (Anonymity Profiles for DHCP Clients) to minimize disclosure of identifying information. + Defaults to false. + + This option should only be set to true when + MACAddressPolicy= is set to random + (see systemd.link5). + + Note that this configuration will overwrite others. + In concrete, the following variables will be ignored: + SendHostname=, ClientIdentifier=, + UseRoutes=, SendHostname=, + UseMTU=, VendorClassIdentifier=, + UseTimezone=. + + SendHostname= diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index e20d339bd5..8426f602a9 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -62,6 +62,7 @@ struct sd_dhcp_client { uint8_t *req_opts; size_t req_opts_allocated; size_t req_opts_size; + bool anonymize; be32_t last_addr; uint8_t mac_addr[MAX_MAC_ADDR_LEN]; size_t mac_addr_len; @@ -116,6 +117,32 @@ static const uint8_t default_req_opts[] = { SD_DHCP_OPTION_DOMAIN_NAME_SERVER, }; +/* RFC7844 section 3: + MAY contain the Parameter Request List option. + RFC7844 section 3.6: + The client intending to protect its privacy SHOULD only request a + minimal number of options in the PRL and SHOULD also randomly shuffle + the ordering of option codes in the PRL. If this random ordering + cannot be implemented, the client MAY order the option codes in the + PRL by option code number (lowest to highest). +*/ +/* NOTE: using PRL options that Windows 10 RFC7844 implementation uses */ +static const uint8_t default_req_opts_anonymize[] = { + SD_DHCP_OPTION_SUBNET_MASK, /* 1 */ + SD_DHCP_OPTION_ROUTER, /* 3 */ + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, /* 6 */ + SD_DHCP_OPTION_DOMAIN_NAME, /* 15 */ + SD_DHCP_OPTION_ROUTER_DISCOVER, /* 31 */ + SD_DHCP_OPTION_STATIC_ROUTE, /* 33 */ + SD_DHCP_OPTION_VENDOR_SPECIFIC, /* 43 */ + SD_DHCP_OPTION_NETBIOS_NAMESERVER, /* 44 */ + SD_DHCP_OPTION_NETBIOS_NODETYPE, /* 46 */ + SD_DHCP_OPTION_NETBIOS_SCOPE, /* 47 */ + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE, /* 121 */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE, /* 249 */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY, /* 252 */ +}; + static int client_receive_message_raw( sd_event_source *s, int fd, @@ -588,11 +615,18 @@ static int client_message_init( it MUST include that list in any subsequent DHCPREQUEST messages. */ - r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, - client->req_opts_size, client->req_opts); - if (r < 0) - return r; + + /* RFC7844 section 3: + MAY contain the Parameter Request List option. */ + /* NOTE: in case that there would be an option to do not send + * any PRL at all, the size should be checked before sending */ + if (client->req_opts_size > 0) { + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + client->req_opts_size, client->req_opts); + if (r < 0) + return r; + } /* RFC2131 section 3.5: The client SHOULD include the ’maximum DHCP message size’ option to @@ -616,12 +650,16 @@ static int client_message_init( Maximum DHCP Message Size option is the total maximum packet size, including IP and UDP headers.) */ - max_size = htobe16(size); - r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, - SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, - 2, &max_size); - if (r < 0) - return r; + /* RFC7844 section 3: + SHOULD NOT contain any other option. */ + if (!client->anonymize) { + max_size = htobe16(size); + r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + 2, &max_size); + if (r < 0) + return r; + } *_optlen = optlen; *_optoffset = optoffset; @@ -1783,7 +1821,14 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { if (r < 0) return r; - if (client->last_addr) + /* RFC7844 section 3.3: + SHOULD perform a complete four-way handshake, starting with a + DHCPDISCOVER, to obtain a new address lease. If the client can + ascertain that this is exactly the same network to which it was + previously connected, and if the link-layer address did not change, + the client MAY issue a DHCPREQUEST to try to reclaim the current + address. */ + if (client->last_addr && !client->anonymize) client->state = DHCP_STATE_INIT_REBOOT; r = client_start(client); @@ -1875,7 +1920,7 @@ sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) { return mfree(client); } -int sd_dhcp_client_new(sd_dhcp_client **ret) { +int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize) { _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; assert_return(ret, -EINVAL); @@ -1892,8 +1937,15 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) { client->mtu = DHCP_DEFAULT_MIN_SIZE; client->port = DHCP_PORT_CLIENT; - client->req_opts_size = ELEMENTSOF(default_req_opts); - client->req_opts = memdup(default_req_opts, client->req_opts_size); + client->anonymize = !!anonymize; + /* NOTE: this could be moved to a function. */ + if (anonymize) { + client->req_opts_size = ELEMENTSOF(default_req_opts_anonymize); + client->req_opts = memdup(default_req_opts_anonymize, client->req_opts_size); + } else { + client->req_opts_size = ELEMENTSOF(default_req_opts); + client->req_opts = memdup(default_req_opts, client->req_opts_size); + } if (!client->req_opts) return -ENOMEM; diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index f5f1284e6d..5c3ae170ed 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -56,7 +56,8 @@ static void test_request_basic(sd_event *e) { if (verbose) printf("* %s\n", __FUNCTION__); - r = sd_dhcp_client_new(&client); + /* Initialize client without Anonymize settings. */ + r = sd_dhcp_client_new(&client, false); assert_se(r >= 0); assert_se(client); @@ -78,6 +79,8 @@ static void test_request_basic(sd_event *e) { SD_DHCP_OPTION_SUBNET_MASK) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_ROUTER) == -EEXIST); + /* This PRL option is not set when using Anonymize, but in this test + * Anonymize settings are not being used. */ assert_se(sd_dhcp_client_set_request_option(client, SD_DHCP_OPTION_HOST_NAME) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, @@ -97,10 +100,49 @@ static void test_request_basic(sd_event *e) { SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); - assert_se(sd_dhcp_client_set_request_option(client, 33) == 0); - assert_se(sd_dhcp_client_set_request_option(client, 33) == -EEXIST); - assert_se(sd_dhcp_client_set_request_option(client, 44) == 0); - assert_se(sd_dhcp_client_set_request_option(client, 33) == -EEXIST); + /* RFC7844: option 33 (SD_DHCP_OPTION_STATIC_ROUTE) is set in the + * default PRL when using Anonymize, so it is changed to other option + * that is not set by default, to check that it succed setting it. + * Ooptions not set by default (using or not anonymize) are option 17 + * (SD_DHCP_OPTION_ROOT_PATH) and 42 (SD_DHCP_OPTION_NTP_SERVER) */ + assert_se(sd_dhcp_client_set_request_option(client, 17) == 0); + assert_se(sd_dhcp_client_set_request_option(client, 17) == -EEXIST); + assert_se(sd_dhcp_client_set_request_option(client, 42) == 0); + assert_se(sd_dhcp_client_set_request_option(client, 17) == -EEXIST); + + sd_dhcp_client_unref(client); +} + +static void test_request_anonymize(sd_event *e) { + int r; + + sd_dhcp_client *client; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + /* Initialize client with Anonymize settings. */ + r = sd_dhcp_client_new(&client, true); + + assert_se(r >= 0); + assert_se(client); + + r = sd_dhcp_client_attach_event(client, e, 0); + assert_se(r >= 0); + + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_NETBIOS_NAMESERVER) == -EEXIST); + /* This PRL option is not set when using Anonymize */ + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_HOST_NAME) == 0); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) + == -EINVAL); + + /* RFC7844: option 101 (SD_DHCP_OPTION_NEW_TZDB_TIMEZONE) is not set in the + * default PRL when using Anonymize, */ + assert_se(sd_dhcp_client_set_request_option(client, 101) == 0); + assert_se(sd_dhcp_client_set_request_option(client, 101) == -EEXIST); sd_dhcp_client_unref(client); } @@ -236,7 +278,7 @@ static void test_discover_message(sd_event *e) { if (verbose) printf("* %s\n", __FUNCTION__); - r = sd_dhcp_client_new(&client); + r = sd_dhcp_client_new(&client, false); assert_se(r >= 0); assert_se(client); @@ -451,7 +493,7 @@ static void test_addr_acq(sd_event *e) { if (verbose) printf("* %s\n", __FUNCTION__); - r = sd_dhcp_client_new(&client); + r = sd_dhcp_client_new(&client, false); assert_se(r >= 0); assert_se(client); @@ -497,6 +539,7 @@ int main(int argc, char *argv[]) { assert_se(sd_event_new(&e) >= 0); test_request_basic(e); + test_request_anonymize(e); test_checksum(); test_discover_message(e); diff --git a/src/network/networkd-dhcp4.c b/src/network/networkd-dhcp4.c index 546c90b6bb..9c69979c7b 100644 --- a/src/network/networkd-dhcp4.c +++ b/src/network/networkd-dhcp4.c @@ -583,7 +583,7 @@ int dhcp4_configure(Link *link) { assert(link->network->dhcp & ADDRESS_FAMILY_IPV4); if (!link->dhcp_client) { - r = sd_dhcp_client_new(&link->dhcp_client); + r = sd_dhcp_client_new(&link->dhcp_client, link->network->dhcp_anonymize); if (r < 0) return r; } @@ -624,7 +624,12 @@ int dhcp4_configure(Link *link) { return r; } - if (link->network->dhcp_use_routes) { + /* NOTE: when using Anonymity Profiles, routes PRL options are sent + * by default, so they should not be added again here. */ + /* NOTE: even if this variable is called "use", it also "sends" PRL + * options, maybe there should be a different configuration variable + * to send or not route options?. */ + if (link->network->dhcp_use_routes && !link->network->dhcp_anonymize) { r = sd_dhcp_client_set_request_option(link->dhcp_client, SD_DHCP_OPTION_STATIC_ROUTE); if (r < 0) diff --git a/src/network/networkd-link.c b/src/network/networkd-link.c index 2ad10d2743..2fb1dd67ab 100644 --- a/src/network/networkd-link.c +++ b/src/network/networkd-link.c @@ -2916,7 +2916,7 @@ network_file_fail: goto dhcp4_address_fail; } - r = sd_dhcp_client_new(&link->dhcp_client); + r = sd_dhcp_client_new(&link->dhcp_client, link->network->dhcp_anonymize); if (r < 0) return log_link_error_errno(link, r, "Failed to create DHCPv4 client: %m"); diff --git a/src/network/networkd-network-gperf.gperf b/src/network/networkd-network-gperf.gperf index 54385b59d7..7dab421392 100644 --- a/src/network/networkd-network-gperf.gperf +++ b/src/network/networkd-network-gperf.gperf @@ -101,6 +101,7 @@ DHCP.UseMTU, config_parse_bool, DHCP.UseHostname, config_parse_bool, 0, offsetof(Network, dhcp_use_hostname) DHCP.UseDomains, config_parse_dhcp_use_domains, 0, offsetof(Network, dhcp_use_domains) DHCP.UseRoutes, config_parse_bool, 0, offsetof(Network, dhcp_use_routes) +DHCP.Anonymize, config_parse_bool, 0, offsetof(Network, dhcp_anonymize) DHCP.SendHostname, config_parse_bool, 0, offsetof(Network, dhcp_send_hostname) DHCP.Hostname, config_parse_hostname, 0, offsetof(Network, dhcp_hostname) DHCP.RequestBroadcast, config_parse_bool, 0, offsetof(Network, dhcp_broadcast) diff --git a/src/network/networkd-network.c b/src/network/networkd-network.c index 77acee10f0..4a997b76d7 100644 --- a/src/network/networkd-network.c +++ b/src/network/networkd-network.c @@ -79,6 +79,46 @@ void network_config_section_free(NetworkConfigSection *cs) { free(cs); } +/* Set defaults following RFC7844 */ +void network_apply_anonymize_if_set(Network *network) { + if (!network->dhcp_anonymize) + return; + /* RFC7844 3.7 + SHOULD NOT send the Host Name option */ + network->dhcp_send_hostname = false; + /* RFC7844 section 3.: + MAY contain the Client Identifier option + Section 3.5: + clients MUST use client identifiers based solely + on the link-layer address */ + /* NOTE: Using MAC, as it does not reveal extra information, + * and some servers might not answer if this option is not sent */ + network->dhcp_client_identifier = DHCP_CLIENT_ID_MAC; + /* RFC 7844 3.10: + SHOULD NOT use the Vendor Class Identifier option */ + /* NOTE: it was not initiallized to any value in network_load_one. */ + network->dhcp_vendor_class_identifier = false; + /* RFC7844 section 3.6.: + The client intending to protect its privacy SHOULD only request a + minimal number of options in the PRL and SHOULD also randomly shuffle + the ordering of option codes in the PRL. If this random ordering + cannot be implemented, the client MAY order the option codes in the + PRL by option code number (lowest to highest). + */ + /* NOTE: dhcp_use_mtu is false by default, + * though it was not initiallized to any value in network_load_one. + * Maybe there should be another var called *send*? + * (to use the MTU sent by the server but to do not send + * the option in the PRL). */ + network->dhcp_use_mtu = false; + /* RFC7844 section 3.6. + * same comments as previous option */ + network->dhcp_use_routes = false; + /* RFC7844 section 3.6. + * same comments as previous option */ + network->dhcp_use_timezone = false; +} + static int network_load_one(Manager *manager, const char *filename) { _cleanup_network_free_ Network *network = NULL; _cleanup_fclose_ FILE *file = NULL; @@ -161,11 +201,24 @@ static int network_load_one(Manager *manager, const char *filename) { network->dhcp_use_ntp = true; network->dhcp_use_dns = true; network->dhcp_use_hostname = true; + /* NOTE: this var might be overwriten by network_apply_anonymize_if_set */ network->dhcp_use_routes = true; + /* NOTE: this var might be overwriten by network_apply_anonymize_if_set */ network->dhcp_send_hostname = true; + /* To enable/disable RFC7844 Anonymity Profiles */ + network->dhcp_anonymize = false; network->dhcp_route_metric = DHCP_ROUTE_METRIC; + /* NOTE: this var might be overwrite by network_apply_anonymize_if_set */ network->dhcp_client_identifier = DHCP_CLIENT_ID_DUID; network->dhcp_route_table = RT_TABLE_MAIN; + /* NOTE: the following vars were not set to any default, + * even if they are commented in the man? + * These vars might be overwriten by network_apply_anonymize_if_set */ + network->dhcp_vendor_class_identifier = false; + /* NOTE: from man: UseMTU=... Defaults to false*/ + network->dhcp_use_mtu = false; + /* NOTE: from man: UseTimezone=... Defaults to "no".*/ + network->dhcp_use_timezone = false; network->dhcp_server_emit_dns = true; network->dhcp_server_emit_ntp = true; @@ -220,6 +273,8 @@ static int network_load_one(Manager *manager, const char *filename) { if (r < 0) return r; + network_apply_anonymize_if_set(network); + /* IPMasquerade=yes implies IPForward=yes */ if (network->ip_masquerade) network->ip_forward |= ADDRESS_FAMILY_IPV4; diff --git a/src/network/networkd-network.h b/src/network/networkd-network.h index d55e6f73f9..4025e0fa2f 100644 --- a/src/network/networkd-network.h +++ b/src/network/networkd-network.h @@ -128,6 +128,7 @@ struct Network { unsigned dhcp_route_metric; uint32_t dhcp_route_table; uint16_t dhcp_client_port; + bool dhcp_anonymize; bool dhcp_send_hostname; bool dhcp_broadcast; bool dhcp_critical; @@ -251,6 +252,7 @@ int network_load(Manager *manager); int network_get_by_name(Manager *manager, const char *name, Network **ret); int network_get(Manager *manager, struct udev_device *device, const char *ifname, const struct ether_addr *mac, Network **ret); int network_apply(Network *network, Link *link); +void network_apply_anonymize_if_set(Network *network); bool network_has_static_ipv6_addresses(Network *network); diff --git a/src/systemd/sd-dhcp-client.h b/src/systemd/sd-dhcp-client.h index f731fdcbd4..5e46d8d0e8 100644 --- a/src/systemd/sd-dhcp-client.h +++ b/src/systemd/sd-dhcp-client.h @@ -58,9 +58,17 @@ enum { SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, SD_DHCP_OPTION_INTERFACE_MTU = 26, SD_DHCP_OPTION_BROADCAST = 28, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_ROUTER_DISCOVER = 31, SD_DHCP_OPTION_STATIC_ROUTE = 33, SD_DHCP_OPTION_NTP_SERVER = 42, SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_NAMESERVER = 44, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_NODETYPE = 46, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_NETBIOS_SCOPE = 47, SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, SD_DHCP_OPTION_OVERLOAD = 52, @@ -79,6 +87,10 @@ enum { SD_DHCP_OPTION_DOMAIN_SEARCH_LIST = 119, SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, SD_DHCP_OPTION_PRIVATE_BASE = 224, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_PRIVATE_CLASSLESS_STATIC_ROUTE = 249, + /* Windows 10 option to send when Anonymize=true */ + SD_DHCP_OPTION_PRIVATE_PROXY_AUTODISCOVERY = 252, SD_DHCP_OPTION_PRIVATE_LAST = 254, SD_DHCP_OPTION_END = 255, }; @@ -146,7 +158,9 @@ int sd_dhcp_client_start(sd_dhcp_client *client); sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); -int sd_dhcp_client_new(sd_dhcp_client **ret); +/* NOTE: anonymize parameter is used to initialize PRL memory with different + * options when using RFC7844 Anonymity Profiles */ +int sd_dhcp_client_new(sd_dhcp_client **ret, int anonymize); int sd_dhcp_client_attach_event( sd_dhcp_client *client,