diff options
Diffstat (limited to 'src/libsystemd-network')
58 files changed, 7112 insertions, 6471 deletions
diff --git a/src/libsystemd-network/arp-util.c b/src/libsystemd-network/arp-util.c new file mode 100644 index 0000000000..02028bf28a --- /dev/null +++ b/src/libsystemd-network/arp-util.c @@ -0,0 +1,154 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 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 <linux/filter.h> +#include <arpa/inet.h> + +#include "arp-util.h" +#include "fd-util.h" +#include "util.h" + +int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Hardware Address must be different from our own */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((uint32_t *) eth_mac))), /* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ + BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((uint16_t *) (((char *) eth_mac) + 4)))), /* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about*/ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter + }; + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + + s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(s, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + r = s; + s = -1; + + return r; +} + +static int arp_send_packet(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha, + bool announce) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + struct ether_arp arp = { + .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ + .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ + }; + int r; + + assert(fd >= 0); + assert(pa != 0); + assert(ha); + + memcpy(&arp.arp_sha, ha, ETH_ALEN); + memcpy(&arp.arp_tpa, &pa, sizeof(pa)); + + if (announce) + memcpy(&arp.arp_spa, &pa, sizeof(pa)); + + r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return 0; +} + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, false); +} + +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, true); +} diff --git a/src/libsystemd-network/ipv4ll-internal.h b/src/libsystemd-network/arp-util.h index ae0ce43985..3ef56b002a 100644 --- a/src/libsystemd-network/ipv4ll-internal.h +++ b/src/libsystemd-network/arp-util.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -23,16 +21,12 @@ #include <netinet/if_ether.h> -#include "sparse-endian.h" #include "socket-util.h" +#include "sparse-endian.h" -int arp_network_bind_raw_socket(int index, union sockaddr_union *link); -int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, - const struct ether_arp *arp); - -void arp_packet_init(struct ether_arp *arp); -void arp_packet_probe(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha); -void arp_packet_announcement(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha); -int arp_packet_verify_headers(struct ether_arp *arp); +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac); -#define log_ipv4ll(ll, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "IPv4LL: " fmt, ##__VA_ARGS__) +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha); diff --git a/src/libsystemd-network/dhcp-identifier.c b/src/libsystemd-network/dhcp-identifier.c index 70c68ad131..a21efc4d06 100644 --- a/src/libsystemd-network/dhcp-identifier.c +++ b/src/libsystemd-network/dhcp-identifier.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -19,24 +17,54 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ - -#include "sd-id128.h" #include "libudev.h" -#include "udev-util.h" - -#include "virt.h" -#include "sparse-endian.h" -#include "siphash24.h" +#include "sd-id128.h" -#include "dhcp6-protocol.h" #include "dhcp-identifier.h" +#include "dhcp6-protocol.h" #include "network-internal.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "udev-util.h" +#include "virt.h" #define SYSTEMD_PEN 43793 #define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len) { + struct duid d; + + assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); + if (duid_len > MAX_DUID_LEN) + return -EINVAL; + + switch (duid_type) { + case DUID_TYPE_LLT: + if (duid_len <= sizeof(d.llt)) + return -EINVAL; + break; + case DUID_TYPE_EN: + if (duid_len != sizeof(d.en)) + return -EINVAL; + break; + case DUID_TYPE_LL: + if (duid_len <= sizeof(d.ll)) + return -EINVAL; + break; + case DUID_TYPE_UUID: + if (duid_len != sizeof(d.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; + } + return 0; +} + int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { sd_id128_t machine_id; + uint64_t hash; int r; assert(duid); @@ -46,19 +74,19 @@ int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { if (r < 0) return r; - unaligned_write_be16(&duid->type, DHCP6_DUID_EN); + unaligned_write_be16(&duid->type, DUID_TYPE_EN); unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); *len = sizeof(duid->type) + sizeof(duid->en); /* a bit of snake-oil perhaps, but no need to expose the machine-id - directly */ - siphash24(duid->en.id, &machine_id, sizeof(machine_id), HASH_KEY.bytes); + directly; duid->en.id might not be aligned, so we need to copy */ + hash = htole64(siphash24(&machine_id, sizeof(machine_id), HASH_KEY.bytes)); + memcpy(duid->en.id, &hash, sizeof(duid->en.id)); return 0; } - int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id) { /* name is a pointer to memory in the udev_device struct, so must have the same scope */ @@ -66,7 +94,7 @@ int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_i const char *name = NULL; uint64_t id; - if (detect_container(NULL) <= 0) { + if (detect_container() <= 0) { /* not in a container, udev will be around */ _cleanup_udev_unref_ struct udev *udev; char ifindex_str[2 + DECIMAL_STR_MAX(int)]; @@ -87,10 +115,12 @@ int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_i } if (name) - siphash24((uint8_t*)&id, name, strlen(name), HASH_KEY.bytes); + id = siphash24(name, strlen(name), HASH_KEY.bytes); else /* fall back to MAC address if no predictable name available */ - siphash24((uint8_t*)&id, mac, mac_len, HASH_KEY.bytes); + id = siphash24(mac, mac_len, HASH_KEY.bytes); + + id = htole64(id); /* fold into 32 bits */ unaligned_write_be32(_id, (id & 0xffffffff) ^ (id >> 32)); diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h index 95117915f4..1cc0f9fb71 100644 --- a/src/libsystemd-network/dhcp-identifier.h +++ b/src/libsystemd-network/dhcp-identifier.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -21,38 +19,48 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include "sd-id128.h" #include "macro.h" #include "sparse-endian.h" #include "unaligned.h" -#include "sd-id128.h" + +typedef enum DUIDType { + DUID_TYPE_LLT = 1, + DUID_TYPE_EN = 2, + DUID_TYPE_LL = 3, + DUID_TYPE_UUID = 4, + _DUID_TYPE_MAX, + _DUID_TYPE_INVALID = -1, +} DUIDType; /* RFC 3315 section 9.1: * A DUID can be no more than 128 octets long (not including the type code). */ #define MAX_DUID_LEN 128 +/* https://tools.ietf.org/html/rfc3315#section-9.1 */ struct duid { - uint16_t type; + be16_t type; union { struct { - /* DHCP6_DUID_LLT */ + /* DUID_TYPE_LLT */ uint16_t htype; uint32_t time; uint8_t haddr[0]; } _packed_ llt; struct { - /* DHCP6_DUID_EN */ + /* DUID_TYPE_EN */ uint32_t pen; uint8_t id[8]; } _packed_ en; struct { - /* DHCP6_DUID_LL */ + /* DUID_TYPE_LL */ int16_t htype; uint8_t haddr[0]; } _packed_ ll; struct { - /* DHCP6_DUID_UUID */ + /* DUID_TYPE_UUID */ sd_id128_t uuid; } _packed_ uuid; struct { @@ -61,5 +69,6 @@ struct duid { }; } _packed_; +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len); int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id); diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h index 7c60ef123c..99f690897d 100644 --- a/src/libsystemd-network/dhcp-internal.h +++ b/src/libsystemd-network/dhcp-internal.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -22,15 +20,15 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdint.h> #include <linux/if_packet.h> -#include <net/if_arp.h> #include <net/ethernet.h> - -#include "socket-util.h" +#include <net/if_arp.h> +#include <stdint.h> #include "sd-dhcp-client.h" + #include "dhcp-protocol.h" +#include "socket-util.h" int dhcp_network_bind_raw_socket(int index, union sockaddr_union *link, uint32_t xid, const uint8_t *mac_addr, @@ -44,11 +42,10 @@ int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, uint8_t overload, uint8_t code, size_t optlen, const void *optval); -typedef int (*dhcp_option_cb_t)(uint8_t code, uint8_t len, - const uint8_t *option, void *user_data); +typedef int (*dhcp_option_callback_t)(uint8_t code, uint8_t len, + const void *option, void *userdata); -int dhcp_option_parse(DHCPMessage *message, size_t len, - dhcp_option_cb_t cb, void *user_data); +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message); int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, uint8_t type, uint16_t arp_type, size_t optlen, @@ -62,13 +59,11 @@ void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum); -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_client*, sd_dhcp_client_unref); -#define _cleanup_dhcp_client_unref_ _cleanup_(sd_dhcp_client_unrefp) - /* If we are invoking callbacks of a dhcp-client, ensure unreffing the * client from the callback doesn't destroy the object we are working * on */ #define DHCP_CLIENT_DONT_DESTROY(client) \ - _cleanup_dhcp_client_unref_ _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client) + _cleanup_(sd_dhcp_client_unrefp) _unused_ sd_dhcp_client *_dont_destroy_##client = sd_dhcp_client_ref(client) -#define log_dhcp_client(client, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__) +#define log_dhcp_client_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__) +#define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h index 6e00b1ad30..82cae2300a 100644 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -25,12 +23,11 @@ #include <stdint.h> #include <linux/if_packet.h> -#include "refcnt.h" -#include "util.h" +#include "sd-dhcp-client.h" #include "dhcp-protocol.h" - -#include "sd-dhcp-client.h" +#include "list.h" +#include "util.h" struct sd_dhcp_route { struct in_addr dst_addr; @@ -38,52 +35,68 @@ struct sd_dhcp_route { unsigned char dst_prefixlen; }; +struct sd_dhcp_raw_option { + LIST_FIELDS(struct sd_dhcp_raw_option, options); + + uint8_t tag; + uint8_t length; + void *data; +}; + struct sd_dhcp_lease { - RefCount n_ref; + unsigned n_ref; - int32_t time_offset; + /* each 0 if unset */ uint32_t t1; uint32_t t2; uint32_t lifetime; - uint32_t mtu_aging_timeout; + + /* each 0 if unset */ be32_t address; be32_t server_address; - be32_t subnet_mask; be32_t router; be32_t next_server; + + bool have_subnet_mask; + be32_t subnet_mask; + + bool have_broadcast; be32_t broadcast; + struct in_addr *dns; size_t dns_size; + struct in_addr *ntp; size_t ntp_size; - struct in_addr *policy_filter; - size_t policy_filter_size; + struct sd_dhcp_route *static_route; - size_t static_route_size; - size_t static_route_allocated; - uint16_t boot_file_size; - uint16_t mdr; - uint16_t mtu; - uint8_t ttl; - bool ip_forward; - bool ip_forward_non_local; + size_t static_route_size, static_route_allocated; + + uint16_t mtu; /* 0 if unset */ + char *domainname; char *hostname; char *root_path; - uint8_t *client_id; + + void *client_id; size_t client_id_len; - uint8_t *vendor_specific; + + void *vendor_specific; size_t vendor_specific_len; + + char *timezone; + + LIST_HEAD(struct sd_dhcp_raw_option, private_options); }; int dhcp_lease_new(sd_dhcp_lease **ret); -int dhcp_lease_parse_options(uint8_t code, uint8_t len, const uint8_t *option, - void *user_data); + +int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata); +int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len); int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease); -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const uint8_t *client_id, - size_t client_id_len); +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len); -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp_lease*, sd_dhcp_lease_unref); -#define _cleanup_dhcp_lease_unref_ _cleanup_(sd_dhcp_lease_unrefp) +int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file); +int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file); diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c index 7f10838de1..a9f5a0a5de 100644 --- a/src/libsystemd-network/dhcp-network.c +++ b/src/libsystemd-network/dhcp-network.c @@ -18,18 +18,18 @@ ***/ #include <errno.h> -#include <sys/socket.h> -#include <string.h> -#include <linux/if_packet.h> -#include <linux/if_infiniband.h> #include <net/ethernet.h> #include <net/if_arp.h> #include <stdio.h> +#include <string.h> +#include <sys/socket.h> #include <linux/filter.h> - -#include "socket-util.h" +#include <linux/if_infiniband.h> +#include <linux/if_packet.h> #include "dhcp-internal.h" +#include "fd-util.h" +#include "socket-util.h" static int _bind_raw_socket(int ifindex, union sockaddr_union *link, uint32_t xid, const uint8_t *mac_addr, @@ -107,9 +107,9 @@ static int _bind_raw_socket(int ifindex, union sockaddr_union *link, return -errno; link->ll.sll_family = AF_PACKET; - link->ll.sll_protocol = htons(ETH_P_IP); + link->ll.sll_protocol = htobe16(ETH_P_IP); link->ll.sll_ifindex = ifindex; - link->ll.sll_hatype = htons(arp_type); + link->ll.sll_hatype = htobe16(arp_type); link->ll.sll_halen = mac_addr_len; memcpy(link->ll.sll_addr, bcast_addr, mac_addr_len); diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c index b6110c5f16..c105196334 100644 --- a/src/libsystemd-network/dhcp-option.c +++ b/src/libsystemd-network/dhcp-option.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -19,10 +17,13 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdint.h> -#include <string.h> #include <errno.h> +#include <stdint.h> #include <stdio.h> +#include <string.h> + +#include "alloc-util.h" +#include "utf8.h" #include "dhcp-internal.h" @@ -31,14 +32,14 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, assert(options); assert(offset); - if (code != DHCP_OPTION_END) + if (code != SD_DHCP_OPTION_END) /* always make sure there is space for an END option */ - size --; + size--; switch (code) { - case DHCP_OPTION_PAD: - case DHCP_OPTION_END: + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_END: if (size < *offset + 1) return -ENOBUFS; @@ -53,12 +54,7 @@ static int option_append(uint8_t options[], size_t size, size_t *offset, options[*offset] = code; options[*offset + 1] = optlen; - if (optlen) { - assert(optval); - - memcpy(&options[*offset + 2], optval, optlen); - } - + memcpy_safe(&options[*offset + 2], optval, optlen); *offset += optlen + 2; break; @@ -88,7 +84,7 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, else if (r == -ENOBUFS && (file || sname)) { /* did not fit, but we have more buffers to try close the options array and move the offset to its end */ - r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL); + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -109,7 +105,7 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, } else if (r == -ENOBUFS && sname) { /* did not fit, but we have more buffers to try close the file array and move the offset to its end */ - r = option_append(message->options, size, offset, DHCP_OPTION_END, 0, NULL); + r = option_append(message->options, size, offset, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -139,72 +135,84 @@ int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, } static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, - uint8_t *message_type, dhcp_option_cb_t cb, - void *user_data) { + uint8_t *message_type, char **error_message, dhcp_option_callback_t cb, + void *userdata) { uint8_t code, len; + const uint8_t *option; size_t offset = 0; while (offset < buflen) { - switch (options[offset]) { - case DHCP_OPTION_PAD: - offset++; + code = options[offset ++]; - break; + switch (code) { + case SD_DHCP_OPTION_PAD: + continue; - case DHCP_OPTION_END: + case SD_DHCP_OPTION_END: return 0; + } + + if (buflen < offset + 1) + return -ENOBUFS; - case DHCP_OPTION_MESSAGE_TYPE: - if (buflen < offset + 3) - return -ENOBUFS; + len = options[offset ++]; - len = options[++offset]; + if (buflen < offset + len) + return -EINVAL; + + option = &options[offset]; + + switch (code) { + case SD_DHCP_OPTION_MESSAGE_TYPE: if (len != 1) return -EINVAL; if (message_type) - *message_type = options[++offset]; - else - offset++; - - offset++; + *message_type = *option; break; - case DHCP_OPTION_OVERLOAD: - if (buflen < offset + 3) - return -ENOBUFS; - - len = options[++offset]; - if (len != 1) + case SD_DHCP_OPTION_ERROR_MESSAGE: + if (len == 0) return -EINVAL; - if (overload) - *overload = options[++offset]; - else - offset++; + if (error_message) { + _cleanup_free_ char *string = NULL; - offset++; + /* Accept a trailing NUL byte */ + if (memchr(option, 0, len - 1)) + return -EINVAL; - break; + string = strndup((const char *) option, len); + if (!string) + return -ENOMEM; - default: - if (buflen < offset + 3) - return -ENOBUFS; + if (!ascii_is_valid(string)) + return -EINVAL; - code = options[offset]; - len = options[++offset]; + free(*error_message); + *error_message = string; + string = NULL; + } - if (buflen < ++offset + len) + break; + case SD_DHCP_OPTION_OVERLOAD: + if (len != 1) return -EINVAL; - if (cb) - cb(code, len, &options[offset], user_data); + if (overload) + *overload = *option; + + break; - offset += len; + default: + if (cb) + cb(code, len, option, userdata); break; } + + offset += len; } if (offset < buflen) @@ -213,8 +221,8 @@ static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overlo return 0; } -int dhcp_option_parse(DHCPMessage *message, size_t len, - dhcp_option_cb_t cb, void *user_data) { +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **_error_message) { + _cleanup_free_ char *error_message = NULL; uint8_t overload = 0; uint8_t message_type = 0; int r; @@ -227,27 +235,29 @@ int dhcp_option_parse(DHCPMessage *message, size_t len, len -= sizeof(DHCPMessage); - r = parse_options(message->options, len, &overload, &message_type, - cb, user_data); + r = parse_options(message->options, len, &overload, &message_type, &error_message, cb, userdata); if (r < 0) return r; if (overload & DHCP_OVERLOAD_FILE) { - r = parse_options(message->file, sizeof(message->file), - NULL, &message_type, cb, user_data); + r = parse_options(message->file, sizeof(message->file), NULL, &message_type, &error_message, cb, userdata); if (r < 0) return r; } if (overload & DHCP_OVERLOAD_SNAME) { - r = parse_options(message->sname, sizeof(message->sname), - NULL, &message_type, cb, user_data); + r = parse_options(message->sname, sizeof(message->sname), NULL, &message_type, &error_message, cb, userdata); if (r < 0) return r; } - if (message_type) - return message_type; + if (message_type == 0) + return -ENOMSG; + + if (_error_message && IN_SET(message_type, DHCP_NAK, DHCP_DECLINE)) { + *_error_message = error_message; + error_message = NULL; + } - return -ENOMSG; + return message_type; } diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c index cd7f5095ca..8be774061d 100644 --- a/src/libsystemd-network/dhcp-packet.c +++ b/src/libsystemd-network/dhcp-packet.c @@ -19,13 +19,12 @@ ***/ #include <errno.h> -#include <string.h> #include <net/ethernet.h> #include <net/if_arp.h> +#include <string.h> - -#include "dhcp-protocol.h" #include "dhcp-internal.h" +#include "dhcp-protocol.h" #define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 @@ -45,7 +44,7 @@ int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, message->magic = htobe32(DHCP_MAGIC_COOKIE); r = dhcp_option_append(message, optlen, &offset, 0, - DHCP_OPTION_MESSAGE_TYPE, 1, &type); + SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type); if (r < 0) return r; @@ -67,7 +66,7 @@ uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { /* wrap around in one's complement */ sum++; - buf_64 ++; + buf_64++; } if (len % sizeof(uint64_t)) { diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h index aa37e9b0b5..5cf7abbff9 100644 --- a/src/libsystemd-network/dhcp-protocol.h +++ b/src/libsystemd-network/dhcp-protocol.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -21,8 +19,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <netinet/udp.h> #include <netinet/ip.h> +#include <netinet/udp.h> #include <stdint.h> #include "macro.h" @@ -61,7 +59,7 @@ typedef struct DHCPPacket DHCPPacket; #define DHCP_IP_UDP_SIZE (int32_t)(sizeof(struct udphdr) + DHCP_IP_SIZE) #define DHCP_MESSAGE_SIZE (int32_t)(sizeof(DHCPMessage)) #define DHCP_DEFAULT_MIN_SIZE 576 /* the minimum internet hosts must be able to receive */ -#define DHCP_MIN_OPTIONS_SIZE DHCP_DEFAULT_MIN_SIZE - DHCP_IP_UDP_SIZE - DHCP_MESSAGE_SIZE +#define DHCP_MIN_OPTIONS_SIZE (DHCP_DEFAULT_MIN_SIZE - DHCP_IP_UDP_SIZE - DHCP_MESSAGE_SIZE) #define DHCP_MAGIC_COOKIE (uint32_t)(0x63825363) enum { @@ -105,38 +103,11 @@ enum { DHCP_OVERLOAD_SNAME = 2, }; +#define DHCP_MAX_FQDN_LENGTH 255 + enum { - DHCP_OPTION_PAD = 0, - DHCP_OPTION_SUBNET_MASK = 1, - DHCP_OPTION_TIME_OFFSET = 2, - DHCP_OPTION_ROUTER = 3, - DHCP_OPTION_DOMAIN_NAME_SERVER = 6, - DHCP_OPTION_HOST_NAME = 12, - DHCP_OPTION_BOOT_FILE_SIZE = 13, - DHCP_OPTION_DOMAIN_NAME = 15, - DHCP_OPTION_ROOT_PATH = 17, - DHCP_OPTION_ENABLE_IP_FORWARDING = 19, - DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, - DHCP_OPTION_POLICY_FILTER = 21, - DHCP_OPTION_INTERFACE_MDR = 22, - DHCP_OPTION_INTERFACE_TTL = 23, - DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, - DHCP_OPTION_INTERFACE_MTU = 26, - DHCP_OPTION_BROADCAST = 28, - DHCP_OPTION_STATIC_ROUTE = 33, - DHCP_OPTION_NTP_SERVER = 42, - DHCP_OPTION_VENDOR_SPECIFIC = 43, - DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, - DHCP_OPTION_OVERLOAD = 52, - DHCP_OPTION_MESSAGE_TYPE = 53, - DHCP_OPTION_SERVER_IDENTIFIER = 54, - DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, - DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, - DHCP_OPTION_RENEWAL_T1_TIME = 58, - DHCP_OPTION_REBINDING_T2_TIME = 59, - DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, - DHCP_OPTION_CLIENT_IDENTIFIER = 61, - DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, - DHCP_OPTION_END = 255, + DHCP_FQDN_FLAG_S = (1 << 0), + DHCP_FQDN_FLAG_O = (1 << 1), + DHCP_FQDN_FLAG_E = (1 << 2), + DHCP_FQDN_FLAG_N = (1 << 3), }; diff --git a/src/libsystemd-network/dhcp-server-internal.h b/src/libsystemd-network/dhcp-server-internal.h index 58750c4418..0c76956fad 100644 --- a/src/libsystemd-network/dhcp-server-internal.h +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -1,4 +1,4 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#pragma once /*** This file is part of systemd. @@ -20,21 +20,17 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#pragma once - -#include "sd-event.h" #include "sd-dhcp-server.h" +#include "sd-event.h" +#include "dhcp-internal.h" #include "hashmap.h" -#include "refcnt.h" -#include "util.h" #include "log.h" - -#include "dhcp-internal.h" +#include "util.h" typedef struct DHCPClientId { size_t length; - uint8_t *data; + void *data; } DHCPClientId; typedef struct DHCPLease { @@ -47,7 +43,7 @@ typedef struct DHCPLease { } DHCPLease; struct sd_dhcp_server { - RefCount n_ref; + unsigned n_ref; sd_event *event; int event_priority; @@ -55,15 +51,25 @@ struct sd_dhcp_server { int fd; int fd_raw; - int index; + int ifindex; be32_t address; be32_t netmask; - be32_t pool_start; - size_t pool_size; - size_t next_offer; + be32_t subnet; + uint32_t pool_offset; + uint32_t pool_size; + + char *timezone; + + struct in_addr *ntp, *dns; + unsigned n_ntp, n_dns; + + bool emit_router; Hashmap *leases_by_client_id; DHCPLease **bound_leases; + DHCPLease invalid_lease; + + uint32_t max_lease_time, default_lease_time; }; typedef struct DHCPRequest { @@ -75,12 +81,9 @@ typedef struct DHCPRequest { size_t max_optlen; be32_t server_id; be32_t requested_ip; - int lifetime; + uint32_t lifetime; } DHCPRequest; -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_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "DHCP SERVER: " fmt, ##__VA_ARGS__) int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, @@ -89,5 +92,5 @@ 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]); +void client_id_hash_func(const void *p, struct siphash *state); int client_id_compare_func(const void *_a, const void *_b); diff --git a/src/libsystemd-network/dhcp6-internal.h b/src/libsystemd-network/dhcp6-internal.h index 4f54ad89a6..945c3b9721 100644 --- a/src/libsystemd-network/dhcp6-internal.h +++ b/src/libsystemd-network/dhcp6-internal.h @@ -1,11 +1,9 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** This file is part of systemd. - Copyright (C) 2014 Intel Corporation. All rights reserved. + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. 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 @@ -24,10 +22,11 @@ #include <net/ethernet.h> #include <netinet/in.h> -#include "sparse-endian.h" #include "sd-event.h" + #include "list.h" #include "macro.h" +#include "sparse-endian.h" typedef struct DHCP6Address DHCP6Address; @@ -56,10 +55,8 @@ struct DHCP6IA { typedef struct DHCP6IA DHCP6IA; -#define log_dhcp6_client(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__) - -int dhcp_network_icmp6_bind_router_solicitation(int index); -int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); +#define log_dhcp6_client_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__) +#define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__) int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, size_t optlen, const void *optval); @@ -68,6 +65,11 @@ int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen, uint8_t **optvalue); int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, DHCP6IA *ia); +int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, + struct in6_addr **addrs, size_t count, + size_t *allocated); +int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, + char ***str_arr); int dhcp6_network_bind_udp_socket(int index, struct in6_addr *address); int dhcp6_network_send_udp_socket(int s, struct in6_addr *address, diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h index 109e0f4f21..14e708ef63 100644 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -1,12 +1,10 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** This file is part of systemd. Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Intel Corporation. All rights reserved. + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. 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 @@ -24,13 +22,12 @@ #include <stdint.h> -#include "refcnt.h" - #include "sd-dhcp6-lease.h" + #include "dhcp6-internal.h" struct sd_dhcp6_lease { - RefCount n_ref; + unsigned n_ref; uint8_t *serverid; size_t serverid_len; @@ -40,6 +37,17 @@ struct sd_dhcp6_lease { DHCP6IA ia; DHCP6Address *addr_iter; + + struct in6_addr *dns; + size_t dns_count; + size_t dns_allocated; + char **domains; + size_t domains_count; + struct in6_addr *ntp; + size_t ntp_count; + size_t ntp_allocated; + char **ntp_fqdn; + size_t ntp_fqdn_count; }; int dhcp6_lease_clear_timers(DHCP6IA *ia); @@ -56,7 +64,11 @@ int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit); int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid); -int dhcp6_lease_new(sd_dhcp6_lease **ret); +int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); +int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen); +int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen); +int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen) ; -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_lease*, sd_dhcp6_lease_unref); -#define _cleanup_dhcp6_lease_free_ _cleanup_(sd_dhcp6_lease_unrefp) +int dhcp6_lease_new(sd_dhcp6_lease **ret); diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c index fe56c10273..fd2d60c9d5 100644 --- a/src/libsystemd-network/dhcp6-network.c +++ b/src/libsystemd-network/dhcp6-network.c @@ -18,150 +18,47 @@ ***/ #include <errno.h> -#include <sys/types.h> -#include <sys/socket.h> -#include <string.h> -#include <linux/if_packet.h> +#include <netinet/in.h> +#include <netinet/ip6.h> #include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> #include <unistd.h> -#include <netinet/ip6.h> -#include <netinet/icmp6.h> -#include <netinet/in.h> - -#include "socket-util.h" +#include <linux/if_packet.h> #include "dhcp6-internal.h" #include "dhcp6-protocol.h" - -#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } - -#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } - -int dhcp_network_icmp6_bind_router_solicitation(int index) -{ - struct icmp6_filter filter = { }; - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, - .ipv6mr_interface = index, - }; - _cleanup_close_ int s = -1; - int r, zero = 0, hops = 255; - - s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, - IPPROTO_ICMPV6); - if (s < 0) - return -errno; - - ICMP6_FILTER_SETBLOCKALL(&filter); - ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); - r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, - sizeof(filter)); - if (r < 0) - return -errno; - - /* RFC 3315, section 6.7, bullet point 2 may indicate that an - IPV6_PKTINFO socket option also applies for ICMPv6 multicast. - Empirical experiments indicates otherwise and therefore an - IPV6_MULTICAST_IF socket option is used here instead */ - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, - sizeof(index)); - if (r < 0) - return -errno; - - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, - sizeof(zero)); - if (r < 0) - return -errno; - - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, - sizeof(hops)); - if (r < 0) - return -errno; - - r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, - sizeof(mreq)); - if (r < 0) - return -errno; - - r = s; - s = -1; - return r; -} - -int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) -{ - struct sockaddr_in6 dst = { - .sin6_family = AF_INET6, - .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, - }; - struct { - struct nd_router_solicit rs; - struct nd_opt_hdr rs_opt; - struct ether_addr rs_opt_mac; - } _packed_ rs = { - .rs.nd_rs_type = ND_ROUTER_SOLICIT, - }; - struct iovec iov[1] = { - { &rs, }, - }; - struct msghdr msg = { - .msg_name = &dst, - .msg_namelen = sizeof(dst), - .msg_iov = iov, - .msg_iovlen = 1, - }; - int r; - - if (ether_addr) { - memcpy(&rs.rs_opt_mac, ether_addr, ETH_ALEN); - rs.rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR; - rs.rs_opt.nd_opt_len = 1; - iov[0].iov_len = sizeof(rs); - } else - iov[0].iov_len = sizeof(rs.rs); - - r = sendmsg(s, &msg, 0); - if (r < 0) - return -errno; - - return 0; -} +#include "fd-util.h" +#include "socket-util.h" int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { - struct in6_pktinfo pktinfo = { - .ipi6_ifindex = index, - }; union sockaddr_union src = { .in6.sin6_family = AF_INET6, .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT), - .in6.sin6_addr = IN6ADDR_ANY_INIT, + .in6.sin6_scope_id = index, }; _cleanup_close_ int s = -1; int r, off = 0, on = 1; - if (local_address) - memcpy(&src.in6.sin6_addr, local_address, - sizeof(src.in6.sin6_addr)); + assert(index > 0); + assert(local_address); - s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, - IPPROTO_UDP); + src.in6.sin6_addr = *local_address; + + s = socket(AF_INET6, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_UDP); if (s < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, &pktinfo, - sizeof(pktinfo)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); if (r < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)); + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(off)); if (r < 0) return -errno; - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(off)); + r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); if (r < 0) return -errno; diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c index ea863f45e4..5462e03476 100644 --- a/src/libsystemd-network/dhcp6-option.c +++ b/src/libsystemd-network/dhcp6-option.c @@ -1,9 +1,7 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. - Copyright (C) 2014 Intel Corporation. All rights reserved. + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. 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 @@ -19,16 +17,20 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <netinet/in.h> #include <errno.h> +#include <netinet/in.h> #include <string.h> -#include "sparse-endian.h" -#include "unaligned.h" -#include "util.h" +#include "sd-dhcp6-client.h" +#include "alloc-util.h" #include "dhcp6-internal.h" #include "dhcp6-protocol.h" +#include "dns-domain.h" +#include "sparse-endian.h" +#include "strv.h" +#include "unaligned.h" +#include "util.h" #define DHCP6_OPTION_IA_NA_LEN 12 #define DHCP6_OPTION_IA_TA_LEN 4 @@ -69,8 +71,7 @@ int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, if (r < 0) return r; - if (optval) - memcpy(*buf, optval, optlen); + memcpy_safe(*buf, optval, optlen); *buf += optlen; *buflen -= optlen; @@ -88,11 +89,11 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { assert_return(buf && *buf && buflen && ia, -EINVAL); switch (ia->type) { - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: len = DHCP6_OPTION_IA_NA_LEN; break; - case DHCP6_OPTION_IA_TA: + case SD_DHCP6_OPTION_IA_TA: len = DHCP6_OPTION_IA_TA_LEN; break; @@ -115,7 +116,7 @@ int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { *buflen -= len; LIST_FOREACH(addresses, addr, ia->addresses) { - r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR, + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, sizeof(addr->iaaddr)); if (r < 0) return r; @@ -194,7 +195,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, assert_return(!ia->addresses, -EINVAL); switch (iatype) { - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) + sizeof(addr->iaaddr)) { @@ -217,7 +218,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; - case DHCP6_OPTION_IA_TA: + case SD_DHCP6_OPTION_IA_TA: if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) + sizeof(addr->iaaddr)) { r = -ENOBUFS; @@ -245,7 +246,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) { switch (opt) { - case DHCP6_OPTION_IAADDR: + case SD_DHCP6_OPTION_IAADDR: addr = new0(DHCP6Address, 1); if (!addr) { @@ -272,7 +273,7 @@ int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, break; - case DHCP6_OPTION_STATUS_CODE: + case SD_DHCP6_OPTION_STATUS_CODE: if (optlen < sizeof(status)) break; @@ -317,3 +318,96 @@ error: return r; } + +int dhcp6_option_parse_ip6addrs(uint8_t *optval, uint16_t optlen, + struct in6_addr **addrs, size_t count, + size_t *allocated) { + + if (optlen == 0 || optlen % sizeof(struct in6_addr) != 0) + return -EINVAL; + + if (!GREEDY_REALLOC(*addrs, *allocated, + count * sizeof(struct in6_addr) + optlen)) + return -ENOMEM; + + memcpy(*addrs + count, optval, optlen); + + count += optlen / sizeof(struct in6_addr); + + return count; +} + +int dhcp6_option_parse_domainname(const uint8_t *optval, uint16_t optlen, char ***str_arr) { + size_t pos = 0, idx = 0; + _cleanup_free_ char **names = NULL; + int r; + + assert_return(optlen > 1, -ENODATA); + assert_return(optval[optlen - 1] == '\0', -EINVAL); + + while (pos < optlen) { + _cleanup_free_ char *ret = NULL; + size_t n = 0, allocated = 0; + bool first = true; + + for (;;) { + uint8_t c; + + c = optval[pos++]; + + if (c == 0) + /* End of name */ + break; + else if (c <= 63) { + const char *label; + + /* Literal label */ + label = (const char *)&optval[pos]; + pos += c; + if (pos > optlen) + return -EMSGSIZE; + + if (!GREEDY_REALLOC(ret, allocated, n + !first + DNS_LABEL_ESCAPED_MAX)) { + r = -ENOMEM; + goto fail; + } + + if (first) + first = false; + else + ret[n++] = '.'; + + r = dns_label_escape(label, c, ret + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + goto fail; + + n += r; + continue; + } else { + r = -EBADMSG; + goto fail; + } + } + + if (!GREEDY_REALLOC(ret, allocated, n + 1)) { + r = -ENOMEM; + goto fail; + } + + ret[n] = 0; + + r = strv_extend(&names, ret); + if (r < 0) + goto fail; + + idx++; + } + + *str_arr = names; + names = NULL; + + return idx; + +fail: + return r; +} diff --git a/src/libsystemd-network/dhcp6-protocol.h b/src/libsystemd-network/dhcp6-protocol.h index 3e0f339237..2487c470ab 100644 --- a/src/libsystemd-network/dhcp6-protocol.h +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -64,13 +62,6 @@ enum { #define DHCP6_REB_TIMEOUT 10 * USEC_PER_SEC #define DHCP6_REB_MAX_RT 600 * USEC_PER_SEC -enum { - DHCP6_DUID_LLT = 1, - DHCP6_DUID_EN = 2, - DHCP6_DUID_LL = 3, - DHCP6_DUID_UUID = 4, -}; - enum DHCP6State { DHCP6_STATE_STOPPED = 0, DHCP6_STATE_INFORMATION_REQUEST = 1, @@ -99,38 +90,9 @@ enum { }; enum { - DHCP6_OPTION_CLIENTID = 1, - DHCP6_OPTION_SERVERID = 2, - DHCP6_OPTION_IA_NA = 3, - DHCP6_OPTION_IA_TA = 4, - DHCP6_OPTION_IAADDR = 5, - DHCP6_OPTION_ORO = 6, - DHCP6_OPTION_PREFERENCE = 7, - DHCP6_OPTION_ELAPSED_TIME = 8, - DHCP6_OPTION_RELAY_MSG = 9, - /* option code 10 is unassigned */ - DHCP6_OPTION_AUTH = 11, - DHCP6_OPTION_UNICAST = 12, - DHCP6_OPTION_STATUS_CODE = 13, - DHCP6_OPTION_RAPID_COMMIT = 14, - DHCP6_OPTION_USER_CLASS = 15, - DHCP6_OPTION_VENDOR_CLASS = 16, - DHCP6_OPTION_VENDOR_OPTS = 17, - DHCP6_OPTION_INTERFACE_ID = 18, - DHCP6_OPTION_RECONF_MSG = 19, - DHCP6_OPTION_RECONF_ACCEPT = 20, - - DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ - DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ - - DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075 */ - - /* option code 35 is unassigned */ - - DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ - - /* option codes 89-142 are unassigned */ - /* option codes 144-65535 are unassigned */ + DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, + DHCP6_NTP_SUBOPTION_MC_ADDR = 2, + DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, }; enum { diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c new file mode 100644 index 0000000000..c2e4b0e9e3 --- /dev/null +++ b/src/libsystemd-network/icmp6-util.c @@ -0,0 +1,141 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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 <errno.h> +#include <netinet/icmp6.h> +#include <netinet/in.h> +#include <netinet/ip6.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <net/if.h> +#include <linux/if_packet.h> + +#include "fd-util.h" +#include "icmp6-util.h" +#include "socket-util.h" + +#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } + +#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } + +int icmp6_bind_router_solicitation(int index) { + struct icmp6_filter filter = { }; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, + .ipv6mr_interface = index, + }; + _cleanup_close_ int s = -1; + char ifname[IF_NAMESIZE] = ""; + static const int zero = 0, one = 1, hops = 255; + int r; + + s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); + if (s < 0) + return -errno; + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); + if (r < 0) + return -errno; + + /* RFC 3315, section 6.7, bullet point 2 may indicate that an + IPV6_PKTINFO socket option also applies for ICMPv6 multicast. + Empirical experiments indicates otherwise and therefore an + IPV6_MULTICAST_IF socket option is used here instead */ + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); + if (r < 0) + return -errno; + + if (if_indextoname(index, ifname) == 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); + if (r < 0) + return -errno; + + r = s; + s = -1; + return r; +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + }; + struct { + struct nd_router_solicit rs; + struct nd_opt_hdr rs_opt; + struct ether_addr rs_opt_mac; + } _packed_ rs = { + .rs.nd_rs_type = ND_ROUTER_SOLICIT, + .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, + .rs_opt.nd_opt_len = 1, + }; + struct iovec iov = { + .iov_base = &rs, + .iov_len = sizeof(rs), + }; + struct msghdr msg = { + .msg_name = &dst, + .msg_namelen = sizeof(dst), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int r; + + assert(s >= 0); + assert(ether_addr); + + rs.rs_opt_mac = *ether_addr; + + r = sendmsg(s, &msg, 0); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/lldp-util.h b/src/libsystemd-network/icmp6-util.h index 112001e4b9..2b4dbc76ce 100644 --- a/src/libsystemd-network/lldp-util.h +++ b/src/libsystemd-network/icmp6-util.h @@ -1,10 +1,9 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#pragma once /*** This file is part of systemd. - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. 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 @@ -20,7 +19,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#pragma once +#include <net/ethernet.h> -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_lldp *, sd_lldp_free); -#define _cleanup_lldp_free_ _cleanup_(sd_lldp_freep) +int icmp6_bind_router_solicitation(int index); +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr); diff --git a/src/libsystemd-network/ipv4ll-network.c b/src/libsystemd-network/ipv4ll-network.c deleted file mode 100644 index 93ffed408f..0000000000 --- a/src/libsystemd-network/ipv4ll-network.c +++ /dev/null @@ -1,91 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. All rights reserved. - - 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 <linux/filter.h> - -#include "util.h" -#include "ipv4ll-internal.h" - -int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, - const struct ether_arp *arp) { - int r; - - assert(arp); - assert(link); - assert(fd >= 0); - - r = sendto(fd, arp, sizeof(struct ether_arp), 0, &link->sa, sizeof(link->ll)); - if (r < 0) - return -errno; - - return 0; -} - -int arp_network_bind_raw_socket(int ifindex, union sockaddr_union *link) { - - static const struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 0, 1), /* protocol == request ? */ - BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 0, 1), /* protocol == reply ? */ - BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - }; - struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = (struct sock_filter*) filter - }; - _cleanup_close_ int s = -1; - int r; - - assert(ifindex > 0); - assert(link); - - s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); - if (r < 0) - return -errno; - - link->ll.sll_family = AF_PACKET; - link->ll.sll_protocol = htons(ETH_P_ARP); - link->ll.sll_ifindex = ifindex; - link->ll.sll_halen = ETH_ALEN; - memset(link->ll.sll_addr, 0xff, ETH_ALEN); - - r = bind(s, &link->sa, sizeof(link->ll)); - if (r < 0) - return -errno; - - r = s; - s = -1; - - return r; -} diff --git a/src/libsystemd-network/ipv4ll-packet.c b/src/libsystemd-network/ipv4ll-packet.c deleted file mode 100644 index 2b6c73ab4b..0000000000 --- a/src/libsystemd-network/ipv4ll-packet.c +++ /dev/null @@ -1,71 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. All rights reserved. - - 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 <arpa/inet.h> - -#include "util.h" -#include "ipv4ll-internal.h" - -void arp_packet_init(struct ether_arp *arp) { - assert(arp); - - memzero(arp, sizeof(struct ether_arp)); - /* Header */ - arp->ea_hdr.ar_hrd = htons(ARPHRD_ETHER); /* HTYPE */ - arp->ea_hdr.ar_pro = htons(ETHERTYPE_IP); /* PTYPE */ - arp->ea_hdr.ar_hln = ETH_ALEN; /* HLEN */ - arp->ea_hdr.ar_pln = sizeof arp->arp_spa; /* PLEN */ - arp->ea_hdr.ar_op = htons(ARPOP_REQUEST); /* REQUEST */ -} - -void arp_packet_probe(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha) { - assert(ha); - - arp_packet_init(arp); - memcpy(arp->arp_sha, ha, ETH_ALEN); - memcpy(arp->arp_tpa, &pa, sizeof(pa)); -} - -void arp_packet_announcement(struct ether_arp *arp, be32_t pa, const struct ether_addr *ha) { - assert(ha); - - arp_packet_init(arp); - memcpy(arp->arp_sha, ha, ETH_ALEN); - memcpy(arp->arp_tpa, &pa, sizeof(pa)); - memcpy(arp->arp_spa, &pa, sizeof(pa)); -} - -int arp_packet_verify_headers(struct ether_arp *arp) { - assert(arp); - - if (arp->ea_hdr.ar_hrd != htons(ARPHRD_ETHER)) { - log_ipv4ll(NULL, "ignoring packet: header is not ARPHRD_ETHER"); - return -EINVAL; - } - if (arp->ea_hdr.ar_pro != htons(ETHERTYPE_IP)) { - log_ipv4ll(NULL, "ignoring packet: protocol is not ETHERTYPE_IP"); - return -EINVAL; - } - if (arp->ea_hdr.ar_op != htons(ARPOP_REQUEST) && - arp->ea_hdr.ar_op != htons(ARPOP_REPLY)) { - log_ipv4ll(NULL, "ignoring packet: operation is not ARPOP_REQUEST or ARPOP_REPLY"); - return -EINVAL; - } - - return 0; -} diff --git a/src/libsystemd-network/lldp-internal.c b/src/libsystemd-network/lldp-internal.c deleted file mode 100644 index 0f354461f7..0000000000 --- a/src/libsystemd-network/lldp-internal.c +++ /dev/null @@ -1,533 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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 "lldp-internal.h" - -/* We store maximum 1K chassis entries */ -#define LLDP_MIB_MAX_CHASSIS 1024 - -/* Maximum Ports can be attached to any chassis */ -#define LLDP_MIB_MAX_PORT_PER_CHASSIS 32 - -int lldp_read_chassis_id(tlv_packet *tlv, - uint8_t *type, - uint16_t *length, - uint8_t **data) { - uint8_t subtype; - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_CHASSIS_ID); - if (r < 0) - goto out2; - - r = tlv_packet_read_u8(tlv, &subtype); - if (r < 0) - goto out1; - - switch (subtype) { - case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: - - r = tlv_packet_read_bytes(tlv, data, length); - if (r < 0) - goto out1; - - break; - default: - r = -EOPNOTSUPP; - break; - } - - *type = subtype; - - out1: - (void) lldp_tlv_packet_exit_container(tlv); - - out2: - return r; -} - -int lldp_read_port_id(tlv_packet *tlv, - uint8_t *type, - uint16_t *length, - uint8_t **data) { - uint8_t subtype; - char *s; - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_ID); - if (r < 0) - goto out2; - - r = tlv_packet_read_u8(tlv, &subtype); - if (r < 0) - goto out1; - - switch (subtype) { - case LLDP_PORT_SUBTYPE_PORT_COMPONENT: - case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: - case LLDP_PORT_SUBTYPE_INTERFACE_NAME: - case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: - - r = tlv_packet_read_string(tlv, &s, length); - if (r < 0) - goto out1; - - *data = (uint8_t *) s; - - break; - case LLDP_PORT_SUBTYPE_MAC_ADDRESS: - - r = tlv_packet_read_bytes(tlv, data, length); - if (r < 0) - goto out1; - - break; - default: - r = -EOPNOTSUPP; - break; - } - - *type = subtype; - - out1: - (void) lldp_tlv_packet_exit_container(tlv); - - out2: - return r; -} - -int lldp_read_ttl(tlv_packet *tlv, uint16_t *ttl) { - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_TTL); - if (r < 0) - goto out; - - r = tlv_packet_read_u16(tlv, ttl); - - (void) lldp_tlv_packet_exit_container(tlv); - - out: - return r; -} - -int lldp_read_system_name(tlv_packet *tlv, - uint16_t *length, - char **data) { - char *s; - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_SYSTEM_NAME); - if (r < 0) - return r; - - r = tlv_packet_read_string(tlv, &s, length); - if (r < 0) - goto out; - - *data = (char *) s; - - out: - (void) lldp_tlv_packet_exit_container(tlv); - - return r; -} - -int lldp_read_system_description(tlv_packet *tlv, - uint16_t *length, - char **data) { - char *s; - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_SYSTEM_DESCRIPTION); - if (r < 0) - return r; - - r = tlv_packet_read_string(tlv, &s, length); - if (r < 0) - goto out; - - *data = (char *) s; - - out: - (void) lldp_tlv_packet_exit_container(tlv); - - return r; -} - -int lldp_read_port_description(tlv_packet *tlv, - uint16_t *length, - char **data) { - char *s; - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_DESCRIPTION); - if (r < 0) - return r; - - r = tlv_packet_read_string(tlv, &s, length); - if (r < 0) - goto out; - - *data = (char *) s; - - out: - (void) lldp_tlv_packet_exit_container(tlv); - - return r; -} - -int lldp_read_system_capability(tlv_packet *tlv, uint16_t *data) { - int r; - - assert_return(tlv, -EINVAL); - - r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_SYSTEM_CAPABILITIES); - if (r < 0) - return r; - - r = tlv_packet_read_u16(tlv, data); - if (r < 0) - goto out; - - return 0; - out: - - (void) lldp_tlv_packet_exit_container(tlv); - - return r; -} - -/* 10.5.5.2.2 mibUpdateObjects () - * The mibUpdateObjects () procedure updates the MIB objects corresponding to - * the TLVs contained in the received LLDPDU for the LLDP remote system - * indicated by the LLDP remote systems update process defined in 10.3.5 */ - -int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv) { - lldp_neighbour_port *p; - uint16_t length, ttl; - uint8_t *data; - uint8_t type; - int r; - - assert_return(c, -EINVAL); - assert_return(tlv, -EINVAL); - - r = lldp_read_port_id(tlv, &type, &length, &data); - if (r < 0) - return r; - - /* Update the packet if we already have */ - LIST_FOREACH(port, p, c->ports) { - - if ((p->type == type && p->length == length && !memcmp(p->data, data, p->length))) { - - r = lldp_read_ttl(tlv, &ttl); - if (r < 0) - return r; - - p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic()); - - tlv_packet_free(p->packet); - p->packet = tlv; - - prioq_reshuffle(p->c->by_expiry, p, &p->prioq_idx); - - return 0; - } - } - - return -1; -} - -int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv) { - lldp_neighbour_port *p, *q; - uint8_t *data; - uint16_t length; - uint8_t type; - int r; - - assert_return(c, -EINVAL); - assert_return(tlv, -EINVAL); - - r = lldp_read_port_id(tlv, &type, &length, &data); - if (r < 0) - return r; - - LIST_FOREACH_SAFE(port, p, q, c->ports) { - - /* Find the port */ - if (p->type == type && p->length == length && !memcmp(p->data, data, p->length)) { - lldp_neighbour_port_remove_and_free(p); - break; - } - } - - return 0; -} - -int lldp_mib_add_objects(Prioq *by_expiry, - Hashmap *neighbour_mib, - tlv_packet *tlv) { - _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL; - _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL; - lldp_chassis_id chassis_id; - bool new_chassis = false; - uint8_t subtype, *data; - uint16_t ttl, length; - int r; - - assert_return(by_expiry, -EINVAL); - assert_return(neighbour_mib, -EINVAL); - assert_return(tlv, -EINVAL); - - r = lldp_read_chassis_id(tlv, &subtype, &length, &data); - if (r < 0) - goto drop; - - r = lldp_read_ttl(tlv, &ttl); - if (r < 0) - goto drop; - - /* Make hash key */ - chassis_id.type = subtype; - chassis_id.length = length; - chassis_id.data = data; - - /* Try to find the Chassis */ - c = hashmap_get(neighbour_mib, &chassis_id); - if (!c) { - - /* Don't create chassis if ttl 0 is received . Silently drop it */ - if (ttl == 0) { - log_lldp("TTL value 0 received. Skiping Chassis creation."); - goto drop; - } - - /* Admission Control: Can we store this packet ? */ - if (hashmap_size(neighbour_mib) >= LLDP_MIB_MAX_CHASSIS) { - - log_lldp("Exceeding number of chassie: %d. Dropping ...", - hashmap_size(neighbour_mib)); - goto drop; - } - - r = lldp_chassis_new(tlv, by_expiry, neighbour_mib, &c); - if (r < 0) - goto drop; - - new_chassis = true; - - r = hashmap_put(neighbour_mib, &c->chassis_id, c); - if (r < 0) - goto drop; - - } else { - - /* When the TTL field is set to zero, the receiving LLDP agent is notified all - * system information associated with the LLDP agent/port is to be deleted */ - if (ttl == 0) { - log_lldp("TTL value 0 received . Deleting associated Port ..."); - - lldp_mib_remove_objects(c, tlv); - - c = NULL; - goto drop; - } - - /* if we already have this port just update it */ - r = lldp_mib_update_objects(c, tlv); - if (r >= 0) { - c = NULL; - return r; - } - - /* Admission Control: Can this port attached to the existing chassis ? */ - if (REFCNT_GET(c->n_ref) >= LLDP_MIB_MAX_PORT_PER_CHASSIS) { - log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...", - REFCNT_GET(c->n_ref)); - - c = NULL; - goto drop; - } - } - - /* This is a new port */ - r = lldp_neighbour_port_new(c, tlv, &p); - if (r < 0) - goto drop; - - r = prioq_put(c->by_expiry, p, &p->prioq_idx); - if (r < 0) - goto drop; - - /* Attach new port to chassis */ - LIST_PREPEND(port, c->ports, p); - REFCNT_INC(c->n_ref); - - p = NULL; - c = NULL; - - return 0; - - drop: - tlv_packet_free(tlv); - - if (new_chassis) - hashmap_remove(neighbour_mib, &c->chassis_id); - - return r; -} - -void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p) { - lldp_chassis *c; - - assert(p); - assert(p->c); - - c = p->c; - - prioq_remove(c->by_expiry, p, &p->prioq_idx); - - LIST_REMOVE(port, c->ports, p); - lldp_neighbour_port_free(p); - - /* Drop the Chassis if no port is attached */ - if (REFCNT_DEC(c->n_ref) <= 1) { - hashmap_remove(c->neighbour_mib, &c->chassis_id); - lldp_chassis_free(c); - } -} - -void lldp_neighbour_port_free(lldp_neighbour_port *p) { - - if(!p) - return; - - tlv_packet_free(p->packet); - - free(p->data); - free(p); -} - -int lldp_neighbour_port_new(lldp_chassis *c, - tlv_packet *tlv, - lldp_neighbour_port **ret) { - _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL; - uint16_t length, ttl; - uint8_t *data; - uint8_t type; - int r; - - assert(tlv); - - r = lldp_read_port_id(tlv, &type, &length, &data); - if (r < 0) - return r; - - r = lldp_read_ttl(tlv, &ttl); - if (r < 0) - return r; - - p = new0(lldp_neighbour_port, 1); - if (!p) - return -ENOMEM; - - p->c = c; - p->type = type; - p->length = length; - p->packet = tlv; - p->prioq_idx = PRIOQ_IDX_NULL; - p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic()); - - p->data = memdup(data, length); - if (!p->data) - return -ENOMEM; - - *ret = p; - p = NULL; - - return 0; -} - -void lldp_chassis_free(lldp_chassis *c) { - - if (!c) - return; - - if (REFCNT_GET(c->n_ref) > 1) - return; - - free(c->chassis_id.data); - free(c); -} - -int lldp_chassis_new(tlv_packet *tlv, - Prioq *by_expiry, - Hashmap *neighbour_mib, - lldp_chassis **ret) { - _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL; - uint16_t length; - uint8_t *data; - uint8_t type; - int r; - - assert(tlv); - - r = lldp_read_chassis_id(tlv, &type, &length, &data); - if (r < 0) - return r; - - c = new0(lldp_chassis, 1); - if (!c) - return -ENOMEM; - - c->n_ref = REFCNT_INIT; - c->chassis_id.type = type; - c->chassis_id.length = length; - - c->chassis_id.data = memdup(data, length); - if (!c->chassis_id.data) - return -ENOMEM; - - LIST_HEAD_INIT(c->ports); - - c->by_expiry = by_expiry; - c->neighbour_mib = neighbour_mib; - - *ret = c; - c = NULL; - - return 0; -} diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h index 8e09ee8f3a..becc162fab 100644 --- a/src/libsystemd-network/lldp-internal.h +++ b/src/libsystemd-network/lldp-internal.h @@ -1,4 +1,4 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#pragma once /*** This file is part of systemd. @@ -20,80 +20,36 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#pragma once +#include "sd-event.h" +#include "sd-lldp.h" +#include "hashmap.h" #include "log.h" -#include "list.h" -#include "refcnt.h" -#include "lldp-tlv.h" #include "prioq.h" -typedef struct lldp_neighbour_port lldp_neighbour_port; -typedef struct lldp_chassis lldp_chassis; -typedef struct lldp_chassis_id lldp_chassis_id; -typedef struct lldp_agent_statistics lldp_agent_statistics; - -struct lldp_neighbour_port { - uint8_t type; - uint8_t *data; - - uint16_t length; - usec_t until; +struct sd_lldp { + unsigned n_ref; - unsigned prioq_idx; + int ifindex; + int fd; - lldp_chassis *c; - tlv_packet *packet; + sd_event *event; + int64_t event_priority; + sd_event_source *io_event_source; + sd_event_source *timer_event_source; - LIST_FIELDS(lldp_neighbour_port, port); -}; + Prioq *neighbor_by_expiry; + Hashmap *neighbor_by_id; -int lldp_neighbour_port_new(lldp_chassis *c, tlv_packet *tlv, lldp_neighbour_port **ret); -void lldp_neighbour_port_free(lldp_neighbour_port *p); -void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p); + uint64_t neighbors_max; -DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_neighbour_port *, lldp_neighbour_port_free); -#define _cleanup_lldp_neighbour_port_free_ _cleanup_(lldp_neighbour_port_freep) + sd_lldp_callback_t callback; + void *userdata; -struct lldp_chassis_id { - uint8_t type; - uint16_t length; + uint16_t capability_mask; - uint8_t *data; + struct ether_addr filter_address; }; -struct lldp_chassis { - RefCount n_ref; - - lldp_chassis_id chassis_id; - - Prioq *by_expiry; - Hashmap *neighbour_mib; - - LIST_HEAD(lldp_neighbour_port, ports); -}; - -int lldp_chassis_new(tlv_packet *tlv, - Prioq *by_expiry, - Hashmap *neighbour_mib, - lldp_chassis **ret); - -void lldp_chassis_free(lldp_chassis *c); - -DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_chassis *, lldp_chassis_free); -#define _cleanup_lldp_chassis_free_ _cleanup_(lldp_chassis_freep) - -int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv); -int lldp_mib_add_objects(Prioq *by_expiry, Hashmap *neighbour_mib, tlv_packet *tlv); -int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv); - -int lldp_read_chassis_id(tlv_packet *tlv, uint8_t *type, uint16_t *length, uint8_t **data); -int lldp_read_port_id(tlv_packet *tlv, uint8_t *type, uint16_t *length, uint8_t **data); -int lldp_read_ttl(tlv_packet *tlv, uint16_t *ttl); -int lldp_read_system_name(tlv_packet *tlv, uint16_t *length, char **data); -int lldp_read_system_description(tlv_packet *tlv, uint16_t *length, char **data); -int lldp_read_system_capability(tlv_packet *tlv, uint16_t *data); -int lldp_read_port_description(tlv_packet *tlv, uint16_t *length, char **data); - -int lldp_handle_packet(tlv_packet *m, uint16_t length); -#define log_lldp(fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__) +#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__) +#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c new file mode 100644 index 0000000000..53e29377b3 --- /dev/null +++ b/src/libsystemd-network/lldp-neighbor.c @@ -0,0 +1,813 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + 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 "alloc-util.h" +#include "escape.h" +#include "ether-addr-util.h" +#include "hexdecoct.h" +#include "in-addr-util.h" +#include "lldp-internal.h" +#include "lldp-neighbor.h" +#include "unaligned.h" + +static void lldp_neighbor_id_hash_func(const void *p, struct siphash *state) { + const LLDPNeighborID *id = p; + + siphash24_compress(id->chassis_id, id->chassis_id_size, state); + siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state); + siphash24_compress(id->port_id, id->port_id_size, state); + siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state); +} + +static int lldp_neighbor_id_compare_func(const void *a, const void *b) { + const LLDPNeighborID *x = a, *y = b; + int r; + + r = memcmp(x->chassis_id, y->chassis_id, MIN(x->chassis_id_size, y->chassis_id_size)); + if (r != 0) + return r; + + if (x->chassis_id_size < y->chassis_id_size) + return -1; + + if (x->chassis_id_size > y->chassis_id_size) + return 1; + + r = memcmp(x->port_id, y->port_id, MIN(x->port_id_size, y->port_id_size)); + if (r != 0) + return r; + + if (x->port_id_size < y->port_id_size) + return -1; + if (x->port_id_size > y->port_id_size) + return 1; + + return 0; +} + +const struct hash_ops lldp_neighbor_id_hash_ops = { + .hash = lldp_neighbor_id_hash_func, + .compare = lldp_neighbor_id_compare_func +}; + +int lldp_neighbor_prioq_compare_func(const void *a, const void *b) { + const sd_lldp_neighbor *x = a, *y = b; + + if (x->until < y->until) + return -1; + + if (x->until > y->until) + return 1; + + return 0; +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) { + if (!n) + return NULL; + + assert(n->n_ref > 0 || n->lldp); + n->n_ref++; + + return n; +} + +static void lldp_neighbor_free(sd_lldp_neighbor *n) { + assert(n); + + free(n->id.port_id); + free(n->id.chassis_id); + free(n->port_description); + free(n->system_name); + free(n->system_description); + free(n->chassis_id_as_string); + free(n->port_id_as_string); + free(n); +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) { + + /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from + * the sd_lldp object. */ + + if (!n) + return NULL; + + assert(n->n_ref > 0); + n->n_ref--; + + if (n->n_ref <= 0 && !n->lldp) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) { + + /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */ + + if (!n) + return NULL; + + if (!n->lldp) + return NULL; + + assert_se(hashmap_remove(n->lldp->neighbor_by_id, &n->id) == n); + assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0); + + n->lldp = NULL; + + if (n->n_ref <= 0) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) { + sd_lldp_neighbor *n; + + n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size); + if (!n) + return NULL; + + n->raw_size = raw_size; + n->n_ref = 1; + + return n; +} + +static int parse_string(char **s, const void *q, size_t n) { + const char *p = q; + char *k; + + assert(s); + assert(p || n == 0); + + if (*s) { + log_lldp("Found duplicate string, ignoring field."); + return 0; + } + + /* Strip trailing NULs, just to be nice */ + while (n > 0 && p[n-1] == 0) + n--; + + if (n <= 0) /* Ignore empty strings */ + return 0; + + /* Look for inner NULs */ + if (memchr(p, 0, n)) { + log_lldp("Found inner NUL in string, ignoring field."); + return 0; + } + + /* Let's escape weird chars, for security reasons */ + k = cescape_length(p, n); + if (!k) + return -ENOMEM; + + free(*s); + *s = k; + + return 1; +} + +int lldp_neighbor_parse(sd_lldp_neighbor *n) { + struct ether_header h; + const uint8_t *p; + size_t left; + int r; + + assert(n); + + if (n->raw_size < sizeof(struct ether_header)) { + log_lldp("Received truncated packet, ignoring."); + return -EBADMSG; + } + + memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h)); + + if (h.ether_type != htobe16(ETHERTYPE_LLDP)) { + log_lldp("Received packet with wrong type, ignoring."); + return -EBADMSG; + } + + if (h.ether_dhost[0] != 0x01 || + h.ether_dhost[1] != 0x80 || + h.ether_dhost[2] != 0xc2 || + h.ether_dhost[3] != 0x00 || + h.ether_dhost[4] != 0x00 || + !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) { + log_lldp("Received packet with wrong destination address, ignoring."); + return -EBADMSG; + } + + memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr)); + memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr)); + + p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header); + left = n->raw_size - sizeof(struct ether_header); + + for (;;) { + uint8_t type; + uint16_t length; + + if (left < 2) { + log_lldp("TLV lacks header, ignoring."); + return -EBADMSG; + } + + type = p[0] >> 1; + length = p[1] + (((uint16_t) (p[0] & 1)) << 8); + p += 2, left -= 2; + + if (left < length) { + log_lldp("TLV truncated, ignoring datagram."); + return -EBADMSG; + } + + switch (type) { + + case SD_LLDP_TYPE_END: + if (length != 0) { + log_lldp("End marker TLV not zero-sized, ignoring datagram."); + return -EBADMSG; + } + if (left != 0) { + log_lldp("Trailing garbage in datagram, ignoring datagram."); + return -EBADMSG; + } + + goto end_marker; + + case SD_LLDP_TYPE_CHASSIS_ID: + if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */ + log_lldp("Chassis ID field size out of range, ignoring datagram."); + return -EBADMSG; + } + if (n->id.chassis_id) { + log_lldp("Duplicate chassis ID field, ignoring datagram."); + return -EBADMSG; + } + + n->id.chassis_id = memdup(p, length); + if (!n->id.chassis_id) + return -ENOMEM; + + n->id.chassis_id_size = length; + break; + + case SD_LLDP_TYPE_PORT_ID: + if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */ + log_lldp("Port ID field size out of range, ignoring datagram."); + return -EBADMSG; + } + if (n->id.port_id) { + log_lldp("Duplicate port ID field, ignoring datagram."); + return -EBADMSG; + } + + n->id.port_id = memdup(p, length); + if (!n->id.port_id) + return -ENOMEM; + + n->id.port_id_size = length; + break; + + case SD_LLDP_TYPE_TTL: + if (length != 2) { + log_lldp("TTL field has wrong size, ignoring datagram."); + return -EBADMSG; + } + + if (n->has_ttl) { + log_lldp("Duplicate TTL field, ignoring datagram."); + return -EBADMSG; + } + + n->ttl = unaligned_read_be16(p); + n->has_ttl = true; + break; + + case SD_LLDP_TYPE_PORT_DESCRIPTION: + r = parse_string(&n->port_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_NAME: + r = parse_string(&n->system_name, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_DESCRIPTION: + r = parse_string(&n->system_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_CAPABILITIES: + if (length != 4) + log_lldp("System capabilities field has wrong size, ignoring."); + else { + n->system_capabilities = unaligned_read_be16(p); + n->enabled_capabilities = unaligned_read_be16(p + 2); + n->has_capabilities = true; + } + + break; + + case SD_LLDP_TYPE_PRIVATE: + if (length < 4) + log_lldp("Found private TLV that is too short, ignoring."); + + break; + } + + + p += length, left -= length; + } + +end_marker: + if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) { + log_lldp("One or more mandatory TLV missing in datagram. Ignoring."); + return -EBADMSG; + + } + + n->rindex = sizeof(struct ether_header); + + return 0; +} + +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) { + assert(n); + + if (n->ttl > 0) { + usec_t base; + + /* Use the packet's timestamp if there is one known */ + base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic()); + if (base <= 0 || base == USEC_INFINITY) + base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */ + + n->until = usec_add(base, n->ttl * USEC_PER_SEC); + } else + n->until = 0; + + if (n->lldp) + prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx); +} + +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) { + if (a == b) + return true; + + if (!a || !b) + return false; + + if (a->raw_size != b->raw_size) + return false; + + return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0; +} + +_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->source_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->destination_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + *ret = LLDP_NEIGHBOR_RAW(n); + *size = n->raw_size; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.chassis_id_size > 0); + + *type = *(uint8_t*) n->id.chassis_id; + *ret = (uint8_t*) n->id.chassis_id + 1; + *size = n->id.chassis_id_size - 1; + + return 0; +} + +static int format_mac_address(const void *data, size_t sz, char **ret) { + struct ether_addr a; + char *k; + + assert(data || sz <= 0); + + if (sz != 7) + return 0; + + memcpy(&a, (uint8_t*) data + 1, sizeof(a)); + + k = new(char, ETHER_ADDR_TO_STRING_MAX); + if (!k) + return -ENOMEM; + + *ret = ether_addr_to_string(&a, k); + return 1; +} + +static int format_network_address(const void *data, size_t sz, char **ret) { + union in_addr_union a; + int family, r; + + if (sz == 6 && ((uint8_t*) data)[1] == 1) { + memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in)); + family = AF_INET; + } else if (sz == 18 && ((uint8_t*) data)[1] == 2) { + memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6)); + family = AF_INET6; + } else + return 0; + + r = in_addr_to_string(family, &a, ret); + if (r < 0) + return r; + return 1; +} + +_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->chassis_id_as_string) { + *ret = n->chassis_id_as_string; + return 0; + } + + assert(n->id.chassis_id_size > 0); + + switch (*(uint8_t*) n->id.chassis_id) { + + case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.chassis_id, n->id.chassis_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->chassis_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.port_id_size > 0); + + *type = *(uint8_t*) n->id.port_id; + *ret = (uint8_t*) n->id.port_id + 1; + *size = n->id.port_id_size - 1; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->port_id_as_string) { + *ret = n->port_id_as_string; + return 0; + } + + assert(n->id.port_id_size > 0); + + switch (*(uint8_t*) n->id.port_id) { + + case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.port_id, n->id.port_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->port_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) { + assert_return(n, -EINVAL); + assert_return(ret_sec, -EINVAL); + + *ret_sec = n->ttl; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_name) + return -ENODATA; + + *ret = n->system_name; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_description) + return -ENODATA; + + *ret = n->system_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->port_description) + return -ENODATA; + + *ret = n->port_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->system_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->enabled_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(raw || raw_size <= 0, -EINVAL); + + n = lldp_neighbor_new(raw_size); + if (!n) + return -ENOMEM; + + memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size); + r = lldp_neighbor_parse(n); + if (r < 0) + return r; + + *ret = n; + n = NULL; + + return r; +} + +_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { + assert_return(n, -EINVAL); + + assert(n->raw_size >= sizeof(struct ether_header)); + n->rindex = sizeof(struct ether_header); + + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { + size_t length; + + assert_return(n, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) /* Truncated message */ + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + n->rindex += 2 + length; + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + *type = LLDP_NEIGHBOR_TLV_TYPE(n); + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) { + uint8_t k; + int r; + + assert_return(n, -EINVAL); + + r = sd_lldp_neighbor_tlv_get_type(n, &k); + if (r < 0) + return r; + + return type == k; +} + +_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype) { + const uint8_t *d; + size_t length; + int r; + + assert_return(n, -EINVAL); + assert_return(oui, -EINVAL); + assert_return(subtype, -EINVAL); + + r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (length < 4) + return -EBADMSG; + + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + d = LLDP_NEIGHBOR_TLV_DATA(n); + memcpy(oui, d, 3); + *subtype = d[3]; + + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype) { + uint8_t k[3], st; + int r; + + r = sd_lldp_neighbor_tlv_get_oui(n, k, &st); + if (r == -ENXIO) + return 0; + if (r < 0) + return r; + + return memcmp(k, oui, 3) == 0 && st == subtype; +} + +_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + size_t length; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + /* Note that this returns the full TLV, including the TLV header */ + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + *size = length + 2; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) { + assert_return(n, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&n->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&n->timestamp, clock); + return 0; +} diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h new file mode 100644 index 0000000000..c1a7606d06 --- /dev/null +++ b/src/libsystemd-network/lldp-neighbor.h @@ -0,0 +1,108 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + 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 <inttypes.h> +#include <stdbool.h> +#include <sys/types.h> + +#include "sd-lldp.h" + +#include "hash-funcs.h" +#include "lldp-internal.h" +#include "time-util.h" + +typedef struct LLDPNeighborID { + /* The spec calls this an "MSAP identifier" */ + void *chassis_id; + size_t chassis_id_size; + + void *port_id; + size_t port_id_size; +} LLDPNeighborID; + +struct sd_lldp_neighbor { + /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */ + sd_lldp *lldp; + unsigned n_ref; + + triple_timestamp timestamp; + + usec_t until; + unsigned prioq_idx; + + struct ether_addr source_address; + struct ether_addr destination_address; + + LLDPNeighborID id; + + /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */ + size_t raw_size; + + /* The current read index for the iterative TLV interface */ + size_t rindex; + + /* And a couple of fields parsed out. */ + bool has_ttl:1; + bool has_capabilities:1; + bool has_port_vlan_id:1; + + uint16_t ttl; + + uint16_t system_capabilities; + uint16_t enabled_capabilities; + + char *port_description; + char *system_name; + char *system_description; + + uint16_t port_vlan_id; + + char *chassis_id_as_string; + char *port_id_as_string; +}; + +static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) { + return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor)); +} + +static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1; +} + +static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) { + uint8_t *p; + + p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + return p[1] + (((size_t) (p[0] & 1)) << 8); +} + +static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; +} + +extern const struct hash_ops lldp_neighbor_id_hash_ops; +int lldp_neighbor_prioq_compare_func(const void *a, const void *b); + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n); +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); +int lldp_neighbor_parse(sd_lldp_neighbor *n); +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c index 664d2f7867..59c25598e9 100644 --- a/src/libsystemd-network/lldp-network.c +++ b/src/libsystemd-network/lldp-network.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -21,91 +19,59 @@ ***/ #include <linux/filter.h> -#include <linux/if_ether.h> +#include <netinet/if_ether.h> -#include "socket-util.h" -#include "lldp-tlv.h" +#include "fd-util.h" #include "lldp-network.h" -#include "lldp-internal.h" +#include "socket-util.h" int lldp_network_bind_raw_socket(int ifindex) { - typedef struct LLDPFrame { - struct ethhdr hdr; - uint8_t tlvs[0]; - } LLDPFrame; - struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(LLDPFrame, hdr.h_dest)), /* A <- 4 bytes of destination MAC */ + static const struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(LLDPFrame, hdr.h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(LLDPFrame, hdr.h_proto)), /* A <- protocol */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */ }; - struct sock_fprog fprog = { + static const struct sock_fprog fprog = { .len = ELEMENTSOF(filter), - .filter = filter + .filter = (struct sock_filter*) filter, }; - _cleanup_close_ int s = -1; - union sockaddr_union saddrll = { .ll.sll_family = AF_PACKET, .ll.sll_ifindex = ifindex, }; + _cleanup_close_ int fd = -1; int r; assert(ifindex > 0); - s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); - if (s < 0) + fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, + htobe16(ETHERTYPE_LLDP)); + if (fd < 0) return -errno; - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); if (r < 0) return -errno; - r = bind(s, &saddrll.sa, sizeof(saddrll.ll)); + r = bind(fd, &saddrll.sa, sizeof(saddrll.ll)); if (r < 0) return -errno; - r = s; - s = -1; + r = fd; + fd = -1; return r; } - -int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_tlv_packet_free_ tlv_packet *packet = NULL; - tlv_packet *p; - uint16_t length; - int r; - - assert(fd); - assert(userdata); - - r = tlv_packet_new(&packet); - if (r < 0) - return r; - - length = read(fd, &packet->pdu, sizeof(packet->pdu)); - - /* Silently drop the packet */ - if ((size_t) length > ETHER_MAX_LEN) - return 0; - - packet->userdata = userdata; - - p = packet; - packet = NULL; - - return lldp_handle_packet(p, (uint16_t) length); -} diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h index b7f8d3bf80..c4cf8c79f1 100644 --- a/src/libsystemd-network/lldp-network.h +++ b/src/libsystemd-network/lldp-network.h @@ -1,4 +1,4 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ +#pragma once /*** This file is part of systemd. @@ -20,9 +20,6 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#pragma once - #include "sd-event.h" int lldp_network_bind_raw_socket(int ifindex); -int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata); diff --git a/src/libsystemd-network/lldp-port.c b/src/libsystemd-network/lldp-port.c deleted file mode 100644 index aa6a3b9224..0000000000 --- a/src/libsystemd-network/lldp-port.c +++ /dev/null @@ -1,116 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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 "async.h" -#include "lldp-port.h" -#include "lldp-network.h" - -int lldp_port_start(lldp_port *p) { - int r; - - assert_return(p, -EINVAL); - - r = lldp_network_bind_raw_socket(p->ifindex); - if (r < 0) - return r; - - p->rawfd = r; - - r = sd_event_add_io(p->event, &p->lldp_port_rx, - p->rawfd, EPOLLIN, lldp_receive_packet, p); - if (r < 0) { - log_debug("Failed to allocate event source: %s", strerror(-r)); - return r; - } - - r = sd_event_source_set_priority(p->lldp_port_rx, p->event_priority); - if (r < 0) { - log_debug("Failed to set event priority: %s", strerror(-r)); - goto fail; - } - - r = sd_event_source_set_description(p->lldp_port_rx, "lldp-port-rx"); - if (r < 0) { - log_debug("Failed to set event name: %s", strerror(-r)); - goto fail; - } - - return 0; - -fail: - lldp_port_stop(p); - - return r; -} - -int lldp_port_stop(lldp_port *p) { - - assert_return(p, -EINVAL); - - p->rawfd = asynchronous_close(p->rawfd); - p->lldp_port_rx = sd_event_source_unref(p->lldp_port_rx); - - return 0; -} - -void lldp_port_free(lldp_port *p) { - if (!p) - return; - - lldp_port_stop(p); - - free(p->ifname); - free(p); -} - -int lldp_port_new(int ifindex, - const char *ifname, - const struct ether_addr *addr, - void *userdata, - lldp_port **ret) { - _cleanup_free_ lldp_port *p = NULL; - - assert_return(ifindex, -EINVAL); - assert_return(ifname, -EINVAL); - assert_return(addr, -EINVAL); - - p = new0(lldp_port, 1); - if (!p) - return -ENOMEM; - - p->rawfd = -1; - p->ifindex = ifindex; - - p->ifname = strdup(ifname); - if (!p->ifname) - return -ENOMEM; - - memcpy(&p->mac, addr, ETH_ALEN); - - p->userdata = userdata; - - *ret = p; - - p = NULL; - - return 0; -} diff --git a/src/libsystemd-network/lldp-port.h b/src/libsystemd-network/lldp-port.h deleted file mode 100644 index b2d3180091..0000000000 --- a/src/libsystemd-network/lldp-port.h +++ /dev/null @@ -1,63 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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/>. -***/ - -#pragma once - -#include <net/ethernet.h> - -#include "sd-event.h" -#include "sd-lldp.h" - -#include "util.h" - -typedef struct lldp_port lldp_port; - -struct lldp_port { - LLDPPortStatus status; - - int ifindex; - char *ifname; - - struct ether_addr mac; - - int rawfd; - - sd_event *event; - sd_event_source *lldp_port_rx; - - int event_priority; - - void *userdata; -}; - -int lldp_port_new(int ifindex, - const char *ifname, - const struct ether_addr *addr, - void *userdata, - lldp_port **ret); -void lldp_port_free(lldp_port *p); - -DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_port*, lldp_port_free); -#define _cleanup_lldp_port_free_ _cleanup_(lldp_port_freep) - -int lldp_port_start(lldp_port *p); -int lldp_port_stop(lldp_port *p); diff --git a/src/libsystemd-network/lldp-tlv.c b/src/libsystemd-network/lldp-tlv.c deleted file mode 100644 index 0cea5b10a6..0000000000 --- a/src/libsystemd-network/lldp-tlv.c +++ /dev/null @@ -1,321 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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 <net/ethernet.h> -#include <arpa/inet.h> - -#include "macro.h" -#include "lldp-tlv.h" - -int tlv_section_new(tlv_section **ret) { - tlv_section *s; - - s = new0(tlv_section, 1); - if (!s) - return -ENOMEM; - - *ret = s; - - return 0; -} - -void tlv_section_free(tlv_section *m) { - - if (!m) - return; - - free(m); -} - -int tlv_packet_new(tlv_packet **ret) { - tlv_packet *m; - - m = new0(tlv_packet, 1); - if (!m) - return -ENOMEM; - - LIST_HEAD_INIT(m->sections); - - *ret = m; - - return 0; -} - -void tlv_packet_free(tlv_packet *m) { - tlv_section *s, *n; - - if (!m) - return; - - LIST_FOREACH_SAFE(section, s, n, m->sections) - tlv_section_free(s); - - free(m); -} - -int tlv_packet_append_bytes(tlv_packet *m, const void *data, size_t data_length) { - uint8_t *p; - - assert_return(m, -EINVAL); - assert_return(data, -EINVAL); - assert_return(data_length, -EINVAL); - - if (m->length + data_length > ETHER_MAX_LEN) - return -ENOMEM; - - p = m->pdu + m->length; - memcpy(p, data, data_length); - m->length += data_length; - - return 0; -} - -int tlv_packet_append_u8(tlv_packet *m, uint8_t data) { - - assert_return(m, -EINVAL); - - return tlv_packet_append_bytes(m, &data, sizeof(uint8_t)); -} - -int tlv_packet_append_u16(tlv_packet *m, uint16_t data) { - uint16_t type; - - assert_return(m, -EINVAL); - - type = htons(data); - - return tlv_packet_append_bytes(m, &type, sizeof(uint16_t)); -} - -int tlv_packet_append_u32(tlv_packet *m, uint32_t data) { - uint32_t type; - - assert_return(m, -EINVAL); - - type = htonl(data); - - return tlv_packet_append_bytes(m, &type, sizeof(uint32_t)); -} - -int tlv_packet_append_string(tlv_packet *m, char *data, uint16_t size) { - - assert_return(m, -EINVAL); - - return tlv_packet_append_bytes(m, data, size); -} - -int lldp_tlv_packet_open_container(tlv_packet *m, uint16_t type) { - - assert_return(m, -EINVAL); - - m->container_pos = m->pdu + m->length; - - return tlv_packet_append_u16(m, type << 9); -} - -int lldp_tlv_packet_close_container(tlv_packet *m) { - uint16_t type; - - assert_return(m, -EINVAL); - assert_return(m->container_pos, -EINVAL); - - memcpy(&type, m->container_pos, sizeof(uint16_t)); - - type |= htons(((m->pdu + m->length) - (m->container_pos + 2)) & 0x01ff); - memcpy(m->container_pos, &type, sizeof(uint16_t)); - - return 0; -} - -static inline int tlv_packet_read_internal(tlv_section *m, void **data) { - - assert_return(m->read_pos, -EINVAL); - - *data = m->read_pos; - - return 0; -} - -int tlv_packet_read_u8(tlv_packet *m, uint8_t *data) { - void *val = NULL; - int r; - - assert_return(m, -EINVAL); - - r = tlv_packet_read_internal(m->container, &val); - if (r < 0) - return r; - - memcpy(data, val, sizeof(uint8_t)); - - m->container->read_pos ++; - - return 0; -} - -int tlv_packet_read_u16(tlv_packet *m, uint16_t *data) { - uint16_t t; - void *val = NULL; - int r; - - assert_return(m, -EINVAL); - - r = tlv_packet_read_internal(m->container, &val); - if (r < 0) - return r; - - memcpy(&t, val, sizeof(uint16_t)); - *data = ntohs(t); - - m->container->read_pos += 2; - - return 0; -} - -int tlv_packet_read_u32(tlv_packet *m, uint32_t *data) { - uint32_t t; - void *val; - int r; - - assert_return(m, -EINVAL); - - r = tlv_packet_read_internal(m->container, &val); - if (r < 0) - return r; - - memcpy(&t, val, sizeof(uint32_t)); - *data = ntohl(t); - - m->container->read_pos += 4; - - return r; -} - -int tlv_packet_read_string(tlv_packet *m, char **data, uint16_t *data_length) { - void *val = NULL; - int r; - - assert_return(m, -EINVAL); - - r = tlv_packet_read_internal(m->container, &val); - if (r < 0) - return r; - - *data = (char *) val; - *data_length = m->container->length; - - m->container->read_pos += m->container->length; - - return 0; -} - -int tlv_packet_read_bytes(tlv_packet *m, uint8_t **data, uint16_t *data_length) { - void *val = NULL; - int r; - - assert_return(m, -EINVAL); - - r = tlv_packet_read_internal(m->container, &val); - if (r < 0) - return r; - - *data = (uint8_t *) val; - *data_length = m->container->length; - - m->container->read_pos += m->container->length; - - return 0; -} - -/* parse raw TLV packet */ -int tlv_packet_parse_pdu(tlv_packet *m, uint16_t size) { - tlv_section *section, *tail; - uint16_t t, l; - uint8_t *p; - int r; - - assert_return(m, -EINVAL); - assert_return(size, -EINVAL); - - p = m->pdu; - - /* extract ethernet herader */ - memcpy(&m->mac, p, ETH_ALEN); - p += sizeof(struct ether_header); - - for (l = 0; l <= size; ) { - r = tlv_section_new(§ion); - if (r < 0) - return r; - - memcpy(&t, p, sizeof(uint16_t)); - - section->type = ntohs(t) >> 9; - section->length = ntohs(t) & 0x01ff; - - if (section->type == LLDP_TYPE_END || section->type >=_LLDP_TYPE_MAX) { - tlv_section_free(section); - break; - } - - p += 2; - section->data = p; - - LIST_FIND_TAIL(section, m->sections, tail); - LIST_INSERT_AFTER(section, m->sections, tail, section); - - p += section->length; - l += (section->length + 2); - } - - return 0; -} - -int lldp_tlv_packet_enter_container(tlv_packet *m, uint16_t type) { - tlv_section *s; - - assert_return(m, -EINVAL); - - LIST_FOREACH(section, s, m->sections) - if (s->type == type) - break; - if (!s) - return -1; - - m->container = s; - - m->container->read_pos = s->data; - if (!m->container->read_pos) { - m->container = 0; - return -1; - } - - return 0; -} - -int lldp_tlv_packet_exit_container(tlv_packet *m) { - assert_return(m, -EINVAL); - - m->container = 0; - - return 0; -} diff --git a/src/libsystemd-network/lldp-tlv.h b/src/libsystemd-network/lldp-tlv.h deleted file mode 100644 index ce3334e115..0000000000 --- a/src/libsystemd-network/lldp-tlv.h +++ /dev/null @@ -1,87 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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/>. -***/ - -#pragma once - -#include <net/ethernet.h> - -#include "util.h" -#include "lldp.h" -#include "list.h" - -typedef struct tlv_packet tlv_packet; -typedef struct tlv_section tlv_section; - -struct tlv_section { - uint16_t type; - uint16_t length; - - uint8_t *read_pos; - uint8_t *data; - - LIST_FIELDS(tlv_section, section); -}; - -int tlv_section_new(tlv_section **ret); -void tlv_section_free(tlv_section *ret); - -struct tlv_packet { - uint16_t type; - uint16_t length; - usec_t ts; - - uint8_t *container_pos; - uint8_t pdu[ETHER_MAX_LEN]; - - void *userdata; - - struct ether_addr mac; - tlv_section *container; - - LIST_HEAD(tlv_section, sections); -}; - -int tlv_packet_new(tlv_packet **ret); -void tlv_packet_free(tlv_packet *m); - -DEFINE_TRIVIAL_CLEANUP_FUNC(tlv_packet*, tlv_packet_free); -#define _cleanup_tlv_packet_free_ _cleanup_(tlv_packet_freep) - -int lldp_tlv_packet_open_container(tlv_packet *m, uint16_t type); -int lldp_tlv_packet_close_container(tlv_packet *m); - -int tlv_packet_append_bytes(tlv_packet *m, const void *data, size_t data_length); -int tlv_packet_append_u8(tlv_packet *m, uint8_t data); -int tlv_packet_append_u16(tlv_packet *m, uint16_t data); -int tlv_packet_append_u32(tlv_packet *m, uint32_t data); -int tlv_packet_append_string(tlv_packet *m, char *data, uint16_t size); - -int lldp_tlv_packet_enter_container(tlv_packet *m, uint16_t type); -int lldp_tlv_packet_exit_container(tlv_packet *m); - -int tlv_packet_read_bytes(tlv_packet *m, uint8_t **data, uint16_t *data_length); -int tlv_packet_read_string(tlv_packet *m, char **data, uint16_t *data_length); -int tlv_packet_read_u8(tlv_packet *m, uint8_t *data); -int tlv_packet_read_u16(tlv_packet *m, uint16_t *data); -int tlv_packet_read_u32(tlv_packet *m, uint32_t *data); - -int tlv_packet_parse_pdu(tlv_packet *t, uint16_t size); diff --git a/src/libsystemd-network/lldp.h b/src/libsystemd-network/lldp.h deleted file mode 100644 index 5e4b283e26..0000000000 --- a/src/libsystemd-network/lldp.h +++ /dev/null @@ -1,115 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani - - 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/>. -***/ - -#pragma once - -#define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e } - -#define ETHERTYPE_LLDP 0x88cc - -/* IEEE 802.3AB Clause 9: TLV Types */ -typedef enum LLDPTypes { - LLDP_TYPE_END = 0, - LLDP_TYPE_CHASSIS_ID = 1, - LLDP_TYPE_PORT_ID = 2, - LLDP_TYPE_TTL = 3, - LLDP_TYPE_PORT_DESCRIPTION = 4, - LLDP_TYPE_SYSTEM_NAME = 5, - LLDP_TYPE_SYSTEM_DESCRIPTION = 6, - LLDP_TYPE_SYSTEM_CAPABILITIES = 7, - LLDP_TYPE_MGMT_ADDRESS = 8, - LLDP_TYPE_PRIVATE = 127, - _LLDP_TYPE_MAX, - _LLDP_TYPE_INVALID = -1, -} LLDPTypes; - -/* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */ -typedef enum LLDPChassisSubtypes { - LLDP_CHASSIS_SUBTYPE_RESERVED = 0, - LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1, - LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2, - LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3, - LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4, - LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5, - LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6, - LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7, - _LLDP_CHASSIS_SUBTYPE_MAX, - _LLDP_CHASSIS_SUBTYPE_INVALID = -1, -} LLDPChassisSubtypes; - -/* IEEE 802.3AB Clause 9.5.3: Port subtype */ -typedef enum LLDPPortSubtypes { - LLDP_PORT_SUBTYPE_RESERVED = 0, - LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1, - LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2, - LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3, - LLDP_PORT_SUBTYPE_NETWORK = 4, - LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5, - LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6, - LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7, - _LLDP_PORT_SUBTYPE_MAX, - _LLDP_PORT_SUBTYPE_INVALID = -1 -} LLDPPortSubtypes; - -typedef enum LLDPSystemCapabilities { - LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0, - LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1, - LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2, - LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3, - LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4, - LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5, - LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6, - LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7, - LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8, - LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9, - LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10, - _LLDP_SYSTEM_CAPABILITIES_MAX, - _LLDP_SYSTEM_CAPABILITIES_INVALID = -1, -} LLDPSystemCapabilities; - -typedef enum LLDPMedSubtype { - LLDP_MED_SUBTYPE_RESERVED = 0, - LLDP_MED_SUBTYPE_CAPABILITIES = 1, - LLDP_MED_SUBTYPE_NETWORK_POLICY = 2, - LLDP_MED_SUBTYPE_LOCATION_ID = 3, - LLDP_MED_SUBTYPE_EXTENDED_PVMDI = 4, - LLDP_MED_SUBTYPE_INV_HWREV = 5, - LLDP_MED_SUBTYPE_INV_FWREV = 6, - LLDP_MED_SUBTYPE_INV_SWREV = 7, - LLDP_MED_SUBTYPE_INV_SERIAL = 8, - LLDP_MED_SUBTYPE_INV_MANUFACTURER = 9, - LLDP_MED_SUBTYPE_INV_MODELNAME = 10, - LLDP_MED_SUBTYPE_INV_ASSETID = 11, - _LLDP_MED_SUBTYPE_MAX, - _LLDP_MED_SUBTYPE_INVALID = -1, -} LLDPMedSubtype; - -typedef enum LLDPMedCapability { - LLDP_MED_CAPABILITY_CAPAPILITIES = 1 << 0, - LLDP_MED_CAPABILITY_NETWORK_POLICY = 1 << 1, - LLDP_MED_CAPABILITY_LOCATION_ID = 1 << 2, - LLDP_MED_CAPABILITY_EXTENDED_PSE = 1 << 3, - LLDP_MED_CAPABILITY_EXTENDED_PD = 1 << 4, - LLDP_MED_CAPABILITY_INVENTORY = 1 << 5, - LLDP_MED_CAPABILITY_MAX, - LLDP_MED_CAPABILITY_INVALID = -1, -} LLDPMedCapability; diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h new file mode 100644 index 0000000000..60e183ff8c --- /dev/null +++ b/src/libsystemd-network/ndisc-internal.h @@ -0,0 +1,49 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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 "log.h" + +#include "sd-ndisc.h" + +struct sd_ndisc { + unsigned n_ref; + + int ifindex; + int fd; + + sd_event *event; + int event_priority; + + struct ether_addr mac_addr; + uint8_t hop_limit; + uint32_t mtu; + + sd_event_source *recv_event_source; + sd_event_source *timeout_event_source; + + unsigned nd_sent; + + sd_ndisc_callback_t callback; + void *userdata; +}; + +#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__) +#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c new file mode 100644 index 0000000000..d9950b638c --- /dev/null +++ b/src/libsystemd-network/ndisc-router.c @@ -0,0 +1,779 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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/icmp6.h> + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "dns-domain.h" +#include "hostname-util.h" +#include "missing.h" +#include "ndisc-internal.h" +#include "ndisc-router.h" +#include "strv.h" + +_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) { + if (!rt) + return NULL; + + assert(rt->n_ref > 0); + rt->n_ref++; + + return rt; +} + +_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) { + if (!rt) + return NULL; + + assert(rt->n_ref > 0); + rt->n_ref--; + + if (rt->n_ref > 0) + return NULL; + + free(rt); + return NULL; +} + +sd_ndisc_router *ndisc_router_new(size_t raw_size) { + sd_ndisc_router *rt; + + rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size); + if (!rt) + return NULL; + + rt->raw_size = raw_size; + rt->n_ref = 1; + + return rt; +} + +_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(raw || raw_size <= 0, -EINVAL); + + rt = ndisc_router_new(raw_size); + if (!rt) + return -ENOMEM; + + memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size); + r = ndisc_router_parse(rt); + if (r < 0) + return r; + + *ret = rt; + rt = NULL; + + return r; +} + +_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + if (in6_addr_is_null(&rt->address)) + return -ENODATA; + + *ret_addr = rt->address; + return 0; +} + +_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&rt->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&rt->timestamp, clock); + return 0; +} + +_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + *ret = NDISC_ROUTER_RAW(rt); + *size = rt->raw_size; + + return 0; +} + +int ndisc_router_parse(sd_ndisc_router *rt) { + struct nd_router_advert *a; + const uint8_t *p; + bool has_mtu = false, has_flag_extension = false; + size_t left; + + assert(rt); + + if (rt->raw_size < sizeof(struct nd_router_advert)) { + log_ndisc("Too small to be a router advertisement, ignoring."); + return -EBADMSG; + } + + /* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */ + a = NDISC_ROUTER_RAW(rt); + + if (a->nd_ra_type != ND_ROUTER_ADVERT) { + log_ndisc("Received ND packet that is not a router advertisement, ignoring."); + return -EBADMSG; + } + + if (a->nd_ra_code != 0) { + log_ndisc("Received ND packet with wrong RA code, ignoring."); + return -EBADMSG; + } + + rt->hop_limit = a->nd_ra_curhoplimit; + rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */ + rt->lifetime = be16toh(a->nd_ra_router_lifetime); + + rt->preference = (rt->flags >> 3) & 3; + if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) + rt->preference = SD_NDISC_PREFERENCE_MEDIUM; + + p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert); + left = rt->raw_size - sizeof(struct nd_router_advert); + + for (;;) { + uint8_t type; + size_t length; + + if (left == 0) + break; + + if (left < 2) { + log_ndisc("Option lacks header, ignoring datagram."); + return -EBADMSG; + } + + type = p[0]; + length = p[1] * 8; + + if (length == 0) { + log_ndisc("Zero-length option, ignoring datagram."); + return -EBADMSG; + } + if (left < length) { + log_ndisc("Option truncated, ignoring datagram."); + return -EBADMSG; + } + + switch (type) { + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + + if (length != 4*8) { + log_ndisc("Prefix option of invalid size, ignoring datagram."); + return -EBADMSG; + } + + if (p[2] > 128) { + log_ndisc("Bad prefix length, ignoring datagram."); + return -EBADMSG; + } + + break; + + case SD_NDISC_OPTION_MTU: { + uint32_t m; + + if (has_mtu) { + log_ndisc("MTU option specified twice, ignoring."); + continue; + } + + if (length != 8) { + log_ndisc("MTU option of invalid size, ignoring datagram."); + return -EBADMSG; + } + + m = be32toh(*(uint32_t*) (p + 4)); + if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */ + rt->mtu = m; + + has_mtu = true; + break; + } + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + if (length < 1*8 || length > 3*8) { + log_ndisc("Route information option of invalid size, ignoring datagram."); + return -EBADMSG; + } + + if (p[2] > 128) { + log_ndisc("Bad route prefix length, ignoring datagram."); + return -EBADMSG; + } + + break; + + case SD_NDISC_OPTION_RDNSS: + if (length < 3*8 || (length % (2*8)) != 1*8) { + log_ndisc("RDNSS option has invalid size."); + return -EBADMSG; + } + + break; + + case SD_NDISC_OPTION_FLAGS_EXTENSION: + + if (has_flag_extension) { + log_ndisc("Flags extension option specified twice, ignoring."); + continue; + } + + if (length < 1*8) { + log_ndisc("Flags extension option has invalid size."); + return -EBADMSG; + } + + /* Add in the additional flags bits */ + rt->flags |= + ((uint64_t) p[2] << 8) | + ((uint64_t) p[3] << 16) | + ((uint64_t) p[4] << 24) | + ((uint64_t) p[5] << 32) | + ((uint64_t) p[6] << 40) | + ((uint64_t) p[7] << 48); + + has_flag_extension = true; + break; + + case SD_NDISC_OPTION_DNSSL: + if (length < 2*8) { + log_ndisc("DNSSL option has invalid size."); + return -EBADMSG; + } + + break; + } + + p += length, left -= length; + } + + rt->rindex = sizeof(struct nd_router_advert); + return 0; +} + +_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->hop_limit; + return 0; +} + +_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) { + assert_return(rt, -EINVAL); + assert_return(ret_flags, -EINVAL); + + *ret_flags = rt->flags; + return 0; +} + +_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) { + assert_return(rt, -EINVAL); + assert_return(ret_lifetime, -EINVAL); + + *ret_lifetime = rt->lifetime; + return 0; +} + +_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->preference; + return 0; +} + +_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + if (rt->mtu <= 0) + return -ENODATA; + + *ret = rt->mtu; + return 0; +} + +_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { + assert_return(rt, -EINVAL); + + assert(rt->raw_size >= sizeof(struct nd_router_advert)); + rt->rindex = sizeof(struct nd_router_advert); + + return rt->rindex < rt->raw_size; +} + +_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) { + size_t length; + + assert_return(rt, -EINVAL); + + if (rt->rindex == rt->raw_size) /* EOF */ + return -ESPIPE; + + if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ + return -EBADMSG; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (rt->rindex + length > rt->raw_size) + return -EBADMSG; + + rt->rindex += length; + return rt->rindex < rt->raw_size; +} + +_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + if (rt->rindex == rt->raw_size) /* EOF */ + return -ESPIPE; + + if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ + return -EBADMSG; + + *ret = NDISC_ROUTER_OPTION_TYPE(rt); + return 0; +} + +_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { + uint8_t k; + int r; + + assert_return(rt, -EINVAL); + + r = sd_ndisc_router_option_get_type(rt, &k); + if (r < 0) + return r; + + return type == k; +} + +_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) { + size_t length; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + /* Note that this returns the full option, including the option header */ + + if (rt->rindex + 2 > rt->raw_size) + return -EBADMSG; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (rt->rindex + length > rt->raw_size) + return -EBADMSG; + + *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + *size = length; + + return 0; +} + +static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) { + struct nd_opt_prefix_info *ri; + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length != sizeof(struct nd_opt_prefix_info)) + return -EBADMSG; + + ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); + if (ri->nd_opt_pi_prefix_len > 128) + return -EBADMSG; + + *ret = ri; + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + struct nd_opt_prefix_info *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &ri); + if (r < 0) + return r; + + *ret = be32toh(ri->nd_opt_pi_valid_time); + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + *ret = be32toh(pi->nd_opt_pi_preferred_time); + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + *ret = pi->nd_opt_pi_flags_reserved; + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + *ret_addr = pi->nd_opt_pi_prefix; + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + if (pi->nd_opt_pi_prefix_len > 128) + return -EBADMSG; + + *ret = pi->nd_opt_pi_prefix_len; + return 0; +} + +static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) { + uint8_t *ri; + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length < 1*8 || length > 3*8) + return -EBADMSG; + + ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + + if (ri[2] > 128) + return -EBADMSG; + + *ret = ri; + return 0; +} + +_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + *ret = be32toh(*(uint32_t*) (ri + 4)); + return 0; +} + +_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + zero(*ret_addr); + memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8); + + return 0; +} + +_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + *ret = ri[2]; + return 0; +} + +_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + *ret = (ri[3] >> 3) & 3; + if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) + *ret = SD_NDISC_PREFERENCE_MEDIUM; + + return 0; +} + +static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) { + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length < 3*8 || (length % (2*8)) != 1*8) + return -EBADMSG; + + *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + return 0; +} + +_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_rdnss_info(rt, &ri); + if (r < 0) + return r; + + *ret = (const struct in6_addr*) (ri + 8); + return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16; +} + +_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_rdnss_info(rt, &ri); + if (r < 0) + return r; + + *ret = be32toh(*(uint32_t*) (ri + 4)); + return 0; +} + +static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) { + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length < 2*8) + return -EBADMSG; + + *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + return 0; +} + +_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *e = NULL; + size_t allocated = 0, n = 0, left; + uint8_t *ri, *p; + bool first = true; + int r; + unsigned k = 0; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_dnssl_info(rt, &ri); + if (r < 0) + return r; + + p = ri + 8; + left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8; + + for (;;) { + if (left == 0) { + + if (n > 0) /* Not properly NUL terminated */ + return -EBADMSG; + + break; + } + + if (*p == 0) { + /* Found NUL termination */ + + if (n > 0) { + _cleanup_free_ char *normalized = NULL; + + e[n] = 0; + r = dns_name_normalize(e, &normalized); + if (r < 0) + return r; + + /* Ignore the root domain name or "localhost" and friends */ + if (!is_localhost(normalized) && + !dns_name_is_root(normalized)) { + + if (strv_push(&l, normalized) < 0) + return -ENOMEM; + + normalized = NULL; + k++; + } + } + + n = 0; + first = true; + p++, left--; + continue; + } + + /* Check for compression (which is not allowed) */ + if (*p > 63) + return -EBADMSG; + + if (1U + *p + 1U > left) + return -EBADMSG; + + if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U)) + return -ENOMEM; + + if (first) + first = false; + else + e[n++] = '.'; + + r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + + left -= 1 + *p; + p += 1 + *p; + } + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + *ret = l; + l = NULL; + + return k; +} + +_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_sec, -EINVAL); + + r = get_dnssl_info(rt, &ri); + if (r < 0) + return r; + + *ret_sec = be32toh(*(uint32_t*) (ri + 4)); + return 0; +} diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h new file mode 100644 index 0000000000..1fe703da63 --- /dev/null +++ b/src/libsystemd-network/ndisc-router.h @@ -0,0 +1,62 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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-ndisc.h" + +#include "time-util.h" + +struct sd_ndisc_router { + unsigned n_ref; + + triple_timestamp timestamp; + struct in6_addr address; + + /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */ + size_t raw_size; + + /* The current read index for the iterative option interface */ + size_t rindex; + + uint64_t flags; + unsigned preference; + uint16_t lifetime; + + uint8_t hop_limit; + uint32_t mtu; +}; + +static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) { + return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router)); +} + +static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) { + return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex; +} + +static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) { + return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0]; +} +static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) { + return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8; +} + +sd_ndisc_router *ndisc_router_new(size_t raw_size); +int ndisc_router_parse(sd_ndisc_router *rt); diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c index d579755cc8..9d78b953fc 100644 --- a/src/libsystemd-network/network-internal.c +++ b/src/libsystemd-network/network-internal.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -19,19 +17,27 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <netinet/ether.h> -#include <linux/if.h> #include <arpa/inet.h> +#include <linux/if.h> +#include <netinet/ether.h> -#include "strv.h" -#include "siphash24.h" +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "condition.h" +#include "conf-parser.h" #include "dhcp-lease-internal.h" +#include "ether-addr-util.h" +#include "hexdecoct.h" #include "log.h" +#include "network-internal.h" +#include "parse-util.h" +#include "siphash24.h" +#include "socket-util.h" +#include "string-util.h" +#include "strv.h" #include "utf8.h" #include "util.h" -#include "conf-parser.h" -#include "condition.h" -#include "network-internal.h" const char *net_get_name(struct udev_device *device) { const char *name, *field; @@ -50,7 +56,7 @@ const char *net_get_name(struct udev_device *device) { #define HASH_KEY SD_ID128_MAKE(d3,1e,48,fa,90,fe,4b,4c,9d,af,d5,d7,a1,b1,2e,8a) -int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]) { +int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result) { size_t l, sz = 0; const char *name = NULL; int r; @@ -75,7 +81,7 @@ int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8 /* Let's hash the machine ID plus the device name. We * use a fixed, but originally randomly created hash * key here. */ - siphash24(result, v, sz, HASH_KEY.bytes); + *result = htole64(siphash24(v, sz, HASH_KEY.bytes)); return 0; } @@ -96,16 +102,16 @@ bool net_match_config(const struct ether_addr *match_mac, const char *dev_type, const char *dev_name) { - if (match_host && !condition_test(match_host)) + if (match_host && condition_test(match_host) <= 0) return false; - if (match_virt && !condition_test(match_virt)) + if (match_virt && condition_test(match_virt) <= 0) return false; - if (match_kernel && !condition_test(match_kernel)) + if (match_kernel && condition_test(match_kernel) <= 0) return false; - if (match_arch && !condition_test(match_arch)) + if (match_arch && condition_test(match_arch) <= 0) return false; if (match_mac && (!dev_mac || memcmp(match_mac, dev_mac, ETH_ALEN))) @@ -171,59 +177,19 @@ int config_parse_net_condition(const char *unit, return 0; } -int config_parse_ifname(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - - char **s = data; - _cleanup_free_ char *n = NULL; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - n = strdup(rvalue); - if (!n) - return log_oom(); - - if (!ascii_is_valid(n) || strlen(n) >= IFNAMSIZ) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue); - return 0; - } - - free(*s); - if (*n) { - *s = n; - n = NULL; - } else - *s = NULL; - - return 0; -} - -int config_parse_ifnames(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { +int config_parse_ifnames( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { char ***sv = data; - const char *word, *state; - size_t l; int r; assert(filename); @@ -231,23 +197,27 @@ int config_parse_ifnames(const char *unit, assert(rvalue); assert(data); - FOREACH_WORD(word, l, rvalue, state) { - char *n; + for (;;) { + _cleanup_free_ char *word = NULL; - n = strndup(word, l); - if (!n) - return log_oom(); + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue); + return 0; + } + if (r == 0) + break; - if (!ascii_is_valid(n) || strlen(n) >= IFNAMSIZ) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue); - free(n); + if (!ifname_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); return 0; } - r = strv_consume(sv, n); + r = strv_push(sv, word); if (r < 0) return log_oom(); + + word = NULL; } return 0; @@ -277,8 +247,7 @@ int config_parse_ifalias(const char *unit, return log_oom(); if (!ascii_is_valid(n) || strlen(n) >= IFALIASZ) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Interface alias is not ASCII clean or is too long, ignoring assignment: %s", rvalue); + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface alias is not ASCII clean or is too long, ignoring assignment: %s", rvalue); return 0; } @@ -304,6 +273,8 @@ int config_parse_hwaddr(const char *unit, void *userdata) { struct ether_addr **hwaddr = data; struct ether_addr *n; + const char *start; + size_t offset; int r; assert(filename); @@ -315,16 +286,11 @@ int config_parse_hwaddr(const char *unit, if (!n) return log_oom(); - r = sscanf(rvalue, "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx", - &n->ether_addr_octet[0], - &n->ether_addr_octet[1], - &n->ether_addr_octet[2], - &n->ether_addr_octet[3], - &n->ether_addr_octet[4], - &n->ether_addr_octet[5]); - if (r != 6) { - log_syntax(unit, LOG_ERR, filename, line, EINVAL, - "Not a valid MAC address, ignoring assignment: %s", rvalue); + start = rvalue + strspn(rvalue, WHITESPACE); + r = ether_addr_from_string(start, n, &offset); + + if (r || (start[offset + strspn(start + offset, WHITESPACE)] != '\0')) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring assignment: %s", rvalue); free(n); return 0; } @@ -335,6 +301,36 @@ int config_parse_hwaddr(const char *unit, return 0; } +int config_parse_iaid(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + uint32_t iaid; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &iaid); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Unable to read IAID, ignoring assignment: %s", rvalue); + return 0; + } + + *((uint32_t *)data) = iaid; + + return 0; +} + void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { unsigned i; @@ -350,32 +346,32 @@ void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { int deserialize_in_addrs(struct in_addr **ret, const char *string) { _cleanup_free_ struct in_addr *addresses = NULL; int size = 0; - const char *word, *state; - size_t len; assert(ret); assert(string); - FOREACH_WORD(word, len, string, state) { - _cleanup_free_ char *addr_str = NULL; + for (;;) { + _cleanup_free_ char *word = NULL; struct in_addr *new_addresses; int r; + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + new_addresses = realloc(addresses, (size + 1) * sizeof(struct in_addr)); if (!new_addresses) return -ENOMEM; else addresses = new_addresses; - addr_str = strndup(word, len); - if (!addr_str) - return -ENOMEM; - - r = inet_pton(AF_INET, addr_str, &(addresses[size])); + r = inet_pton(AF_INET, word, &(addresses[size])); if (r <= 0) continue; - size ++; + size++; } *ret = addresses; @@ -384,31 +380,48 @@ int deserialize_in_addrs(struct in_addr **ret, const char *string) { return size; } +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, size_t size) { + unsigned i; + + assert(f); + assert(addresses); + assert(size); + + for (i = 0; i < size; i++) { + char buffer[INET6_ADDRSTRLEN]; + + fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f); + + if (i < size - 1) + fputc(' ', f); + } +} + int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { _cleanup_free_ struct in6_addr *addresses = NULL; int size = 0; - const char *word, *state; - size_t len; assert(ret); assert(string); - FOREACH_WORD(word, len, string, state) { - _cleanup_free_ char *addr_str = NULL; + for (;;) { + _cleanup_free_ char *word = NULL; struct in6_addr *new_addresses; int r; + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + new_addresses = realloc(addresses, (size + 1) * sizeof(struct in6_addr)); if (!new_addresses) return -ENOMEM; else addresses = new_addresses; - addr_str = strndup(word, len); - if (!addr_str) - return -ENOMEM; - - r = inet_pton(AF_INET6, addr_str, &(addresses[size])); + r = inet_pton(AF_INET6, word, &(addresses[size])); if (r <= 0) continue; @@ -421,7 +434,7 @@ int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { return size; } -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *routes, size_t size) { +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { unsigned i; assert(f); @@ -432,10 +445,15 @@ void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *route fprintf(f, "%s=", key); for (i = 0; i < size; i++) { - fprintf(f, "%s/%" PRIu8, inet_ntoa(routes[i].dst_addr), - routes[i].dst_prefixlen); - fprintf(f, ",%s%s", inet_ntoa(routes[i].gw_addr), - (i < (size - 1)) ? " ": ""); + struct in_addr dest, gw; + uint8_t length; + + assert_se(sd_dhcp_route_get_destination(routes[i], &dest) >= 0); + assert_se(sd_dhcp_route_get_gateway(routes[i], &gw) >= 0); + assert_se(sd_dhcp_route_get_destination_prefix_length(routes[i], &length) >= 0); + + fprintf(f, "%s/%" PRIu8, inet_ntoa(dest), length); + fprintf(f, ",%s%s", inet_ntoa(gw), (i < (size - 1)) ? " ": ""); } fputs("\n", f); @@ -444,29 +462,29 @@ void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *route int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string) { _cleanup_free_ struct sd_dhcp_route *routes = NULL; size_t size = 0, allocated = 0; - const char *word, *state; - size_t len; assert(ret); assert(ret_size); assert(ret_allocated); assert(string); - FOREACH_WORD(word, len, string, state) { - /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ - _cleanup_free_ char* entry = NULL; + /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ + for (;;) { + _cleanup_free_ char *word = NULL; char *tok, *tok_end; unsigned n; int r; - if (!GREEDY_REALLOC(routes, allocated, size + 1)) - return -ENOMEM; + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; - entry = strndup(word, len); - if(!entry) + if (!GREEDY_REALLOC(routes, allocated, size + 1)) return -ENOMEM; - tok = entry; + tok = word; /* get the subnet */ tok_end = strchr(tok, '/'); @@ -509,3 +527,30 @@ int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t return 0; } + +int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size) { + _cleanup_free_ char *hex_buf = NULL; + + assert(f); + assert(key); + assert(data); + + hex_buf = hexmem(data, size); + if (hex_buf == NULL) + return -ENOMEM; + + fprintf(f, "%s=%s\n", key, hex_buf); + + return 0; +} + +int deserialize_dhcp_option(void **data, size_t *data_len, const char *string) { + assert(data); + assert(data_len); + assert(string); + + if (strlen(string) % 2) + return -EINVAL; + + return unhexmem(string, strlen(string), (void **)data, data_len); +} diff --git a/src/libsystemd-network/network-internal.h b/src/libsystemd-network/network-internal.h index 06aba893ce..5bcd577167 100644 --- a/src/libsystemd-network/network-internal.h +++ b/src/libsystemd-network/network-internal.h @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - #pragma once /*** @@ -23,8 +21,10 @@ #include <stdbool.h> -#include "udev.h" +#include "sd-dhcp-lease.h" + #include "condition.h" +#include "udev.h" bool net_match_config(const struct ether_addr *match_mac, char * const *match_path, @@ -50,10 +50,6 @@ int config_parse_hwaddr(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int config_parse_ifname(const char *unit, const char *filename, unsigned line, - const char *section, unsigned section_line, const char *lvalue, - int ltype, const char *rvalue, void *data, void *userdata); - int config_parse_ifnames(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); @@ -62,15 +58,24 @@ int config_parse_ifalias(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); -int net_get_unique_predictable_data(struct udev_device *device, uint8_t result[8]); +int config_parse_iaid(const char *unit, const char *filename, unsigned line, + const char *section, unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, void *userdata); + +int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result); const char *net_get_name(struct udev_device *device); void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size); int deserialize_in_addrs(struct in_addr **addresses, const char *string); +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, + size_t size); int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); /* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ struct sd_dhcp_route; -void serialize_dhcp_routes(FILE *f, const char *key, struct sd_dhcp_route *routes, size_t size); +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size); int deserialize_dhcp_routes(struct sd_dhcp_route **ret, size_t *ret_size, size_t *ret_allocated, const char *string); + +int serialize_dhcp_option(FILE *f, const char *key, const void *data, size_t size); +int deserialize_dhcp_option(void **data, size_t *data_len, const char *string); diff --git a/src/libsystemd-network/sd-dhcp-client.c b/src/libsystemd-network/sd-dhcp-client.c index 6a0d270739..179e5950bd 100644 --- a/src/libsystemd-network/sd-dhcp-client.c +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -17,37 +17,43 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdlib.h> #include <errno.h> -#include <string.h> -#include <stdio.h> #include <net/ethernet.h> #include <net/if_arp.h> -#include <linux/if_infiniband.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <sys/ioctl.h> +#include <linux/if_infiniband.h> -#include "util.h" -#include "refcnt.h" -#include "random-util.h" -#include "async.h" +#include "sd-dhcp-client.h" -#include "dhcp-protocol.h" +#include "alloc-util.h" +#include "async.h" +#include "dhcp-identifier.h" #include "dhcp-internal.h" #include "dhcp-lease-internal.h" -#include "dhcp-identifier.h" -#include "sd-dhcp-client.h" +#include "dhcp-protocol.h" +#include "dns-domain.h" +#include "hostname-util.h" +#include "random-util.h" +#include "string-util.h" +#include "util.h" #define MAX_CLIENT_ID_LEN (sizeof(uint32_t) + MAX_DUID_LEN) /* Arbitrary limit */ #define MAX_MAC_ADDR_LEN CONST_MAX(INFINIBAND_ALEN, ETH_ALEN) +#define RESTART_AFTER_NAK_MIN_USEC (1 * USEC_PER_SEC) +#define RESTART_AFTER_NAK_MAX_USEC (30 * USEC_PER_MINUTE) + struct sd_dhcp_client { - RefCount n_ref; + unsigned n_ref; DHCPState state; sd_event *event; int event_priority; sd_event_source *timeout_resend; - int index; + int ifindex; int fd; union sockaddr_union link; sd_event_source *receive_message; @@ -76,7 +82,7 @@ struct sd_dhcp_client { } _packed_ ll; struct { /* 255: Node-specific (RFC 4361) */ - uint32_t iaid; + be32_t iaid; struct duid duid; } _packed_ ns; struct { @@ -95,31 +101,40 @@ struct sd_dhcp_client { sd_event_source *timeout_t1; sd_event_source *timeout_t2; sd_event_source *timeout_expire; - sd_dhcp_client_cb_t cb; + sd_dhcp_client_callback_t callback; void *userdata; sd_dhcp_lease *lease; + usec_t start_delay; }; static const uint8_t default_req_opts[] = { - DHCP_OPTION_SUBNET_MASK, - DHCP_OPTION_ROUTER, - DHCP_OPTION_HOST_NAME, - DHCP_OPTION_DOMAIN_NAME, - DHCP_OPTION_DOMAIN_NAME_SERVER, - DHCP_OPTION_NTP_SERVER, + SD_DHCP_OPTION_SUBNET_MASK, + SD_DHCP_OPTION_ROUTER, + SD_DHCP_OPTION_HOST_NAME, + SD_DHCP_OPTION_DOMAIN_NAME, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, }; -static int client_receive_message_raw(sd_event_source *s, int fd, - uint32_t revents, void *userdata); -static int client_receive_message_udp(sd_event_source *s, int fd, - uint32_t revents, void *userdata); +static int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); +static int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata); static void client_stop(sd_dhcp_client *client, int error); -int sd_dhcp_client_set_callback(sd_dhcp_client *client, sd_dhcp_client_cb_t cb, - void *userdata) { +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata) { + assert_return(client, -EINVAL); - client->cb = cb; + client->callback = cb; client->userdata = userdata; return 0; @@ -137,15 +152,15 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { size_t i; assert_return(client, -EINVAL); - assert_return (IN_SET(client->state, DHCP_STATE_INIT, - DHCP_STATE_STOPPED), -EBUSY); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); switch(option) { - case DHCP_OPTION_PAD: - case DHCP_OPTION_OVERLOAD: - case DHCP_OPTION_MESSAGE_TYPE: - case DHCP_OPTION_PARAMETER_REQUEST_LIST: - case DHCP_OPTION_END: + + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_PARAMETER_REQUEST_LIST: + case SD_DHCP_OPTION_END: return -EINVAL; default: @@ -165,11 +180,12 @@ int sd_dhcp_client_set_request_option(sd_dhcp_client *client, uint8_t option) { return 0; } -int sd_dhcp_client_set_request_address(sd_dhcp_client *client, - const struct in_addr *last_addr) { +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_addr) { + assert_return(client, -EINVAL); - assert_return (IN_SET(client->state, DHCP_STATE_INIT, - DHCP_STATE_STOPPED), -EBUSY); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); if (last_addr) client->last_addr = last_addr->s_addr; @@ -179,19 +195,22 @@ int sd_dhcp_client_set_request_address(sd_dhcp_client *client, return 0; } -int sd_dhcp_client_set_index(sd_dhcp_client *client, int interface_index) { - assert_return(client, -EINVAL); - assert_return (IN_SET(client->state, DHCP_STATE_INIT, - DHCP_STATE_STOPPED), -EBUSY); - assert_return(interface_index > 0, -EINVAL); +int sd_dhcp_client_set_ifindex(sd_dhcp_client *client, int ifindex) { - client->index = interface_index; + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + assert_return(ifindex > 0, -EINVAL); + client->ifindex = ifindex; return 0; } -int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, - size_t addr_len, uint16_t arp_type) { +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type) { + DHCP_CLIENT_DONT_DESTROY(client); bool need_restart = false; @@ -212,10 +231,9 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, return 0; if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { - log_dhcp_client(client, "Changing MAC address on running DHCP " - "client, restarting"); + log_dhcp_client(client, "Changing MAC address on running DHCP client, restarting"); need_restart = true; - client_stop(client, DHCP_EVENT_STOP); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); } memcpy(&client->mac_addr, addr, addr_len); @@ -228,8 +246,11 @@ int sd_dhcp_client_set_mac(sd_dhcp_client *client, const uint8_t *addr, return 0; } -int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, - const uint8_t **data, size_t *data_len) { +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len) { assert_return(client, -EINVAL); assert_return(type, -EINVAL); @@ -248,8 +269,12 @@ int sd_dhcp_client_get_client_id(sd_dhcp_client *client, uint8_t *type, return 0; } -int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, - const uint8_t *data, size_t data_len) { +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len) { + DHCP_CLIENT_DONT_DESTROY(client); bool need_restart = false; @@ -258,14 +283,17 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, assert_return(data_len > 0 && data_len <= MAX_CLIENT_ID_LEN, -EINVAL); switch (type) { + case ARPHRD_ETHER: if (data_len != ETH_ALEN) return -EINVAL; break; + case ARPHRD_INFINIBAND: if (data_len != INFINIBAND_ALEN) return -EINVAL; break; + default: break; } @@ -279,7 +307,7 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, log_dhcp_client(client, "Changing client ID on running DHCP " "client, restarting"); need_restart = true; - client_stop(client, DHCP_EVENT_STOP); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); } client->client_id.type = type; @@ -292,12 +320,78 @@ int sd_dhcp_client_set_client_id(sd_dhcp_client *client, uint8_t type, return 0; } -int sd_dhcp_client_set_hostname(sd_dhcp_client *client, - const char *hostname) { +/** + * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + + DHCP_CLIENT_DONT_DESTROY(client); + int r; + size_t len; + + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid != NULL, -EINVAL); + + if (duid != NULL) { + r = dhcp_validate_duid_len(duid_type, duid_len); + if (r < 0) + return r; + } + + zero(client->client_id); + client->client_id.type = 255; + + /* If IAID is not configured, generate it. */ + if (iaid == 0) { + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, + client->mac_addr_len, + &client->client_id.ns.iaid); + if (r < 0) + return r; + } else + client->client_id.ns.iaid = htobe32(iaid); + + if (duid != NULL) { + client->client_id.ns.duid.type = htobe16(duid_type); + memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); + len = sizeof(client->client_id.ns.duid.type) + duid_len; + } else if (duid_type == DUID_TYPE_EN) { + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); + if (r < 0) + return r; + } else + return -EOPNOTSUPP; + + client->client_id_len = sizeof(client->client_id.type) + len + + sizeof(client->client_id.ns.iaid); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Configured IAID+DUID, restarting."); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + sd_dhcp_client_start(client); + } + + return 0; +} + +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname) { + char *new_hostname = NULL; assert_return(client, -EINVAL); + if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname)) + return -EINVAL; + if (streq_ptr(client->hostname, hostname)) return 0; @@ -313,8 +407,10 @@ int sd_dhcp_client_set_hostname(sd_dhcp_client *client, return 0; } -int sd_dhcp_client_set_vendor_class_identifier(sd_dhcp_client *client, - const char *vci) { +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci) { + char *new_vci = NULL; assert_return(client, -EINVAL); @@ -341,28 +437,29 @@ int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { int sd_dhcp_client_get_lease(sd_dhcp_client *client, sd_dhcp_lease **ret) { assert_return(client, -EINVAL); - assert_return(ret, -EINVAL); if (client->state != DHCP_STATE_BOUND && client->state != DHCP_STATE_RENEWING && client->state != DHCP_STATE_REBINDING) return -EADDRNOTAVAIL; - *ret = sd_dhcp_lease_ref(client->lease); + if (ret) + *ret = client->lease; return 0; } static void client_notify(sd_dhcp_client *client, int event) { - if (client->cb) - client->cb(client, event, client->userdata); + assert(client); + + if (client->callback) + client->callback(client, event, client->userdata); } static int client_initialize(sd_dhcp_client *client) { assert_return(client, -EINVAL); - client->receive_message = - sd_event_source_unref(client->receive_message); + client->receive_message = sd_event_source_unref(client->receive_message); client->fd = asynchronous_close(client->fd); @@ -377,8 +474,7 @@ static int client_initialize(sd_dhcp_client *client) { client->state = DHCP_STATE_INIT; client->xid = 0; - if (client->lease) - client->lease = sd_dhcp_lease_unref(client->lease); + client->lease = sd_dhcp_lease_unref(client->lease); return 0; } @@ -388,7 +484,7 @@ static void client_stop(sd_dhcp_client *client, int error) { if (error < 0) log_dhcp_client(client, "STOPPED: %s", strerror(-error)); - else if (error == DHCP_EVENT_STOP) + else if (error == SD_DHCP_CLIENT_EVENT_STOP) log_dhcp_client(client, "STOPPED"); else log_dhcp_client(client, "STOPPED: Unknown event"); @@ -398,9 +494,14 @@ static void client_stop(sd_dhcp_client *client, int error) { client_initialize(client); } -static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, - uint8_t type, size_t *_optlen, size_t *_optoffset) { - _cleanup_free_ DHCPPacket *packet; +static int client_message_init( + sd_dhcp_client *client, + DHCPPacket **ret, + uint8_t type, + size_t *_optlen, + size_t *_optoffset) { + + _cleanup_free_ DHCPPacket *packet = NULL; size_t optlen, optoffset, size; be16_t max_size; usec_t time_now; @@ -467,7 +568,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, client->client_id.type = 255; - r = dhcp_identifier_set_iaid(client->index, client->mac_addr, client->mac_addr_len, &client->client_id.ns.iaid); + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->client_id.ns.iaid); if (r < 0) return r; @@ -482,7 +583,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, Identifier option is not set */ if (client->client_id_len) { r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_CLIENT_IDENTIFIER, + SD_DHCP_OPTION_CLIENT_IDENTIFIER, client->client_id_len, &client->client_id); if (r < 0) @@ -498,7 +599,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, messages. */ r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_PARAMETER_REQUEST_LIST, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, client->req_opts_size, client->req_opts); if (r < 0) return r; @@ -527,7 +628,7 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, */ max_size = htobe16(size); r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, - DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, 2, &max_size); if (r < 0) return r; @@ -540,8 +641,33 @@ static int client_message_init(sd_dhcp_client *client, DHCPPacket **ret, return 0; } -static int dhcp_client_send_raw(sd_dhcp_client *client, DHCPPacket *packet, - size_t len) { +static int client_append_fqdn_option( + DHCPMessage *message, + size_t optlen, + size_t *optoffset, + const char *fqdn) { + + uint8_t buffer[3 + DHCP_MAX_FQDN_LENGTH]; + int r; + + buffer[0] = DHCP_FQDN_FLAG_S | /* Request server to perform A RR DNS updates */ + DHCP_FQDN_FLAG_E; /* Canonical wire format */ + buffer[1] = 0; /* RCODE1 (deprecated) */ + buffer[2] = 0; /* RCODE2 (deprecated) */ + + r = dns_name_to_wire_format(fqdn, buffer + 3, sizeof(buffer) - 3, false); + if (r > 0) + r = dhcp_option_append(message, optlen, optoffset, 0, + SD_DHCP_OPTION_FQDN, 3 + r, buffer); + + return r; +} + +static int dhcp_client_send_raw( + sd_dhcp_client *client, + DHCPPacket *packet, + size_t len) { + dhcp_packet_append_ip_headers(packet, INADDR_ANY, DHCP_PORT_CLIENT, INADDR_BROADCAST, DHCP_PORT_SERVER, len); @@ -571,26 +697,34 @@ static int client_send_discover(sd_dhcp_client *client) { */ if (client->last_addr != INADDR_ANY) { r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_REQUESTED_IP_ADDRESS, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->last_addr); if (r < 0) return r; } - /* it is unclear from RFC 2131 if client should send hostname in - DHCPDISCOVER but dhclient does and so we do as well - */ if (client->hostname) { - r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_HOST_NAME, - strlen(client->hostname), client->hostname); + /* According to RFC 4702 "clients that send the Client FQDN option in + their messages MUST NOT also send the Host Name option". Just send + one of the two depending on the hostname type. + */ + if (dns_name_is_single_label(client->hostname)) { + /* it is unclear from RFC 2131 if client should send hostname in + DHCPDISCOVER but dhclient does and so we do as well + */ + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_HOST_NAME, + strlen(client->hostname), client->hostname); + } else + r = client_append_fqdn_option(&discover->dhcp, optlen, &optoffset, + client->hostname); if (r < 0) return r; } if (client->vendor_class_identifier) { r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, strlen(client->vendor_class_identifier), client->vendor_class_identifier); if (r < 0) @@ -598,7 +732,7 @@ static int client_send_discover(sd_dhcp_client *client) { } r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_END, 0, NULL); + SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -620,8 +754,9 @@ static int client_send_request(sd_dhcp_client *client) { size_t optoffset, optlen; int r; - r = client_message_init(client, &request, DHCP_REQUEST, - &optlen, &optoffset); + assert(client); + + r = client_message_init(client, &request, DHCP_REQUEST, &optlen, &optoffset); if (r < 0) return r; @@ -637,13 +772,13 @@ static int client_send_request(sd_dhcp_client *client) { */ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_SERVER_IDENTIFIER, + SD_DHCP_OPTION_SERVER_IDENTIFIER, 4, &client->lease->server_address); if (r < 0) return r; r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_REQUESTED_IP_ADDRESS, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->lease->address); if (r < 0) return r; @@ -656,7 +791,7 @@ static int client_send_request(sd_dhcp_client *client) { assigned address. ’ciaddr’ MUST be zero. */ r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_REQUESTED_IP_ADDRESS, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, 4, &client->last_addr); if (r < 0) return r; @@ -689,15 +824,19 @@ static int client_send_request(sd_dhcp_client *client) { } if (client->hostname) { - r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_HOST_NAME, - strlen(client->hostname), client->hostname); + if (dns_name_is_single_label(client->hostname)) + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_HOST_NAME, + strlen(client->hostname), client->hostname); + else + r = client_append_fqdn_option(&request->dhcp, optlen, &optoffset, + client->hostname); if (r < 0) return r; } r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, - DHCP_OPTION_END, 0, NULL); + SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -714,18 +853,23 @@ static int client_send_request(sd_dhcp_client *client) { return r; switch (client->state) { + case DHCP_STATE_REQUESTING: log_dhcp_client(client, "REQUEST (requesting)"); break; + case DHCP_STATE_INIT_REBOOT: log_dhcp_client(client, "REQUEST (init-reboot)"); break; + case DHCP_STATE_RENEWING: log_dhcp_client(client, "REQUEST (renewing)"); break; + case DHCP_STATE_REBINDING: log_dhcp_client(client, "REQUEST (rebinding)"); break; + default: log_dhcp_client(client, "REQUEST (invalid)"); break; @@ -736,8 +880,11 @@ static int client_send_request(sd_dhcp_client *client) { static int client_start(sd_dhcp_client *client); -static int client_timeout_resend(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_timeout_resend( + sd_event_source *s, + uint64_t usec, + void *userdata) { + sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); usec_t next_timeout = 0; @@ -754,6 +901,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, goto error; switch (client->state) { + case DHCP_STATE_RENEWING: time_left = (client->lease->t2 - client->lease->t1) / 2; @@ -881,8 +1029,10 @@ error: return 0; } -static int client_initialize_io_events(sd_dhcp_client *client, - sd_event_io_handler_t io_callback) { +static int client_initialize_io_events( + sd_dhcp_client *client, + sd_event_io_handler_t io_callback) { + int r; assert(client); @@ -911,6 +1061,7 @@ error: } static int client_initialize_time_events(sd_dhcp_client *client) { + uint64_t usec = 0; int r; assert(client); @@ -918,10 +1069,15 @@ static int client_initialize_time_events(sd_dhcp_client *client) { client->timeout_resend = sd_event_source_unref(client->timeout_resend); + if (client->start_delay) { + assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0); + usec += client->start_delay; + } + r = sd_event_add_time(client->event, &client->timeout_resend, clock_boottime_or_monotonic(), - 0, 0, + usec, 0, client_timeout_resend, client); if (r < 0) goto error; @@ -943,28 +1099,26 @@ error: } -static int client_initialize_events(sd_dhcp_client *client, - sd_event_io_handler_t io_callback) { +static int client_initialize_events(sd_dhcp_client *client, sd_event_io_handler_t io_callback) { client_initialize_io_events(client, io_callback); client_initialize_time_events(client); return 0; } -static int client_start(sd_dhcp_client *client) { +static int client_start_delayed(sd_dhcp_client *client) { int r; assert_return(client, -EINVAL); assert_return(client->event, -EINVAL); - assert_return(client->index > 0, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); assert_return(client->fd < 0, -EBUSY); assert_return(client->xid == 0, -EINVAL); - assert_return(client->state == DHCP_STATE_INIT || - client->state == DHCP_STATE_INIT_REBOOT, -EBUSY); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY); client->xid = random_u32(); - r = dhcp_network_bind_raw_socket(client->index, &client->link, + r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, client->mac_addr, client->mac_addr_len, client->arp_type); if (r < 0) { @@ -979,14 +1133,18 @@ static int client_start(sd_dhcp_client *client) { return client_initialize_events(client, client_receive_message_raw); } -static int client_timeout_expire(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_start(sd_dhcp_client *client) { + client->start_delay = 0; + return client_start_delayed(client); +} + +static int client_timeout_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); log_dhcp_client(client, "EXPIRED"); - client_notify(client, DHCP_EVENT_EXPIRED); + client_notify(client, SD_DHCP_CLIENT_EVENT_EXPIRED); /* lease was lost, start over if not freed or stopped in callback */ if (client->state != DHCP_STATE_STOPPED) { @@ -1002,13 +1160,15 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) DHCP_CLIENT_DONT_DESTROY(client); int r; + assert(client); + client->receive_message = sd_event_source_unref(client->receive_message); client->fd = asynchronous_close(client->fd); client->state = DHCP_STATE_REBINDING; client->attempt = 1; - r = dhcp_network_bind_raw_socket(client->index, &client->link, + r = dhcp_network_bind_raw_socket(client->ifindex, &client->link, client->xid, client->mac_addr, client->mac_addr_len, client->arp_type); if (r < 0) { @@ -1020,8 +1180,7 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) return client_initialize_events(client, client_receive_message_raw); } -static int client_timeout_t1(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp_client *client = userdata; DHCP_CLIENT_DONT_DESTROY(client); @@ -1031,9 +1190,8 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, return client_initialize_time_events(client); } -static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, - size_t len) { - _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; +static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; int r; r = dhcp_lease_new(&lease); @@ -1048,25 +1206,23 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, return r; } - r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease); + r = dhcp_option_parse(offer, len, dhcp_lease_parse_options, lease, NULL); if (r != DHCP_OFFER) { log_dhcp_client(client, "received message was not an OFFER, ignoring"); return -ENOMSG; } lease->next_server = offer->siaddr; - lease->address = offer->yiaddr; - if (lease->address == INADDR_ANY || - lease->server_address == INADDR_ANY || + if (lease->address == 0 || + lease->server_address == 0 || lease->lifetime == 0) { - log_dhcp_client(client, "received lease lacks address, server " - "address or lease lifetime, ignoring"); + log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring"); return -ENOMSG; } - if (lease->subnet_mask == INADDR_ANY) { + if (!lease->have_subnet_mask) { r = dhcp_lease_set_default_subnet_mask(lease); if (r < 0) { log_dhcp_client(client, "received lease lacks subnet " @@ -1085,11 +1241,10 @@ static int client_handle_offer(sd_dhcp_client *client, DHCPMessage *offer, return 0; } -static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, - size_t len) { +static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, size_t len) { int r; - r = dhcp_option_parse(force, len, NULL, NULL); + r = dhcp_option_parse(force, len, NULL, NULL, NULL); if (r != DHCP_FORCERENEW) return -ENOMSG; @@ -1098,9 +1253,9 @@ static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, return 0; } -static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, - size_t len) { - _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; +static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, size_t len) { + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char *error_message = NULL; int r; r = dhcp_lease_new(&lease); @@ -1115,9 +1270,9 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, return r; } - r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease); + r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message); if (r == DHCP_NAK) { - log_dhcp_client(client, "NAK"); + log_dhcp_client(client, "NAK: %s", strna(error_message)); return -EADDRNOTAVAIL; } @@ -1148,14 +1303,14 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, } } - r = DHCP_EVENT_IP_ACQUIRE; + r = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; if (client->lease) { if (client->lease->address != lease->address || client->lease->subnet_mask != lease->subnet_mask || client->lease->router != lease->router) { - r = DHCP_EVENT_IP_CHANGE; + r = SD_DHCP_CLIENT_EVENT_IP_CHANGE; } else - r = DHCP_EVENT_RENEW; + r = SD_DHCP_CLIENT_EVENT_RENEW; client->lease = sd_dhcp_lease_unref(client->lease); } @@ -1168,13 +1323,17 @@ static int client_handle_ack(sd_dhcp_client *client, DHCPMessage *ack, return r; } -static uint64_t client_compute_timeout(sd_dhcp_client *client, - uint32_t lifetime, double factor) { +static uint64_t client_compute_timeout(sd_dhcp_client *client, uint32_t lifetime, double factor) { assert(client); assert(client->request_sent); - assert(lifetime); + assert(lifetime > 0); + + if (lifetime > 3) + lifetime -= 3; + else + lifetime = 0; - return client->request_sent + ((lifetime - 3) * USEC_PER_SEC * factor) + + return client->request_sent + (lifetime * USEC_PER_SEC * factor) + + (random_u32() & 0x1fffff); } @@ -1206,7 +1365,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { /* convert the various timeouts from relative (secs) to absolute (usecs) */ lifetime_timeout = client_compute_timeout(client, client->lease->lifetime, 1); - if (client->lease->t1 && client->lease->t2) { + if (client->lease->t1 > 0 && client->lease->t2 > 0) { /* both T1 and T2 are given */ if (client->lease->t1 < client->lease->t2 && client->lease->t2 < client->lease->lifetime) { @@ -1220,7 +1379,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5); client->lease->t1 = client->lease->lifetime / 2; } - } else if (client->lease->t2 && client->lease->t2 < client->lease->lifetime) { + } else if (client->lease->t2 > 0 && client->lease->t2 < client->lease->lifetime) { /* only T2 is given, and it is valid */ t2_timeout = client_compute_timeout(client, client->lease->t2, 1); t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5); @@ -1230,7 +1389,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0); client->lease->t2 = (client->lease->lifetime * 7) / 8; } - } else if (client->lease->t1 && client->lease->t1 < client->lease->lifetime) { + } else if (client->lease->t1 > 0 && client->lease->t1 < client->lease->lifetime) { /* only T1 is given, and it is valid */ t1_timeout = client_compute_timeout(client, client->lease->t1, 1); t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0); @@ -1266,8 +1425,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return r; log_dhcp_client(client, "lease expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - lifetime_timeout - time_now, 0)); + format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_timeout - time_now, USEC_PER_SEC)); /* don't arm earlier timeouts if this has already expired */ if (lifetime_timeout <= time_now) @@ -1293,8 +1451,7 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return r; log_dhcp_client(client, "T2 expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - t2_timeout - time_now, 0)); + format_timespan(time_string, FORMAT_TIMESPAN_MAX, t2_timeout - time_now, USEC_PER_SEC)); /* don't arm earlier timeout if this has already expired */ if (t2_timeout <= time_now) @@ -1319,15 +1476,14 @@ static int client_set_lease_timeouts(sd_dhcp_client *client) { return r; log_dhcp_client(client, "T1 expires in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - t1_timeout - time_now, 0)); + format_timespan(time_string, FORMAT_TIMESPAN_MAX, t1_timeout - time_now, USEC_PER_SEC)); return 0; } -static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, - int len) { +static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, int len) { DHCP_CLIENT_DONT_DESTROY(client); + char time_string[FORMAT_TIMESPAN_MAX]; int r = 0, notify_event = 0; assert(client); @@ -1375,6 +1531,7 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, r = client_handle_ack(client, message, len); if (r >= 0) { + client->start_delay = 0; client->timeout_resend = sd_event_source_unref(client->timeout_resend); client->receive_message = @@ -1383,8 +1540,8 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, if (IN_SET(client->state, DHCP_STATE_REQUESTING, DHCP_STATE_REBOOTING)) - notify_event = DHCP_EVENT_IP_ACQUIRE; - else if (r != DHCP_EVENT_IP_ACQUIRE) + notify_event = SD_DHCP_CLIENT_EVENT_IP_ACQUIRE; + else if (r != SD_DHCP_CLIENT_EVENT_IP_ACQUIRE) notify_event = r; client->state = DHCP_STATE_BOUND; @@ -1424,11 +1581,15 @@ static int client_handle_message(sd_dhcp_client *client, DHCPMessage *message, if (r < 0) goto error; - r = client_start(client); + r = client_start_delayed(client); if (r < 0) goto error; - log_dhcp_client(client, "REBOOTED"); + log_dhcp_client(client, "REBOOT in %s", format_timespan(time_string, FORMAT_TIMESPAN_MAX, + client->start_delay, USEC_PER_SEC)); + + client->start_delay = CLAMP(client->start_delay * 2, + RESTART_AFTER_NAK_MIN_USEC, RESTART_AFTER_NAK_MAX_USEC); return 0; } else if (r == -ENOMSG) @@ -1466,52 +1627,54 @@ error: return r; } -static int client_receive_message_udp(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { +static int client_receive_message_udp( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + sd_dhcp_client *client = userdata; _cleanup_free_ DHCPMessage *message = NULL; - int buflen = 0, len, r; - const struct ether_addr zero_mac = { { 0, 0, 0, 0, 0, 0 } }; + const struct ether_addr zero_mac = {}; const struct ether_addr *expected_chaddr = NULL; uint8_t expected_hlen = 0; + ssize_t len, buflen; assert(s); assert(client); - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0) - return r; - + buflen = next_datagram_size_fd(fd); if (buflen < 0) - /* this can't be right */ - return -EIO; + return buflen; message = malloc0(buflen); if (!message) return -ENOMEM; - len = read(fd, message, buflen); + len = recv(fd, message, buflen, 0); if (len < 0) { - log_dhcp_client(client, "could not receive message from UDP " - "socket: %m"); - return 0; - } else if ((size_t)len < sizeof(DHCPMessage)) { - log_dhcp_client(client, "too small to be a DHCP message: ignoring"); + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_dhcp_client_errno(client, errno, "Could not receive message from UDP socket: %m"); + } + if ((size_t) len < sizeof(DHCPMessage)) { + log_dhcp_client(client, "Too small to be a DHCP message: ignoring"); return 0; } if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { - log_dhcp_client(client, "not a DHCP message: ignoring"); + log_dhcp_client(client, "Not a DHCP message: ignoring"); return 0; } if (message->op != BOOTREPLY) { - log_dhcp_client(client, "not a BOOTREPLY message: ignoring"); + log_dhcp_client(client, "Not a BOOTREPLY message: ignoring"); return 0; } if (message->htype != client->arp_type) { - log_dhcp_client(client, "packet type does not match client type"); + log_dhcp_client(client, "Packet type does not match client type"); return 0; } @@ -1519,19 +1682,18 @@ static int client_receive_message_udp(sd_event_source *s, int fd, expected_hlen = ETH_ALEN; expected_chaddr = (const struct ether_addr *) &client->mac_addr; } else { - /* Non-ethernet links expect zero chaddr */ + /* Non-Ethernet links expect zero chaddr */ expected_hlen = 0; expected_chaddr = &zero_mac; } if (message->hlen != expected_hlen) { - log_dhcp_client(client, "unexpected packet hlen %d", message->hlen); + log_dhcp_client(client, "Unexpected packet hlen %d", message->hlen); return 0; } if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) { - log_dhcp_client(client, "received chaddr does not match " - "expected: ignoring"); + log_dhcp_client(client, "Received chaddr does not match expected: ignoring"); return 0; } @@ -1539,8 +1701,7 @@ static int client_receive_message_udp(sd_event_source *s, int fd, be32toh(message->xid) != client->xid) { /* in BOUND state, we may receive FORCERENEW with xid set by server, so ignore the xid in this case */ - log_dhcp_client(client, "received xid (%u) does not match " - "expected (%u): ignoring", + log_dhcp_client(client, "Received xid (%u) does not match expected (%u): ignoring", be32toh(message->xid), client->xid); return 0; } @@ -1548,8 +1709,12 @@ static int client_receive_message_udp(sd_event_source *s, int fd, return client_handle_message(client, message, len); } -static int client_receive_message_raw(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { +static int client_receive_message_raw( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + sd_dhcp_client *client = userdata; _cleanup_free_ DHCPPacket *packet = NULL; uint8_t cmsgbuf[CMSG_LEN(sizeof(struct tpacket_auxdata))]; @@ -1562,18 +1727,15 @@ static int client_receive_message_raw(sd_event_source *s, int fd, }; struct cmsghdr *cmsg; bool checksum = true; - int buflen = 0, len, r; + ssize_t buflen, len; + int r; assert(s); assert(client); - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0) - return r; - + buflen = next_datagram_size_fd(fd); if (buflen < 0) - /* this can't be right */ - return -EIO; + return buflen; packet = malloc0(buflen); if (!packet) @@ -1584,9 +1746,12 @@ static int client_receive_message_raw(sd_event_source *s, int fd, len = recvmsg(fd, &msg, 0); if (len < 0) { - log_dhcp_client(client, "could not receive message from raw " - "socket: %m"); - return 0; + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_dhcp_client(client, "Could not receive message from raw socket: %m"); + + return -errno; } else if ((size_t)len < sizeof(DHCPPacket)) return 0; @@ -1624,7 +1789,7 @@ int sd_dhcp_client_start(sd_dhcp_client *client) { r = client_start(client); if (r >= 0) - log_dhcp_client(client, "STARTED on ifindex %i", client->index); + log_dhcp_client(client, "STARTED on ifindex %i", client->ifindex); return r; } @@ -1634,14 +1799,13 @@ int sd_dhcp_client_stop(sd_dhcp_client *client) { assert_return(client, -EINVAL); - client_stop(client, DHCP_EVENT_STOP); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); client->state = DHCP_STATE_STOPPED; return 0; } -int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, - int priority) { +int sd_dhcp_client_attach_event(sd_dhcp_client *client, sd_event *event, int64_t priority) { int r; assert_return(client, -EINVAL); @@ -1669,43 +1833,53 @@ int sd_dhcp_client_detach_event(sd_dhcp_client *client) { } sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { - if (!client) - return NULL; + assert_return(client, NULL); return client->event; } sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client) { - if (client) - assert_se(REFCNT_INC(client->n_ref) >= 2); + + if (!client) + return NULL; + + assert(client->n_ref >= 1); + client->n_ref++; return client; } sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client) { - if (client && REFCNT_DEC(client->n_ref) == 0) { - log_dhcp_client(client, "FREE"); - client_initialize(client); + if (!client) + return NULL; - client->receive_message = - sd_event_source_unref(client->receive_message); + assert(client->n_ref >= 1); + client->n_ref--; - sd_dhcp_client_detach_event(client); + if (client->n_ref > 0) + return NULL; - sd_dhcp_lease_unref(client->lease); + log_dhcp_client(client, "FREE"); - free(client->req_opts); - free(client->hostname); - free(client->vendor_class_identifier); - free(client); - } + client_initialize(client); + + client->receive_message = sd_event_source_unref(client->receive_message); + + sd_dhcp_client_detach_event(client); + + sd_dhcp_lease_unref(client->lease); + + free(client->req_opts); + free(client->hostname); + free(client->vendor_class_identifier); + free(client); return NULL; } int sd_dhcp_client_new(sd_dhcp_client **ret) { - _cleanup_dhcp_client_unref_ sd_dhcp_client *client = NULL; + _cleanup_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; assert_return(ret, -EINVAL); @@ -1713,15 +1887,14 @@ int sd_dhcp_client_new(sd_dhcp_client **ret) { if (!client) return -ENOMEM; - client->n_ref = REFCNT_INIT; + client->n_ref = 1; client->state = DHCP_STATE_INIT; - client->index = -1; + client->ifindex = -1; client->fd = -1; client->attempt = 1; client->mtu = DHCP_DEFAULT_MIN_SIZE; 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/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c index 54417b3af3..ef50ed17a1 100644 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -18,28 +18,48 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdlib.h> +#include <arpa/inet.h> #include <errno.h> -#include <string.h> #include <stdio.h> -#include <arpa/inet.h> +#include <stdlib.h> +#include <string.h> + +#include "sd-dhcp-lease.h" +#include "alloc-util.h" +#include "dhcp-lease-internal.h" +#include "dhcp-protocol.h" +#include "dns-domain.h" +#include "fd-util.h" #include "fileio.h" -#include "unaligned.h" -#include "in-addr-util.h" +#include "hexdecoct.h" #include "hostname-util.h" -#include "dhcp-protocol.h" -#include "dhcp-lease-internal.h" -#include "sd-dhcp-lease.h" +#include "in-addr-util.h" #include "network-internal.h" -#include "dns-domain.h" +#include "parse-util.h" +#include "stdio-util.h" +#include "string-util.h" +#include "unaligned.h" int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); + if (lease->address == 0) + return -ENODATA; + addr->s_addr = lease->address; + return 0; +} +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (!lease->have_broadcast) + return -ENODATA; + + addr->s_addr = lease->broadcast; return 0; } @@ -47,8 +67,32 @@ int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime) { assert_return(lease, -EINVAL); assert_return(lifetime, -EINVAL); + if (lease->lifetime <= 0) + return -ENODATA; + *lifetime = lease->lifetime; + return 0; +} + +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1) { + assert_return(lease, -EINVAL); + assert_return(t1, -EINVAL); + + if (lease->t1 <= 0) + return -ENODATA; + + *t1 = lease->t1; + return 0; +} + +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2) { + assert_return(lease, -EINVAL); + assert_return(t2, -EINVAL); + if (lease->t2 <= 0) + return -ENODATA; + + *t2 = lease->t2; return 0; } @@ -56,11 +100,10 @@ int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) { assert_return(lease, -EINVAL); assert_return(mtu, -EINVAL); - if (lease->mtu) - *mtu = lease->mtu; - else - return -ENOENT; + if (lease->mtu <= 0) + return -ENODATA; + *mtu = lease->mtu; return 0; } @@ -68,37 +111,32 @@ int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - if (lease->dns_size) { - *addr = lease->dns; - return lease->dns_size; - } else - return -ENOENT; + if (lease->dns_size <= 0) + return -ENODATA; - return 0; + *addr = lease->dns; + return (int) lease->dns_size; } int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - if (lease->ntp_size) { - *addr = lease->ntp; - return lease->ntp_size; - } else - return -ENOENT; + if (lease->ntp_size <= 0) + return -ENODATA; - return 0; + *addr = lease->ntp; + return (int) lease->ntp_size; } int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname) { assert_return(lease, -EINVAL); assert_return(domainname, -EINVAL); - if (lease->domainname) - *domainname = lease->domainname; - else - return -ENOENT; + if (!lease->domainname) + return -ENODATA; + *domainname = lease->domainname; return 0; } @@ -106,11 +144,10 @@ int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) { assert_return(lease, -EINVAL); assert_return(hostname, -EINVAL); - if (lease->hostname) - *hostname = lease->hostname; - else - return -ENOENT; + if (!lease->hostname) + return -ENODATA; + *hostname = lease->hostname; return 0; } @@ -118,11 +155,10 @@ int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path) { assert_return(lease, -EINVAL); assert_return(root_path, -EINVAL); - if (lease->root_path) - *root_path = lease->root_path; - else - return -ENOENT; + if (!lease->root_path) + return -ENODATA; + *root_path = lease->root_path; return 0; } @@ -130,11 +166,10 @@ int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - if (lease->router != INADDR_ANY) - addr->s_addr = lease->router; - else - return -ENOENT; + if (lease->router == 0) + return -ENODATA; + addr->s_addr = lease->router; return 0; } @@ -142,8 +177,10 @@ int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - addr->s_addr = lease->subnet_mask; + if (!lease->have_subnet_mask) + return -ENODATA; + addr->s_addr = lease->subnet_mask; return 0; } @@ -151,8 +188,10 @@ int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *ad assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - addr->s_addr = lease->server_address; + if (lease->server_address == 0) + return -ENODATA; + addr->s_addr = lease->server_address; return 0; } @@ -160,128 +199,152 @@ int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { assert_return(lease, -EINVAL); assert_return(addr, -EINVAL); - addr->s_addr = lease->next_server; + if (lease->next_server == 0) + return -ENODATA; + addr->s_addr = lease->next_server; return 0; } -int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, struct sd_dhcp_route **routes) { +/* + * The returned routes array must be freed by the caller. + * Route objects have the same lifetime of the lease and must not be freed. + */ +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes) { + sd_dhcp_route **ret; + unsigned i; assert_return(lease, -EINVAL); assert_return(routes, -EINVAL); - if (lease->static_route_size) { - *routes = lease->static_route; - return lease->static_route_size; - } else - return -ENOENT; + if (lease->static_route_size <= 0) + return -ENODATA; - return 0; + ret = new(sd_dhcp_route *, lease->static_route_size); + if (!ret) + return -ENOMEM; + + for (i = 0; i < lease->static_route_size; i++) + ret[i] = &lease->static_route[i]; + + *routes = ret; + return (int) lease->static_route_size; } -int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const uint8_t **data, - size_t *data_len) { +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len) { assert_return(lease, -EINVAL); assert_return(data, -EINVAL); assert_return(data_len, -EINVAL); - if (!lease->vendor_specific) - return -ENOENT; + if (lease->vendor_specific_len <= 0) + return -ENODATA; *data = lease->vendor_specific; *data_len = lease->vendor_specific_len; - return 0; } sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease) { - if (lease) - assert_se(REFCNT_INC(lease->n_ref) >= 2); + + if (!lease) + return NULL; + + assert(lease->n_ref >= 1); + lease->n_ref++; return lease; } sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease) { - if (lease && REFCNT_DEC(lease->n_ref) == 0) { - free(lease->hostname); - free(lease->domainname); - free(lease->dns); - free(lease->ntp); - free(lease->static_route); - free(lease->client_id); - free(lease->vendor_specific); - free(lease); - } - return NULL; -} + if (!lease) + return NULL; -static void lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) { - assert(option); - assert(ret); + assert(lease->n_ref >= 1); + lease->n_ref--; - if (len == 4) { - *ret = unaligned_read_be32((be32_t*) option); + if (lease->n_ref > 0) + return NULL; - if (*ret < min) - *ret = min; + while (lease->private_options) { + struct sd_dhcp_raw_option *option = lease->private_options; + + LIST_REMOVE(options, lease->private_options, option); + + free(option->data); + free(option); } -} -static void lease_parse_s32(const uint8_t *option, size_t len, int32_t *ret) { - lease_parse_u32(option, len, (uint32_t *)ret, 0); + free(lease->hostname); + free(lease->domainname); + free(lease->dns); + free(lease->ntp); + free(lease->static_route); + free(lease->client_id); + free(lease->vendor_specific); + free(lease); + + return NULL; } -static void lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { +static int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) { assert(option); assert(ret); - if (len == 2) { - *ret = unaligned_read_be16((be16_t*) option); + if (len != 4) + return -EINVAL; - if (*ret < min) - *ret = min; - } + *ret = unaligned_read_be32((be32_t*) option); + if (*ret < min) + *ret = min; + + return 0; } -static void lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { +static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { assert(option); assert(ret); - if (len == 4) - memcpy(ret, option, 4); -} + if (len != 2) + return -EINVAL; -static void lease_parse_bool(const uint8_t *option, size_t len, bool *ret) { - assert(option); - assert(ret); + *ret = unaligned_read_be16((be16_t*) option); + if (*ret < min) + *ret = min; - if (len == 1) - *ret = !!(*option); + return 0; } -static void lease_parse_u8(const uint8_t *option, size_t len, uint8_t *ret, uint8_t min) { +static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { assert(option); assert(ret); - if (len == 1) { - *ret = *option; + if (len != 4) + return -EINVAL; - if (*ret < min) - *ret = min; - } + memcpy(ret, option, 4); + return 0; } static int lease_parse_string(const uint8_t *option, size_t len, char **ret) { assert(option); assert(ret); - if (len >= 1) { + if (len <= 0) + *ret = mfree(*ret); + else { char *string; - string = strndup((const char *)option, len); + /* + * One trailing NUL byte is OK, we don't mind. See: + * https://github.com/systemd/systemd/issues/1337 + */ + if (memchr(option, 0, len - 1)) + return -EINVAL; + + string = strndup((const char *) option, len); if (!string) - return -errno; + return -ENOMEM; free(*ret); *ret = string; @@ -290,48 +353,79 @@ static int lease_parse_string(const uint8_t *option, size_t len, char **ret) { return 0; } -static int lease_parse_in_addrs_aux(const uint8_t *option, size_t len, struct in_addr **ret, size_t *ret_size, size_t mult) { +static int lease_parse_domain(const uint8_t *option, size_t len, char **ret) { + _cleanup_free_ char *name = NULL, *normalized = NULL; + int r; + assert(option); assert(ret); - assert(ret_size); - if (len && !(len % (4 * mult))) { - size_t size; + r = lease_parse_string(option, len, &name); + if (r < 0) + return r; + if (!name) { + *ret = mfree(*ret); + return 0; + } + + r = dns_name_normalize(name, &normalized); + if (r < 0) + return r; + + if (is_localhost(normalized)) + return -EINVAL; + + if (dns_name_is_root(normalized)) + return -EINVAL; + + free(*ret); + *ret = normalized; + normalized = NULL; + + return 0; +} + +static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *n_ret) { + assert(option); + assert(ret); + assert(n_ret); + + if (len <= 0) { + *ret = mfree(*ret); + *n_ret = 0; + } else { + size_t n_addresses; struct in_addr *addresses; - size = len / 4; + if (len % 4 != 0) + return -EINVAL; + + n_addresses = len / 4; - addresses = newdup(struct in_addr, option, size); + addresses = newdup(struct in_addr, option, n_addresses); if (!addresses) return -ENOMEM; free(*ret); *ret = addresses; - *ret_size = size; + *n_ret = n_addresses; } return 0; } -static int lease_parse_in_addrs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *ret_size) { - return lease_parse_in_addrs_aux(option, len, ret, ret_size, 1); -} - -static int lease_parse_in_addrs_pairs(const uint8_t *option, size_t len, struct in_addr **ret, size_t *ret_size) { - return lease_parse_in_addrs_aux(option, len, ret, ret_size, 2); -} - -static int lease_parse_routes(const uint8_t *option, size_t len, struct sd_dhcp_route **routes, - size_t *routes_size, size_t *routes_allocated) { +static int lease_parse_routes( + const uint8_t *option, size_t len, + struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) { struct in_addr addr; - assert(option); + assert(option || len <= 0); assert(routes); assert(routes_size); assert(routes_allocated); - if (!len) + if (len <= 0) return 0; if (len % 8 != 0) @@ -346,15 +440,15 @@ static int lease_parse_routes(const uint8_t *option, size_t len, struct sd_dhcp_ r = in_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen); if (r < 0) { - log_error("Failed to determine destination prefix length from class based IP, ignoring"); + log_debug("Failed to determine destination prefix length from class based IP, ignoring"); continue; } - lease_parse_be32(option, 4, &addr.s_addr); + assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0); route->dst_addr = inet_makeaddr(inet_netof(addr), 0); option += 4; - lease_parse_be32(option, 4, &route->gw_addr.s_addr); + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); option += 4; len -= 8; @@ -365,14 +459,18 @@ static int lease_parse_routes(const uint8_t *option, size_t len, struct sd_dhcp_ } /* parses RFC3442 Classless Static Route Option */ -static int lease_parse_classless_routes(const uint8_t *option, size_t len, struct sd_dhcp_route **routes, - size_t *routes_size, size_t *routes_allocated) { +static int lease_parse_classless_routes( + const uint8_t *option, size_t len, + struct sd_dhcp_route **routes, size_t *routes_size, size_t *routes_allocated) { - assert(option); + assert(option || len <= 0); assert(routes); assert(routes_size); assert(routes_allocated); + if (len <= 0) + return 0; + /* option format: (subnet-mask-width significant-subnet-octets gateway-ip)* */ while (len > 0) { @@ -380,7 +478,7 @@ static int lease_parse_classless_routes(const uint8_t *option, size_t len, struc struct sd_dhcp_route *route; if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + 1)) - return -ENOMEM; + return -ENOMEM; route = *routes + *routes_size; @@ -401,7 +499,7 @@ static int lease_parse_classless_routes(const uint8_t *option, size_t len, struc if (len < 4) return -EINVAL; - lease_parse_be32(option, 4, &route->gw_addr.s_addr); + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); option += 4; len -= 4; @@ -411,204 +509,203 @@ static int lease_parse_classless_routes(const uint8_t *option, size_t len, struc return 0; } -int dhcp_lease_parse_options(uint8_t code, uint8_t len, const uint8_t *option, - void *user_data) { - sd_dhcp_lease *lease = user_data; +int dhcp_lease_parse_options(uint8_t code, uint8_t len, const void *option, void *userdata) { + sd_dhcp_lease *lease = userdata; int r; assert(lease); switch(code) { - case DHCP_OPTION_TIME_OFFSET: - lease_parse_s32(option, len, &lease->time_offset); - - break; - - case DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT: - lease_parse_u32(option, len, &lease->mtu_aging_timeout, 0); - - break; - - case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: - lease_parse_u32(option, len, &lease->lifetime, 1); + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + r = lease_parse_u32(option, len, &lease->lifetime, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse lease time, ignoring: %m"); break; - case DHCP_OPTION_SERVER_IDENTIFIER: - lease_parse_be32(option, len, &lease->server_address); + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + r = lease_parse_be32(option, len, &lease->server_address); + if (r < 0) + log_debug_errno(r, "Failed to parse server identifier, ignoring: %m"); break; - case DHCP_OPTION_SUBNET_MASK: - lease_parse_be32(option, len, &lease->subnet_mask); - + case SD_DHCP_OPTION_SUBNET_MASK: + r = lease_parse_be32(option, len, &lease->subnet_mask); + if (r < 0) + log_debug_errno(r, "Failed to parse subnet mask, ignoring: %m"); + else + lease->have_subnet_mask = true; break; - case DHCP_OPTION_BROADCAST: - lease_parse_be32(option, len, &lease->broadcast); - + case SD_DHCP_OPTION_BROADCAST: + r = lease_parse_be32(option, len, &lease->broadcast); + if (r < 0) + log_debug_errno(r, "Failed to parse broadcast address, ignoring: %m"); + else + lease->have_broadcast = true; break; - case DHCP_OPTION_ROUTER: - if(len >= 4) - lease_parse_be32(option, 4, &lease->router); - + case SD_DHCP_OPTION_ROUTER: + if (len >= 4) { + r = lease_parse_be32(option, 4, &lease->router); + if (r < 0) + log_debug_errno(r, "Failed to parse router address, ignoring: %m"); + } break; - case DHCP_OPTION_DOMAIN_NAME_SERVER: + case SD_DHCP_OPTION_DOMAIN_NAME_SERVER: r = lease_parse_in_addrs(option, len, &lease->dns, &lease->dns_size); if (r < 0) - return r; - + log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); break; - case DHCP_OPTION_NTP_SERVER: + case SD_DHCP_OPTION_NTP_SERVER: r = lease_parse_in_addrs(option, len, &lease->ntp, &lease->ntp_size); if (r < 0) - return r; - + log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); break; - case DHCP_OPTION_POLICY_FILTER: - r = lease_parse_in_addrs_pairs(option, len, &lease->policy_filter, &lease->policy_filter_size); + case SD_DHCP_OPTION_STATIC_ROUTE: + r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, &lease->static_route_allocated); if (r < 0) - return r; - + log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); break; - case DHCP_OPTION_STATIC_ROUTE: - r = lease_parse_routes(option, len, &lease->static_route, &lease->static_route_size, - &lease->static_route_allocated); + case SD_DHCP_OPTION_INTERFACE_MTU: + r = lease_parse_u16(option, len, &lease->mtu, 68); if (r < 0) - return r; - + log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); break; - case DHCP_OPTION_INTERFACE_MTU: - lease_parse_u16(option, len, &lease->mtu, 68); + case SD_DHCP_OPTION_DOMAIN_NAME: + r = lease_parse_domain(option, len, &lease->domainname); + if (r < 0) { + log_debug_errno(r, "Failed to parse domain name, ignoring: %m"); + return 0; + } break; - case DHCP_OPTION_INTERFACE_MDR: - lease_parse_u16(option, len, &lease->mdr, 576); + case SD_DHCP_OPTION_HOST_NAME: + r = lease_parse_domain(option, len, &lease->hostname); + if (r < 0) { + log_debug_errno(r, "Failed to parse host name, ignoring: %m"); + return 0; + } break; - case DHCP_OPTION_INTERFACE_TTL: - lease_parse_u8(option, len, &lease->ttl, 1); - + case SD_DHCP_OPTION_ROOT_PATH: + r = lease_parse_string(option, len, &lease->root_path); + if (r < 0) + log_debug_errno(r, "Failed to parse root path, ignoring: %m"); break; - case DHCP_OPTION_BOOT_FILE_SIZE: - lease_parse_u16(option, len, &lease->boot_file_size, 0); - + case SD_DHCP_OPTION_RENEWAL_T1_TIME: + r = lease_parse_u32(option, len, &lease->t1, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse T1 time, ignoring: %m"); break; - case DHCP_OPTION_DOMAIN_NAME: - { - _cleanup_free_ char *domainname = NULL; - char *e; + case SD_DHCP_OPTION_REBINDING_T2_TIME: + r = lease_parse_u32(option, len, &lease->t2, 1); + if (r < 0) + log_debug_errno(r, "Failed to parse T2 time, ignoring: %m"); + break; - r = lease_parse_string(option, len, &domainname); + case SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE: + r = lease_parse_classless_routes( + option, len, + &lease->static_route, + &lease->static_route_size, + &lease->static_route_allocated); if (r < 0) - return r; + log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); + break; - /* Chop off trailing dot of domain name that some DHCP - * servers send us back. Internally we want to store - * host names without trailing dots and - * host_name_is_valid() doesn't accept them. */ - e = endswith(domainname, "."); - if (e) - *e = 0; + case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: { + _cleanup_free_ char *tz = NULL; - if (is_localhost(domainname)) - break; + r = lease_parse_string(option, len, &tz); + if (r < 0) { + log_debug_errno(r, "Failed to parse timezone option, ignoring: %m"); + return 0; + } - r = dns_name_is_valid(domainname); - if (r <= 0) { - if (r < 0) - log_error_errno(r, "Failed to validate domain name: %s: %m", domainname); - if (r == 0) - log_warning("Domain name is not valid, ignoring: %s", domainname); - break; + if (!timezone_is_valid(tz)) { + log_debug_errno(r, "Timezone is not valid, ignoring: %m"); + return 0; } - free(lease->domainname); - lease->domainname = domainname; - domainname = NULL; + free(lease->timezone); + lease->timezone = tz; + tz = NULL; break; } - case DHCP_OPTION_HOST_NAME: - { - _cleanup_free_ char *hostname = NULL; - char *e; - r = lease_parse_string(option, len, &hostname); - if (r < 0) - return r; + case SD_DHCP_OPTION_VENDOR_SPECIFIC: - e = endswith(hostname, "."); - if (e) - *e = 0; + if (len <= 0) + lease->vendor_specific = mfree(lease->vendor_specific); + else { + void *p; - if (!hostname_is_valid(hostname) || is_localhost(hostname)) - break; + p = memdup(option, len); + if (!p) + return -ENOMEM; - free(lease->hostname); - lease->hostname = hostname; - hostname = NULL; + free(lease->vendor_specific); + lease->vendor_specific = p; + } + lease->vendor_specific_len = len; break; - } - case DHCP_OPTION_ROOT_PATH: - r = lease_parse_string(option, len, &lease->root_path); + + case SD_DHCP_OPTION_PRIVATE_BASE ... SD_DHCP_OPTION_PRIVATE_LAST: + r = dhcp_lease_insert_private_option(lease, code, option, len); if (r < 0) return r; break; - case DHCP_OPTION_RENEWAL_T1_TIME: - lease_parse_u32(option, len, &lease->t1, 1); - - break; - - case DHCP_OPTION_REBINDING_T2_TIME: - lease_parse_u32(option, len, &lease->t2, 1); - + default: + log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code); break; + } - case DHCP_OPTION_ENABLE_IP_FORWARDING: - lease_parse_bool(option, len, &lease->ip_forward); - - break; - - case DHCP_OPTION_ENABLE_IP_FORWARDING_NL: - lease_parse_bool(option, len, &lease->ip_forward_non_local); - - break; + return 0; +} - case DHCP_OPTION_CLASSLESS_STATIC_ROUTE: - r = lease_parse_classless_routes(option, len, &lease->static_route, &lease->static_route_size, - &lease->static_route_allocated); - if (r < 0) - return r; +int dhcp_lease_insert_private_option(sd_dhcp_lease *lease, uint8_t tag, const void *data, uint8_t len) { + struct sd_dhcp_raw_option *cur, *option; - break; + assert(lease); - case DHCP_OPTION_VENDOR_SPECIFIC: - if (len >= 1) { - free(lease->vendor_specific); - lease->vendor_specific = memdup(option, len); - if (!lease->vendor_specific) - return -ENOMEM; - lease->vendor_specific_len = len; + LIST_FOREACH(options, cur, lease->private_options) { + if (tag < cur->tag) + break; + if (tag == cur->tag) { + log_debug("Ignoring duplicate option, tagged %i.", tag); + return 0; } + } + + option = new(struct sd_dhcp_raw_option, 1); + if (!option) + return -ENOMEM; - break; + option->tag = tag; + option->length = len; + option->data = memdup(data, len); + if (!option->data) { + free(option); + return -ENOMEM; } + LIST_INSERT_BEFORE(options, lease->private_options, cur, option); return 0; } @@ -620,22 +717,24 @@ int dhcp_lease_new(sd_dhcp_lease **ret) { return -ENOMEM; lease->router = INADDR_ANY; - lease->n_ref = REFCNT_INIT; + lease->n_ref = 1; *ret = lease; return 0; } -int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { +int dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { _cleanup_free_ char *temp_path = NULL; _cleanup_fclose_ FILE *f = NULL; + struct sd_dhcp_raw_option *option; struct in_addr address; const struct in_addr *addresses; - const uint8_t *client_id, *data; + const void *client_id, *data; size_t client_id_len, data_len; const char *string; uint16_t mtu; - struct sd_dhcp_route *routes; + _cleanup_free_ sd_dhcp_route **routes = NULL; + uint32_t t1, t2, lifetime; int r; assert(lease); @@ -643,23 +742,20 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { r = fopen_temporary(lease_file, &f, &temp_path); if (r < 0) - goto finish; + goto fail; fchmod(fileno(f), 0644); - r = sd_dhcp_lease_get_address(lease, &address); - if (r < 0) - goto finish; - fprintf(f, - "# This is private data. Do not parse.\n" - "ADDRESS=%s\n", inet_ntoa(address)); + "# This is private data. Do not parse.\n"); - r = sd_dhcp_lease_get_netmask(lease, &address); - if (r < 0) - goto finish; + r = sd_dhcp_lease_get_address(lease, &address); + if (r >= 0) + fprintf(f, "ADDRESS=%s\n", inet_ntoa(address)); - fprintf(f, "NETMASK=%s\n", inet_ntoa(address)); + r = sd_dhcp_lease_get_netmask(lease, &address); + if (r >= 0) + fprintf(f, "NETMASK=%s\n", inet_ntoa(address)); r = sd_dhcp_lease_get_router(lease, &address); if (r >= 0) @@ -667,28 +763,45 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { r = sd_dhcp_lease_get_server_identifier(lease, &address); if (r >= 0) - fprintf(f, "SERVER_ADDRESS=%s\n", - inet_ntoa(address)); + fprintf(f, "SERVER_ADDRESS=%s\n", inet_ntoa(address)); r = sd_dhcp_lease_get_next_server(lease, &address); if (r >= 0) fprintf(f, "NEXT_SERVER=%s\n", inet_ntoa(address)); + r = sd_dhcp_lease_get_broadcast(lease, &address); + if (r >= 0) + fprintf(f, "BROADCAST=%s\n", inet_ntoa(address)); + r = sd_dhcp_lease_get_mtu(lease, &mtu); if (r >= 0) fprintf(f, "MTU=%" PRIu16 "\n", mtu); - fputs("DNS=", f); - r = sd_dhcp_lease_get_dns(lease, &addresses); + r = sd_dhcp_lease_get_t1(lease, &t1); if (r >= 0) + fprintf(f, "T1=%" PRIu32 "\n", t1); + + r = sd_dhcp_lease_get_t2(lease, &t2); + if (r >= 0) + fprintf(f, "T2=%" PRIu32 "\n", t2); + + r = sd_dhcp_lease_get_lifetime(lease, &lifetime); + if (r >= 0) + fprintf(f, "LIFETIME=%" PRIu32 "\n", lifetime); + + r = sd_dhcp_lease_get_dns(lease, &addresses); + if (r > 0) { + fputs("DNS=", f); serialize_in_addrs(f, addresses, r); - fputs("\n", f); + fputs("\n", f); + } - fputs("NTP=", f); r = sd_dhcp_lease_get_ntp(lease, &addresses); - if (r >= 0) + if (r > 0) { + fputs("NTP=", f); serialize_in_addrs(f, addresses, r); - fputs("\n", f); + fputs("\n", f); + } r = sd_dhcp_lease_get_domainname(lease, &string); if (r >= 0) @@ -703,17 +816,21 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { fprintf(f, "ROOT_PATH=%s\n", string); r = sd_dhcp_lease_get_routes(lease, &routes); - if (r >= 0) + if (r > 0) serialize_dhcp_routes(f, "ROUTES", routes, r); + r = sd_dhcp_lease_get_timezone(lease, &string); + if (r >= 0) + fprintf(f, "TIMEZONE=%s\n", string); + r = sd_dhcp_lease_get_client_id(lease, &client_id, &client_id_len); if (r >= 0) { - _cleanup_free_ char *client_id_hex; + _cleanup_free_ char *client_id_hex = NULL; client_id_hex = hexmem(client_id, client_id_len); if (!client_id_hex) { r = -ENOMEM; - goto finish; + goto fail; } fprintf(f, "CLIENTID=%s\n", client_id_hex); } @@ -725,37 +842,60 @@ int sd_dhcp_lease_save(sd_dhcp_lease *lease, const char *lease_file) { option_hex = hexmem(data, data_len); if (!option_hex) { r = -ENOMEM; - goto finish; + goto fail; } fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); } - r = 0; + LIST_FOREACH(options, option, lease->private_options) { + char key[strlen("OPTION_000")+1]; - fflush(f); + xsprintf(key, "OPTION_%" PRIu8, option->tag); + r = serialize_dhcp_option(f, key, option->data, option->length); + if (r < 0) + goto fail; + } - if (ferror(f) || rename(temp_path, lease_file) < 0) { + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, lease_file) < 0) { r = -errno; - unlink(lease_file); - unlink(temp_path); + goto fail; } -finish: - if (r < 0) - log_error_errno(r, "Failed to save lease data %s: %m", lease_file); + return 0; - return r; +fail: + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save lease data %s: %m", lease_file); } -int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { - _cleanup_dhcp_lease_unref_ sd_dhcp_lease *lease = NULL; - _cleanup_free_ char *address = NULL, *router = NULL, *netmask = NULL, - *server_address = NULL, *next_server = NULL, - *dns = NULL, *ntp = NULL, *mtu = NULL, - *routes = NULL, *client_id_hex = NULL, - *vendor_specific_hex = NULL; - struct in_addr addr; - int r; +int dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { + + _cleanup_(sd_dhcp_lease_unrefp) sd_dhcp_lease *lease = NULL; + _cleanup_free_ char + *address = NULL, + *router = NULL, + *netmask = NULL, + *server_address = NULL, + *next_server = NULL, + *broadcast = NULL, + *dns = NULL, + *ntp = NULL, + *mtu = NULL, + *routes = NULL, + *client_id_hex = NULL, + *vendor_specific_hex = NULL, + *lifetime = NULL, + *t1 = NULL, + *t2 = NULL, + *options[SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE + 1] = {}; + + int r, i; assert(lease_file); assert(ret); @@ -770,6 +910,7 @@ int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { "NETMASK", &netmask, "SERVER_IDENTIFIER", &server_address, "NEXT_SERVER", &next_server, + "BROADCAST", &broadcast, "DNS", &dns, "NTP", &ntp, "MTU", &mtu, @@ -778,94 +919,162 @@ int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { "ROOT_PATH", &lease->root_path, "ROUTES", &routes, "CLIENTID", &client_id_hex, + "TIMEZONE", &lease->timezone, "VENDOR_SPECIFIC", &vendor_specific_hex, + "LIFETIME", &lifetime, + "T1", &t1, + "T2", &t2, + "OPTION_224", &options[0], + "OPTION_225", &options[1], + "OPTION_226", &options[2], + "OPTION_227", &options[3], + "OPTION_228", &options[4], + "OPTION_229", &options[5], + "OPTION_230", &options[6], + "OPTION_231", &options[7], + "OPTION_232", &options[8], + "OPTION_233", &options[9], + "OPTION_234", &options[10], + "OPTION_235", &options[11], + "OPTION_236", &options[12], + "OPTION_237", &options[13], + "OPTION_238", &options[14], + "OPTION_239", &options[15], + "OPTION_240", &options[16], + "OPTION_241", &options[17], + "OPTION_242", &options[18], + "OPTION_243", &options[19], + "OPTION_244", &options[20], + "OPTION_245", &options[21], + "OPTION_246", &options[22], + "OPTION_247", &options[23], + "OPTION_248", &options[24], + "OPTION_249", &options[25], + "OPTION_250", &options[26], + "OPTION_251", &options[27], + "OPTION_252", &options[28], + "OPTION_253", &options[29], + "OPTION_254", &options[30], NULL); - if (r < 0) { - if (r == -ENOENT) - return 0; - - return log_error_errno(r, "Failed to read %s: %m", lease_file); - } - - r = inet_pton(AF_INET, address, &addr); if (r < 0) return r; - lease->address = addr.s_addr; + if (address) { + r = inet_pton(AF_INET, address, &lease->address); + if (r <= 0) + log_debug("Failed to parse address %s, ignoring.", address); + } if (router) { - r = inet_pton(AF_INET, router, &addr); - if (r < 0) - return r; - - lease->router = addr.s_addr; + r = inet_pton(AF_INET, router, &lease->router); + if (r <= 0) + log_debug("Failed to parse router %s, ignoring.", router); } - r = inet_pton(AF_INET, netmask, &addr); - if (r < 0) - return r; - - lease->subnet_mask = addr.s_addr; + if (netmask) { + r = inet_pton(AF_INET, netmask, &lease->subnet_mask); + if (r <= 0) + log_debug("Failed to parse netmask %s, ignoring.", netmask); + else + lease->have_subnet_mask = true; + } if (server_address) { - r = inet_pton(AF_INET, server_address, &addr); - if (r < 0) - return r; - - lease->server_address = addr.s_addr; + r = inet_pton(AF_INET, server_address, &lease->server_address); + if (r <= 0) + log_debug("Failed to parse server address %s, ignoring.", server_address); } if (next_server) { - r = inet_pton(AF_INET, next_server, &addr); - if (r < 0) - return r; + r = inet_pton(AF_INET, next_server, &lease->next_server); + if (r <= 0) + log_debug("Failed to parse next server %s, ignoring.", next_server); + } - lease->next_server = addr.s_addr; + if (broadcast) { + r = inet_pton(AF_INET, broadcast, &lease->broadcast); + if (r <= 0) + log_debug("Failed to parse broadcast address %s, ignoring.", broadcast); + else + lease->have_broadcast = true; } if (dns) { r = deserialize_in_addrs(&lease->dns, dns); if (r < 0) - return r; - - lease->dns_size = r; + log_debug_errno(r, "Failed to deserialize DNS servers %s, ignoring: %m", dns); + else + lease->dns_size = r; } if (ntp) { r = deserialize_in_addrs(&lease->ntp, ntp); if (r < 0) - return r; - - lease->ntp_size = r; + log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp); + else + lease->ntp_size = r; } if (mtu) { - uint16_t u; - if (sscanf(mtu, "%" SCNu16, &u) > 0) - lease->mtu = u; + r = safe_atou16(mtu, &lease->mtu); + if (r < 0) + log_debug_errno(r, "Failed to parse MTU %s, ignoring: %m", mtu); } if (routes) { - r = deserialize_dhcp_routes(&lease->static_route, &lease->static_route_size, - &lease->static_route_allocated, routes); + r = deserialize_dhcp_routes( + &lease->static_route, + &lease->static_route_size, + &lease->static_route_allocated, + routes); if (r < 0) - return r; + log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes); } - if (client_id_hex) { - if (strlen(client_id_hex) % 2) - return -EINVAL; + if (lifetime) { + r = safe_atou32(lifetime, &lease->lifetime); + if (r < 0) + log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime); + } - r = unhexmem(client_id_hex, strlen(client_id_hex), (void**) &lease->client_id, &lease->client_id_len); + if (t1) { + r = safe_atou32(t1, &lease->t1); if (r < 0) - return r; + log_debug_errno(r, "Failed to parse T1 %s, ignoring: %m", t1); + } + + if (t2) { + r = safe_atou32(t2, &lease->t2); + if (r < 0) + log_debug_errno(r, "Failed to parse T2 %s, ignoring: %m", t2); + } + + if (client_id_hex) { + r = deserialize_dhcp_option(&lease->client_id, &lease->client_id_len, client_id_hex); + if (r < 0) + log_debug_errno(r, "Failed to parse client ID %s, ignoring: %m", client_id_hex); } if (vendor_specific_hex) { - if (strlen(vendor_specific_hex) % 2) - return -EINVAL; + r = deserialize_dhcp_option(&lease->vendor_specific, &lease->vendor_specific_len, vendor_specific_hex); + if (r < 0) + log_debug_errno(r, "Failed to parse vendor specific data %s, ignoring: %m", vendor_specific_hex); + } + + for (i = 0; i <= SD_DHCP_OPTION_PRIVATE_LAST - SD_DHCP_OPTION_PRIVATE_BASE; i++) { + _cleanup_free_ void *data = NULL; + size_t len; + + if (!options[i]) + continue; + + r = deserialize_dhcp_option(&data, &len, options[i]); + if (r < 0) { + log_debug_errno(r, "Failed to parse private DHCP option %s, ignoring: %m", options[i]); + continue; + } - r = unhexmem(vendor_specific_hex, strlen(vendor_specific_hex), (void**) &lease->vendor_specific, &lease->vendor_specific_len); + r = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); if (r < 0) return r; } @@ -877,12 +1086,14 @@ int sd_dhcp_lease_load(sd_dhcp_lease **ret, const char *lease_file) { } int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { - struct in_addr address; - struct in_addr mask; + struct in_addr address, mask; int r; assert(lease); + if (lease->address == 0) + return -ENODATA; + address.s_addr = lease->address; /* fall back to the default subnet masks based on address class */ @@ -891,35 +1102,77 @@ int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { return r; lease->subnet_mask = mask.s_addr; + lease->have_subnet_mask = true; return 0; } -int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const uint8_t **client_id, - size_t *client_id_len) { +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len) { assert_return(lease, -EINVAL); assert_return(client_id, -EINVAL); assert_return(client_id_len, -EINVAL); + if (!lease->client_id) + return -ENODATA; + *client_id = lease->client_id; *client_id_len = lease->client_id_len; + return 0; } -int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const uint8_t *client_id, - size_t client_id_len) { +int dhcp_lease_set_client_id(sd_dhcp_lease *lease, const void *client_id, size_t client_id_len) { assert_return(lease, -EINVAL); - assert_return((!client_id && !client_id_len) || - (client_id && client_id_len), -EINVAL); + assert_return(client_id || client_id_len <= 0, -EINVAL); + + if (client_id_len <= 0) + lease->client_id = mfree(lease->client_id); + else { + void *p; - free (lease->client_id); - lease->client_id = NULL; - lease->client_id_len = 0; + p = memdup(client_id, client_id_len); + if (!p) + return -ENOMEM; - if (client_id) { - lease->client_id = memdup (client_id, client_id_len); + free(lease->client_id); + lease->client_id = p; lease->client_id_len = client_id_len; } return 0; } + +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **tz) { + assert_return(lease, -EINVAL); + assert_return(tz, -EINVAL); + + if (!lease->timezone) + return -ENODATA; + + *tz = lease->timezone; + return 0; +} + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination) { + assert_return(route, -EINVAL); + assert_return(destination, -EINVAL); + + *destination = route->dst_addr; + return 0; +} + +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length) { + assert_return(route, -EINVAL); + assert_return(length, -EINVAL); + + *length = route->dst_prefixlen; + return 0; +} + +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway) { + assert_return(route, -EINVAL); + assert_return(gateway, -EINVAL); + + *gateway = route->gw_addr; + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp-server.c b/src/libsystemd-network/sd-dhcp-server.c index cc5e032344..11ee2e252e 100644 --- a/src/libsystemd-network/sd-dhcp-server.c +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -22,75 +20,107 @@ #include <sys/ioctl.h> -#include "siphash24.h" - #include "sd-dhcp-server.h" -#include "dhcp-server-internal.h" + +#include "alloc-util.h" #include "dhcp-internal.h" +#include "dhcp-server-internal.h" +#include "fd-util.h" +#include "in-addr-util.h" +#include "siphash24.h" +#include "string-util.h" +#include "unaligned.h" -#define DHCP_DEFAULT_LEASE_TIME 3600 /* one hour */ +#define DHCP_DEFAULT_LEASE_TIME_USEC USEC_PER_HOUR +#define DHCP_MAX_LEASE_TIME_USEC (USEC_PER_HOUR*12) + +/* configures the server's address and subnet, and optionally the pool's size and offset into the subnet + * the whole pool must fit into the subnet, and may not contain the first (any) nor last (broadcast) address + * moreover, the server's own address may be in the pool, and is in that case reserved in order not to + * accidentally hand it out */ +int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size) { + struct in_addr netmask_addr; + be32_t netmask; + uint32_t server_off, broadcast_off, size_max; -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); - assert_return(!server->bound_leases, -EBUSY); + assert_return(address->s_addr != INADDR_ANY, -EINVAL); + assert_return(prefixlen <= 32, -ERANGE); + assert_return(server->address == INADDR_ANY, -EBUSY); + + assert_se(in_addr_prefixlen_to_netmask(&netmask_addr, prefixlen)); + netmask = netmask_addr.s_addr; + + server_off = be32toh(address->s_addr & ~netmask); + broadcast_off = be32toh(~netmask); + + /* the server address cannot be the subnet address */ + assert_return(server_off != 0, -ERANGE); + + /* nor the broadcast address */ + assert_return(server_off != broadcast_off, -ERANGE); + + /* 0 offset means we should set a default, we skip the first (subnet) address + and take the next one */ + if (offset == 0) + offset = 1; + + size_max = (broadcast_off + 1) /* the number of addresses in the subnet */ + - offset /* exclude the addresses before the offset */ + - 1; /* exclude the last (broadcast) address */ + + /* The pool must contain at least one address */ + assert_return(size_max >= 1, -ERANGE); + + if (size != 0) + assert_return(size <= size_max, -ERANGE); + else + size = size_max; server->bound_leases = new0(DHCPLease*, size); if (!server->bound_leases) return -ENOMEM; - server->pool_start = address->s_addr; + server->pool_offset = offset; server->pool_size = size; - return 0; -} - -int sd_dhcp_server_set_address(sd_dhcp_server *server, struct in_addr *address, - unsigned char prefixlen) { - assert_return(server, -EINVAL); - assert_return(address, -EINVAL); - assert_return(address->s_addr, -EINVAL); - assert_return(prefixlen <= 32, -ERANGE); - assert_return(server->address == htobe32(INADDR_ANY), -EBUSY); - assert_return(server->netmask == htobe32(INADDR_ANY), -EBUSY); - server->address = address->s_addr; - server->netmask = htobe32(0xfffffffflu << (32 - prefixlen)); + server->netmask = netmask; + server->subnet = address->s_addr & netmask; + + if (server_off >= offset && server_off - offset < size) + server->bound_leases[server_off - offset] = &server->invalid_lease; return 0; } -bool sd_dhcp_server_is_running(sd_dhcp_server *server) { - assert_return(server, -EINVAL); +int sd_dhcp_server_is_running(sd_dhcp_server *server) { + assert_return(server, false); return !!server->receive_message; } sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server) { - if (server) - assert_se(REFCNT_INC(server->n_ref) >= 2); + + if (!server) + return NULL; + + assert(server->n_ref >= 1); + server->n_ref++; return server; } -unsigned long client_id_hash_func(const void *p, - const uint8_t hash_key[HASH_KEY_SIZE]) { - uint64_t u; +void client_id_hash_func(const void *p, struct siphash *state) { 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; + siphash24_compress(&id->length, sizeof(id->length), state); + siphash24_compress(id->data, id->length, state); } int client_id_compare_func(const void *_a, const void *_b) { @@ -127,7 +157,10 @@ sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { if (!server) return NULL; - if (REFCNT_DEC(server->n_ref) > 0) + assert(server->n_ref >= 1); + server->n_ref--; + + if (server->n_ref > 0) return NULL; log_dhcp_server(server, "UNREF"); @@ -136,6 +169,10 @@ sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { sd_event_unref(server->event); + free(server->timezone); + free(server->dns); + free(server->ntp); + while ((lease = hashmap_steal_first(server->leases_by_client_id))) dhcp_lease_free(lease); hashmap_free(server->leases_by_client_id); @@ -147,7 +184,7 @@ sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { } int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { - _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL; + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; assert_return(ret, -EINVAL); assert_return(ifindex > 0, -EINVAL); @@ -156,13 +193,15 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { if (!server) return -ENOMEM; - server->n_ref = REFCNT_INIT; + server->n_ref = 1; server->fd_raw = -1; server->fd = -1; server->address = htobe32(INADDR_ANY); server->netmask = htobe32(INADDR_ANY); - server->index = ifindex; + server->ifindex = ifindex; server->leases_by_client_id = hashmap_new(&client_id_hash_ops); + server->default_lease_time = DIV_ROUND_UP(DHCP_DEFAULT_LEASE_TIME_USEC, USEC_PER_SEC); + server->max_lease_time = DIV_ROUND_UP(DHCP_MAX_LEASE_TIME_USEC, USEC_PER_SEC); *ret = server; server = NULL; @@ -170,8 +209,7 @@ int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { return 0; } -int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, - int priority) { +int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, int64_t priority) { int r; assert_return(server, -EINVAL); @@ -222,14 +260,13 @@ 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_protocol = htobe16(ETH_P_IP), + .ll.sll_ifindex = server->ifindex, .ll.sll_halen = ETH_ALEN, }; - int r; assert(server); - assert(server->index > 0); + assert(server->ifindex > 0); assert(server->address); assert(packet); assert(len > sizeof(DHCPPacket)); @@ -240,18 +277,15 @@ static int dhcp_server_send_unicast_raw(sd_dhcp_server *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; + return dhcp_network_send_raw_socket(server->fd_raw, &link, packet, len); } static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, + uint16_t destination_port, DHCPMessage *message, size_t len) { union sockaddr_union dest = { .in.sin_family = AF_INET, - .in.sin_port = htobe16(DHCP_PORT_CLIENT), + .in.sin_port = htobe16(destination_port), .in.sin_addr.s_addr = destination, }; struct iovec iov = { @@ -290,7 +324,7 @@ static int dhcp_server_send_udp(sd_dhcp_server *server, be32_t destination, pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg); assert(pktinfo); - pktinfo->ipi_ifindex = server->index; + pktinfo->ipi_ifindex = server->ifindex; pktinfo->ipi_spec_dst.s_addr = server->address; r = sendmsg(server->fd, &msg, 0); @@ -310,6 +344,7 @@ int dhcp_server_send_packet(sd_dhcp_server *server, DHCPRequest *req, DHCPPacket *packet, int type, size_t optoffset) { be32_t destination = INADDR_ANY; + uint16_t destination_port = DHCP_PORT_CLIENT; int r; assert(server); @@ -319,13 +354,13 @@ int dhcp_server_send_packet(sd_dhcp_server *server, assert(packet); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, - DHCP_OPTION_SERVER_IDENTIFIER, + SD_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); + SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; @@ -354,17 +389,19 @@ int dhcp_server_send_packet(sd_dhcp_server *server, */ if (req->message->giaddr) { destination = req->message->giaddr; + destination_port = DHCP_PORT_SERVER; if (type == DHCP_NAK) packet->dhcp.flags = htobe16(0x8000); } else if (req->message->ciaddr && type != DHCP_NAK) destination = req->message->ciaddr; if (destination != INADDR_ANY) - return dhcp_server_send_udp(server, destination, &packet->dhcp, + return dhcp_server_send_udp(server, destination, + destination_port, &packet->dhcp, sizeof(DHCPMessage) + optoffset); else if (requested_broadcast(req) || type == DHCP_NAK) return dhcp_server_send_udp(server, INADDR_BROADCAST, - &packet->dhcp, + destination_port, &packet->dhcp, sizeof(DHCPMessage) + optoffset); else /* we cannot send UDP packet to specific MAC address when the @@ -422,20 +459,22 @@ static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, lease_time = htobe32(req->lifetime); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_ROUTER, 4, &server->address); - if (r < 0) - return r; + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, &server->address); + if (r < 0) + return r; + } r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset); if (r < 0) @@ -459,20 +498,49 @@ static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, lease_time = htobe32(req->lifetime); r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME, 4, &lease_time); if (r < 0) return r; r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); if (r < 0) return r; - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - DHCP_OPTION_ROUTER, 4, &server->address); - if (r < 0) - return r; + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, &server->address); + if (r < 0) + return r; + } + + if (server->n_dns > 0) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + sizeof(struct in_addr) * server->n_dns, server->dns); + if (r < 0) + return r; + } + + if (server->n_ntp > 0) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_NTP_SERVER, + sizeof(struct in_addr) * server->n_ntp, server->ntp); + if (r < 0) + return r; + } + + if (server->timezone) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE, + strlen(server->timezone), server->timezone); + if (r < 0) + return r; + } r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset); if (r < 0) @@ -490,11 +558,7 @@ static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) { if (r < 0) return r; - r = dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); - if (r < 0) - return r; - - return 0; + return dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); } static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, @@ -518,13 +582,14 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, return r; r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, - &optoffset, 0, DHCP_OPTION_END, 0, NULL); + &optoffset, 0, SD_DHCP_OPTION_END, 0, NULL); if (r < 0) return r; memcpy(&packet->dhcp.chaddr, chaddr, ETH_ALEN); - r = dhcp_server_send_udp(server, address, &packet->dhcp, + r = dhcp_server_send_udp(server, address, DHCP_PORT_CLIENT, + &packet->dhcp, sizeof(DHCPMessage) + optoffset); if (r < 0) return r; @@ -532,29 +597,28 @@ static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, return 0; } -static int parse_request(uint8_t code, uint8_t len, const uint8_t *option, - void *user_data) { - DHCPRequest *req = user_data; +static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { + DHCPRequest *req = userdata; assert(req); switch(code) { - case DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: if (len == 4) - req->lifetime = be32toh(*(be32_t*)option); + req->lifetime = unaligned_read_be32(option); break; - case DHCP_OPTION_REQUESTED_IP_ADDRESS: + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: if (len == 4) - req->requested_ip = *(be32_t*)option; + memcpy(&req->requested_ip, option, sizeof(be32_t)); break; - case DHCP_OPTION_SERVER_IDENTIFIER: + case SD_DHCP_OPTION_SERVER_IDENTIFIER: if (len == 4) - req->server_id = *(be32_t*)option; + memcpy(&req->server_id, option, sizeof(be32_t)); break; - case DHCP_OPTION_CLIENT_IDENTIFIER: + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: if (len >= 2) { uint8_t *data; @@ -568,10 +632,10 @@ static int parse_request(uint8_t code, uint8_t len, const uint8_t *option, } break; - case DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: - if (len == 2) - req->max_optlen = be16toh(*(be16_t*)option) - - - sizeof(DHCPPacket); + case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + + if (len == 2 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) + req->max_optlen = unaligned_read_be16(option) - sizeof(DHCPPacket); break; } @@ -590,7 +654,7 @@ static void dhcp_request_free(DHCPRequest *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) { +static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { assert(req); assert(message); @@ -599,23 +663,27 @@ static int ensure_sane_request(DHCPRequest *req, DHCPMessage *message) { /* set client id based on MAC address if client did not send an explicit one */ if (!req->client_id.data) { - uint8_t *data; + void *data; - data = new0(uint8_t, ETH_ALEN + 1); + data = malloc0(ETH_ALEN + 1); if (!data) return -ENOMEM; + ((uint8_t*) data)[0] = 0x01; + memcpy((uint8_t*) data + 1, &message->chaddr, ETH_ALEN); + 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; - if (!req->lifetime) - req->lifetime = DHCP_DEFAULT_LEASE_TIME; + if (req->lifetime <= 0) + req->lifetime = MAX(1ULL, server->default_lease_time); + + if (server->max_lease_time > 0 && req->lifetime > server->max_lease_time) + req->lifetime = server->max_lease_time; return 0; } @@ -626,17 +694,19 @@ static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) { 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; + if (be32toh(requested_ip) < (be32toh(server->subnet) | server->pool_offset) || + be32toh(requested_ip) >= (be32toh(server->subnet) | (server->pool_offset + server->pool_size))) + return -ERANGE; - return be32toh(requested_ip) - be32toh(server->pool_start); + return be32toh(requested_ip & ~server->netmask) - server->pool_offset; } +#define HASH_KEY SD_ID128_MAKE(0d,1d,fe,bd,f1,24,bd,b3,47,f1,dd,6e,73,21,93,30) + int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, size_t length) { _cleanup_dhcp_request_free_ DHCPRequest *req = NULL; + _cleanup_free_ char *error_message = NULL; DHCPLease *existing_lease; int type, r; @@ -652,11 +722,11 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, if (!req) return -ENOMEM; - type = dhcp_option_parse(message, length, parse_request, req); + type = dhcp_option_parse(message, length, parse_request, req, &error_message); if (type < 0) return 0; - r = ensure_sane_request(req, message); + r = ensure_sane_request(server, req, message); if (r < 0) /* this only fails on critical errors */ return r; @@ -665,8 +735,8 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, &req->client_id); switch(type) { - case DHCP_DISCOVER: - { + + case DHCP_DISCOVER: { be32_t address = INADDR_ANY; unsigned i; @@ -681,12 +751,25 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, if (existing_lease) address = existing_lease->address; else { + struct siphash state; + uint64_t hash; + uint32_t next_offer; + + /* even with no persistence of leases, we try to offer the same client + the same IP address. we do this by using the hash of the client id + as the offset into the pool of leases when finding the next free one */ + + siphash24_init(&state, HASH_KEY.bytes); + client_id_hash_func(&req->client_id, &state); + hash = htole64(siphash24_finalize(&state)); + next_offer = hash % server->pool_size; + for (i = 0; i < server->pool_size; i++) { - if (!server->bound_leases[server->next_offer]) { - address = htobe32(be32toh(server->pool_start) + server->next_offer); + if (!server->bound_leases[next_offer]) { + address = server->subnet | htobe32(server->pool_offset + next_offer); break; } else - server->next_offer = (server->next_offer + 1) % server->pool_size; + next_offer = (next_offer + 1) % server->pool_size; } } @@ -709,16 +792,13 @@ 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)); + log_dhcp_server(server, "DECLINE (0x%x): %s", be32toh(req->message->xid), strna(error_message)); /* TODO: make sure we don't offer this address again */ return 1; - break; - case DHCP_REQUEST: - { + case DHCP_REQUEST: { be32_t address; bool init_reboot = false; int pool_offset; @@ -796,8 +876,12 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, r = sd_event_now(server->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - time_now = now(clock_boottime_or_monotonic()); + if (r < 0) { + if (!existing_lease) + dhcp_lease_free(lease); + return r; + } + lease->expiration = req->lifetime * USEC_PER_SEC + time_now; r = server_send_ack(server, req, address); @@ -836,6 +920,7 @@ int dhcp_server_handle_message(sd_dhcp_server *server, DHCPMessage *message, break; } + case DHCP_RELEASE: { int pool_offset; @@ -879,17 +964,15 @@ static int server_receive_message(sd_event_source *s, int fd, .msg_controllen = sizeof(cmsgbuf), }; struct cmsghdr *cmsg; - int buflen = 0, len, r; + ssize_t buflen, len; assert(server); - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0) - return r; + buflen = next_datagram_size_fd(fd); if (buflen < 0) - return -EIO; + return buflen; - message = malloc0(buflen); + message = malloc(buflen); if (!message) return -ENOMEM; @@ -897,9 +980,12 @@ static int server_receive_message(sd_event_source *s, int fd, iov.iov_len = buflen; len = recvmsg(fd, &msg, 0); - if (len < buflen) - return 0; - else if ((size_t)len < sizeof(DHCPMessage)) + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return -errno; + } else if ((size_t)len < sizeof(DHCPMessage)) return 0; CMSG_FOREACH(cmsg, &msg) { @@ -910,7 +996,7 @@ static int server_receive_message(sd_event_source *s, int fd, /* TODO figure out if this can be done as a filter on * the socket, like for IPv6 */ - if (server->index != info->ipi_ifindex) + if (server->ifindex != info->ipi_ifindex) return 0; break; @@ -975,7 +1061,7 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { for (i = 0; i < server->pool_size; i++) { DHCPLease *lease = server->bound_leases[i]; - if (!lease) + if (!lease || lease == &server->invalid_lease) continue; r = server_send_forcerenew(server, lease->address, @@ -989,3 +1075,102 @@ int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { return r; } + +int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *tz) { + int r; + + assert_return(server, -EINVAL); + assert_return(timezone_is_valid(tz), -EINVAL); + + if (streq_ptr(tz, server->timezone)) + return 0; + + r = free_and_strdup(&server->timezone, tz); + if (r < 0) + return r; + + return 1; +} + +int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t) { + assert_return(server, -EINVAL); + + if (t == server->max_lease_time) + return 0; + + server->max_lease_time = t; + return 1; +} + +int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t) { + assert_return(server, -EINVAL); + + if (t == server->default_lease_time) + return 0; + + server->default_lease_time = t; + return 1; +} + +int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr dns[], unsigned n) { + assert_return(server, -EINVAL); + assert_return(dns || n <= 0, -EINVAL); + + if (server->n_dns == n && + memcmp(server->dns, dns, sizeof(struct in_addr) * n) == 0) + return 0; + + if (n <= 0) { + server->dns = mfree(server->dns); + server->n_dns = 0; + } else { + struct in_addr *c; + + c = newdup(struct in_addr, dns, n); + if (!c) + return -ENOMEM; + + free(server->dns); + server->dns = c; + server->n_dns = n; + } + + return 1; +} + +int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n) { + assert_return(server, -EINVAL); + assert_return(ntp || n <= 0, -EINVAL); + + if (server->n_ntp == n && + memcmp(server->ntp, ntp, sizeof(struct in_addr) * n) == 0) + return 0; + + if (n <= 0) { + server->ntp = mfree(server->ntp); + server->n_ntp = 0; + } else { + struct in_addr *c; + + c = newdup(struct in_addr, ntp, n); + if (!c) + return -ENOMEM; + + free(server->ntp); + server->ntp = c; + server->n_ntp = n; + } + + return 1; +} + +int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled) { + assert_return(server, -EINVAL); + + if (enabled == server->emit_router) + return 0; + + server->emit_router = enabled; + + return 1; +} diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c index 85162dc555..463fde401c 100644 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -1,9 +1,7 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. - Copyright (C) 2014 Intel Corporation. All rights reserved. + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. 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 @@ -24,28 +22,31 @@ #include <sys/ioctl.h> #include <linux/if_infiniband.h> -#include "udev.h" -#include "udev-util.h" -#include "util.h" -#include "refcnt.h" -#include "random-util.h" - -#include "network-internal.h" #include "sd-dhcp6-client.h" -#include "dhcp6-protocol.h" + +#include "alloc-util.h" +#include "dhcp-identifier.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" -#include "dhcp-identifier.h" +#include "dhcp6-protocol.h" +#include "fd-util.h" +#include "in-addr-util.h" +#include "network-internal.h" +#include "random-util.h" +#include "socket-util.h" +#include "string-table.h" +#include "util.h" #define MAX_MAC_ADDR_LEN INFINIBAND_ALEN struct sd_dhcp6_client { - RefCount n_ref; + unsigned n_ref; enum DHCP6State state; sd_event *event; int event_priority; - int index; + int ifindex; + struct in6_addr local_address; uint8_t mac_addr[MAX_MAC_ADDR_LEN]; size_t mac_addr_len; uint16_t arp_type; @@ -63,16 +64,17 @@ struct sd_dhcp6_client { uint8_t retransmit_count; sd_event_source *timeout_resend; sd_event_source *timeout_resend_expire; - sd_dhcp6_client_cb_t cb; + sd_dhcp6_client_callback_t callback; void *userdata; struct duid duid; size_t duid_len; }; static const uint16_t default_req_opts[] = { - DHCP6_OPTION_DNS_SERVERS, - DHCP6_OPTION_DOMAIN_LIST, - DHCP6_OPTION_NTP_SERVER, + SD_DHCP6_OPTION_DNS_SERVERS, + SD_DHCP6_OPTION_DOMAIN_LIST, + SD_DHCP6_OPTION_NTP_SERVER, + SD_DHCP6_OPTION_SNTP_SERVERS, }; const char * dhcp6_message_type_table[_DHCP6_MESSAGE_MAX] = { @@ -104,43 +106,61 @@ const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int); -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_dhcp6_client*, sd_dhcp6_client_unref); -#define _cleanup_dhcp6_client_unref_ _cleanup_(sd_dhcp6_client_unrefp) - #define DHCP6_CLIENT_DONT_DESTROY(client) \ - _cleanup_dhcp6_client_unref_ _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client) + _cleanup_(sd_dhcp6_client_unrefp) _unused_ sd_dhcp6_client *_dont_destroy_##client = sd_dhcp6_client_ref(client) static int client_start(sd_dhcp6_client *client, enum DHCP6State state); -int sd_dhcp6_client_set_callback(sd_dhcp6_client *client, - sd_dhcp6_client_cb_t cb, void *userdata) -{ +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata) { + assert_return(client, -EINVAL); - client->cb = cb; + client->callback = cb; client->userdata = userdata; return 0; } -int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index) -{ +int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) { + assert_return(client, -EINVAL); - assert_return(interface_index >= -1, -EINVAL); + assert_return(ifindex >= -1, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - client->index = interface_index; + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address) { + + assert_return(client, -EINVAL); + assert_return(local_address, -EINVAL); + assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL); + + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->local_address = *local_address; return 0; } -int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, - size_t addr_len, uint16_t arp_type) -{ +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, size_t addr_len, + uint16_t arp_type) { + assert_return(client, -EINVAL); assert_return(addr, -EINVAL); assert_return(addr_len > 0 && addr_len <= MAX_MAC_ADDR_LEN, -EINVAL); assert_return(arp_type > 0, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + if (arp_type == ARPHRD_ETHER) assert_return(addr_len == ETH_ALEN, -EINVAL); else if (arp_type == ARPHRD_INFINIBAND) @@ -159,60 +179,68 @@ int sd_dhcp6_client_set_mac(sd_dhcp6_client *client, const uint8_t *addr, return 0; } -static int client_ensure_duid(sd_dhcp6_client *client) -{ +static int client_ensure_duid(sd_dhcp6_client *client) { if (client->duid_len != 0) return 0; + return dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); } -int sd_dhcp6_client_set_duid(sd_dhcp6_client *client, uint16_t type, uint8_t *duid, - size_t duid_len) -{ +/** + * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + + int r; assert_return(client, -EINVAL); - assert_return(duid, -EINVAL); - assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -EINVAL); + assert_return(duid_len == 0 || duid != NULL, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - switch (type) { - case DHCP6_DUID_LLT: - if (duid_len <= sizeof(client->duid.llt)) - return -EINVAL; - break; - case DHCP6_DUID_EN: - if (duid_len != sizeof(client->duid.en)) - return -EINVAL; - break; - case DHCP6_DUID_LL: - if (duid_len <= sizeof(client->duid.ll)) - return -EINVAL; - break; - case DHCP6_DUID_UUID: - if (duid_len != sizeof(client->duid.uuid)) - return -EINVAL; - break; - default: - /* accept unknown type in order to be forward compatible */ - break; + if (duid != NULL) { + r = dhcp_validate_duid_len(duid_type, duid_len); + if (r < 0) + return r; } - client->duid.type = htobe16(type); - memcpy(&client->duid.raw.data, duid, duid_len); - client->duid_len = duid_len + sizeof(client->duid.type); + if (duid != NULL) { + client->duid.type = htobe16(duid_type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = sizeof(client->duid.type) + duid_len; + } else if (duid_type == DUID_TYPE_EN) { + r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + if (r < 0) + return r; + } else + return -EOPNOTSUPP; return 0; } -int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, - bool enabled) { +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ia_na.id = htobe32(iaid); + + return 0; +} + +int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); client->information_request = enabled; return 0; } -int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, - bool *enabled) { +int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, int *enabled) { assert_return(client, -EINVAL); assert_return(enabled, -EINVAL); @@ -221,18 +249,18 @@ int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, return 0; } -int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, - uint16_t option) { +int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, uint16_t option) { size_t t; assert_return(client, -EINVAL); assert_return(client->state == DHCP6_STATE_STOPPED, -EBUSY); switch(option) { - case DHCP6_OPTION_DNS_SERVERS: - case DHCP6_OPTION_DOMAIN_LIST: - case DHCP6_OPTION_SNTP_SERVERS: - case DHCP6_OPTION_NTP_SERVER: + + case SD_DHCP6_OPTION_DNS_SERVERS: + case SD_DHCP6_OPTION_DOMAIN_LIST: + case SD_DHCP6_OPTION_SNTP_SERVERS: + case SD_DHCP6_OPTION_NTP_SERVER: break; default: @@ -254,23 +282,38 @@ int sd_dhcp6_client_set_request_option(sd_dhcp6_client *client, int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) { assert_return(client, -EINVAL); - assert_return(ret, -EINVAL); if (!client->lease) return -ENOMSG; - *ret = sd_dhcp6_lease_ref(client->lease); + if (ret) + *ret = client->lease; return 0; } static void client_notify(sd_dhcp6_client *client, int event) { - if (client->cb) - client->cb(client, event, client->userdata); + assert(client); + + if (client->callback) + client->callback(client, event, client->userdata); +} + +static void client_set_lease(sd_dhcp6_client *client, sd_dhcp6_lease *lease) { + assert(client); + + if (client->lease) { + dhcp6_lease_clear_timers(&client->lease->ia); + sd_dhcp6_lease_unref(client->lease); + } + + client->lease = lease; } static int client_reset(sd_dhcp6_client *client) { - assert_return(client, -EINVAL); + assert(client); + + client_set_lease(client, NULL); client->receive_message = sd_event_source_unref(client->receive_message); @@ -316,6 +359,8 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { usec_t elapsed_usec; be16_t elapsed_time; + assert(client); + len = sizeof(DHCP6Message) + optlen; message = malloc0(len); @@ -336,7 +381,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { message->type = DHCP6_SOLICIT; r = dhcp6_option_append(&opt, &optlen, - DHCP6_OPTION_RAPID_COMMIT, 0, NULL); + SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); if (r < 0) return r; @@ -354,7 +399,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { else message->type = DHCP6_RENEW; - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_SERVERID, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID, client->lease->serverid_len, client->lease->serverid); if (r < 0) @@ -380,14 +425,14 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return -EINVAL; } - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ORO, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ORO, client->req_opts_len * sizeof(be16_t), client->req_opts); if (r < 0) return r; assert (client->duid_len); - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_CLIENTID, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_CLIENTID, client->duid_len, &client->duid); if (r < 0) return r; @@ -398,7 +443,7 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { else elapsed_time = 0xffff; - r = dhcp6_option_append(&opt, &optlen, DHCP6_OPTION_ELAPSED_TIME, + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME, sizeof(elapsed_time), &elapsed_time); if (r < 0) return r; @@ -414,13 +459,12 @@ static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { return 0; } -static int client_timeout_t2(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp6_client *client = userdata; - assert_return(s, -EINVAL); - assert_return(client, -EINVAL); - assert_return(client->lease, -EINVAL); + assert(s); + assert(client); + assert(client->lease); client->lease->ia.timeout_t2 = sd_event_source_unref(client->lease->ia.timeout_t2); @@ -432,13 +476,12 @@ static int client_timeout_t2(sd_event_source *s, uint64_t usec, return 0; } -static int client_timeout_t1(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_timeout_t1(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp6_client *client = userdata; - assert_return(s, -EINVAL); - assert_return(client, -EINVAL); - assert_return(client->lease, -EINVAL); + assert(s); + assert(client); + assert(client->lease); client->lease->ia.timeout_t1 = sd_event_source_unref(client->lease->ia.timeout_t1); @@ -450,8 +493,7 @@ static int client_timeout_t1(sd_event_source *s, uint64_t usec, return 0; } -static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, void *userdata) { sd_dhcp6_client *client = userdata; DHCP6_CLIENT_DONT_DESTROY(client); enum DHCP6State state; @@ -462,7 +504,7 @@ static int client_timeout_resend_expire(sd_event_source *s, uint64_t usec, state = client->state; - client_stop(client, DHCP6_EVENT_RESEND_EXPIRE); + client_stop(client, SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE); /* RFC 3315, section 18.1.4., says that "...the client may choose to use a Solicit message to locate a new DHCP server..." */ @@ -477,8 +519,7 @@ static usec_t client_timeout_compute_random(usec_t val) { (random_u32() % (2 * USEC_PER_SEC)) * val / 10 / USEC_PER_SEC; } -static int client_timeout_resend(sd_event_source *s, uint64_t usec, - void *userdata) { +static int client_timeout_resend(sd_event_source *s, uint64_t usec, void *userdata) { int r = 0; sd_dhcp6_client *client = userdata; usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0; @@ -552,7 +593,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, if (max_retransmit_count && client->retransmit_count >= max_retransmit_count) { - client_stop(client, DHCP6_EVENT_RETRANS_MAX); + client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX); return 0; } @@ -580,8 +621,7 @@ static int client_timeout_resend(sd_event_source *s, uint64_t usec, } log_dhcp6_client(client, "Next retransmission in %s", - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - client->retransmit_time, 0)); + format_timespan(time_string, FORMAT_TIMESPAN_MAX, client->retransmit_time, USEC_PER_SEC)); r = sd_event_add_time(client->event, &client->timeout_resend, clock_boottime_or_monotonic(), @@ -639,16 +679,18 @@ static int client_ensure_iaid(sd_dhcp6_client *client) { if (client->ia_na.id) return 0; - r = dhcp_identifier_set_iaid(client->index, client->mac_addr, client->mac_addr_len, &client->ia_na.id); + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.id); if (r < 0) return r; return 0; } -static int client_parse_message(sd_dhcp6_client *client, - DHCP6Message *message, size_t len, - sd_dhcp6_lease *lease) { +static int client_parse_message( + sd_dhcp6_client *client, + DHCP6Message *message, + size_t len, + sd_dhcp6_lease *lease) { int r; uint8_t *optval, *option, *id = NULL; uint16_t optcode, status; @@ -656,13 +698,18 @@ static int client_parse_message(sd_dhcp6_client *client, bool clientid = false; be32_t iaid_lease; + assert(client); + assert(message); + assert(len >= sizeof(DHCP6Message)); + assert(lease); + option = (uint8_t *)message + sizeof(DHCP6Message); len -= sizeof(DHCP6Message); while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch (optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: if (clientid) { log_dhcp6_client(client, "%s contains multiple clientids", dhcp6_message_type_to_string(message->type)); @@ -680,7 +727,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: r = dhcp6_lease_get_serverid(lease, &id, &id_len); if (r >= 0 && id) { log_dhcp6_client(client, "%s contains multiple serverids", @@ -694,7 +741,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_PREFERENCE: + case SD_DHCP6_OPTION_PREFERENCE: if (optlen != 1) return -EINVAL; @@ -704,7 +751,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_STATUS_CODE: + case SD_DHCP6_OPTION_STATUS_CODE: if (optlen < 2) return -EINVAL; @@ -718,7 +765,7 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { log_dhcp6_client(client, "Information request ignoring IA NA option"); @@ -742,13 +789,42 @@ static int client_parse_message(sd_dhcp6_client *client, break; - case DHCP6_OPTION_RAPID_COMMIT: + case SD_DHCP6_OPTION_RAPID_COMMIT: r = dhcp6_lease_set_rapid_commit(lease); if (r < 0) return r; break; + + case SD_DHCP6_OPTION_DNS_SERVERS: + r = dhcp6_lease_set_dns(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_DOMAIN_LIST: + r = dhcp6_lease_set_domains(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_NTP_SERVER: + r = dhcp6_lease_set_ntp(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_SNTP_SERVERS: + r = dhcp6_lease_set_sntp(lease, optval, optlen); + if (r < 0) + return r; + + break; } + } if (r == -ENOMSG) @@ -770,12 +846,13 @@ static int client_parse_message(sd_dhcp6_client *client, return r; } -static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, - size_t len) -{ - int r; - _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; +static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; bool rapid_commit; + int r; + + assert(client); + assert(reply); if (reply->type != DHCP6_REPLY) return 0; @@ -797,24 +874,16 @@ static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, return 0; } - if (client->lease) { - dhcp6_lease_clear_timers(&client->lease->ia); - client->lease = sd_dhcp6_lease_unref(client->lease); - } - - if (client->state != DHCP6_STATE_INFORMATION_REQUEST) { - client->lease = lease; - lease = NULL; - } + client_set_lease(client, lease); + lease = NULL; return DHCP6_STATE_BOUND; } -static int client_receive_advertise(sd_dhcp6_client *client, - DHCP6Message *advertise, size_t len) { - int r; - _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; +static int client_receive_advertise(sd_dhcp6_client *client, DHCP6Message *advertise, size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; uint8_t pref_advertise = 0, pref_lease = 0; + int r; if (advertise->type != DHCP6_ADVERTISE) return 0; @@ -834,8 +903,7 @@ static int client_receive_advertise(sd_dhcp6_client *client, r = dhcp6_lease_get_preference(client->lease, &pref_lease); if (r < 0 || pref_advertise > pref_lease) { - sd_dhcp6_lease_unref(client->lease); - client->lease = lease; + client_set_lease(client, lease); lease = NULL; r = 0; } @@ -846,28 +914,40 @@ static int client_receive_advertise(sd_dhcp6_client *client, return r; } -static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, - void *userdata) { +static int client_receive_message( + sd_event_source *s, + int fd, uint32_t + revents, + void *userdata) { + sd_dhcp6_client *client = userdata; DHCP6_CLIENT_DONT_DESTROY(client); - _cleanup_free_ DHCP6Message *message; - int r, buflen, len; + _cleanup_free_ DHCP6Message *message = NULL; + ssize_t buflen, len; + int r = 0; assert(s); assert(client); assert(client->event); - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = DHCP6_MIN_OPTIONS_SIZE; + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return buflen; - message = malloc0(buflen); + message = malloc(buflen); if (!message) return -ENOMEM; - len = read(fd, message, buflen); - if ((size_t)len < sizeof(DHCP6Message)) { - log_dhcp6_client(client, "could not receive message from UDP socket: %m"); + len = recv(fd, message, buflen, 0); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m"); + + } + if ((size_t) len < sizeof(DHCP6Message)) { + log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); return 0; } @@ -890,8 +970,7 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, break; default: - log_dhcp6_client(client, "unknown message type %d", - message->type); + log_dhcp6_client(client, "Unknown message type %d", message->type); return 0; } @@ -905,7 +984,7 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, if (r < 0) return 0; - client_notify(client, DHCP6_EVENT_INFORMATION_REQUEST); + client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); client_start(client, DHCP6_STATE_STOPPED); @@ -937,7 +1016,7 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, return 0; } - client_notify(client, DHCP6_EVENT_IP_ACQUIRE); + client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); } break; @@ -950,23 +1029,21 @@ static int client_receive_message(sd_event_source *s, int fd, uint32_t revents, return 0; } - if (r >= 0) { + if (r >= 0) log_dhcp6_client(client, "Recv %s", dhcp6_message_type_to_string(message->type)); - } return 0; } -static int client_start(sd_dhcp6_client *client, enum DHCP6State state) -{ +static int client_start(sd_dhcp6_client *client, enum DHCP6State state) { int r; usec_t timeout, time_now; char time_string[FORMAT_TIMESPAN_MAX]; assert_return(client, -EINVAL); assert_return(client->event, -EINVAL); - assert_return(client->index > 0, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); assert_return(client->state != state, -EINVAL); client->timeout_resend_expire = @@ -975,14 +1052,9 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) client->retransmit_time = 0; client->retransmit_count = 0; - if (client->state == DHCP6_STATE_STOPPED) { - time_now = now(clock_boottime_or_monotonic()); - } else { - r = sd_event_now(client->event, clock_boottime_or_monotonic(), - &time_now); - if (r < 0) - return r; - } + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; switch (state) { case DHCP6_STATE_STOPPED: @@ -1012,7 +1084,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) if (client->lease->ia.lifetime_t1 == 0xffffffff || client->lease->ia.lifetime_t2 == 0xffffffff) { - log_dhcp6_client(client, "infinite T1 0x%08x or T2 0x%08x", + log_dhcp6_client(client, "Infinite T1 0x%08x or T2 0x%08x", be32toh(client->lease->ia.lifetime_t1), be32toh(client->lease->ia.lifetime_t2)); @@ -1022,9 +1094,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t1) * USEC_PER_SEC); log_dhcp6_client(client, "T1 expires in %s", - format_timespan(time_string, - FORMAT_TIMESPAN_MAX, - timeout, 0)); + format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); r = sd_event_add_time(client->event, &client->lease->ia.timeout_t1, @@ -1046,9 +1116,7 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) timeout = client_timeout_compute_random(be32toh(client->lease->ia.lifetime_t2) * USEC_PER_SEC); log_dhcp6_client(client, "T2 expires in %s", - format_timespan(time_string, - FORMAT_TIMESPAN_MAX, - timeout, 0)); + format_timespan(time_string, FORMAT_TIMESPAN_MAX, timeout, USEC_PER_SEC)); r = sd_event_add_time(client->event, &client->lease->ia.timeout_t2, @@ -1093,21 +1161,31 @@ static int client_start(sd_dhcp6_client *client, enum DHCP6State state) return 0; } -int sd_dhcp6_client_stop(sd_dhcp6_client *client) -{ - client_stop(client, DHCP6_EVENT_STOP); +int sd_dhcp6_client_stop(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + client_stop(client, SD_DHCP6_CLIENT_EVENT_STOP); return 0; } -int sd_dhcp6_client_start(sd_dhcp6_client *client) -{ - int r = 0; +int sd_dhcp6_client_is_running(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + return client->state != DHCP6_STATE_STOPPED; +} + +int sd_dhcp6_client_start(sd_dhcp6_client *client) { enum DHCP6State state = DHCP6_STATE_SOLICITATION; + int r = 0; assert_return(client, -EINVAL); assert_return(client->event, -EINVAL); - assert_return(client->index > 0, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL); + + if (!IN_SET(client->state, DHCP6_STATE_STOPPED)) + return -EBUSY; r = client_reset(client); if (r < 0) @@ -1121,9 +1199,14 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) if (r < 0) return r; - r = dhcp6_network_bind_udp_socket(client->index, NULL); - if (r < 0) - return r; + r = dhcp6_network_bind_udp_socket(client->ifindex, &client->local_address); + if (r < 0) { + _cleanup_free_ char *p = NULL; + + (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p); + return log_dhcp6_client_errno(client, r, + "Failed to bind to UDP socket at address %s: %m", strna(p)); + } client->fd = r; @@ -1139,7 +1222,7 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) goto error; r = sd_event_source_set_description(client->receive_message, - "dhcp6-receive-message"); + "dhcp6-receive-message"); if (r < 0) goto error; @@ -1147,8 +1230,8 @@ int sd_dhcp6_client_start(sd_dhcp6_client *client) state = DHCP6_STATE_INFORMATION_REQUEST; log_dhcp6_client(client, "Started in %s mode", - client->information_request? "Information request": - "Managed"); + client->information_request? "Information request": + "Managed"); return client_start(client, state); @@ -1157,9 +1240,7 @@ error: return r; } -int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, - int priority) -{ +int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int64_t priority) { int r; assert_return(client, -EINVAL); @@ -1187,38 +1268,45 @@ int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) { } sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { - if (!client) - return NULL; + assert_return(client, NULL); return client->event; } sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) { - if (client) - assert_se(REFCNT_INC(client->n_ref) >= 2); + + if (!client) + return NULL; + + assert(client->n_ref >= 1); + client->n_ref++; return client; } sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client) { - if (client && REFCNT_DEC(client->n_ref) == 0) { - client_reset(client); - sd_dhcp6_client_detach_event(client); - sd_dhcp6_lease_unref(client->lease); + if (!client) + return NULL; - free(client->req_opts); - free(client); + assert(client->n_ref >= 1); + client->n_ref--; + if (client->n_ref > 0) return NULL; - } - return client; + client_reset(client); + + sd_dhcp6_client_detach_event(client); + + free(client->req_opts); + free(client); + + return NULL; } -int sd_dhcp6_client_new(sd_dhcp6_client **ret) -{ - _cleanup_dhcp6_client_unref_ sd_dhcp6_client *client = NULL; +int sd_dhcp6_client_new(sd_dhcp6_client **ret) { + _cleanup_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; size_t t; assert_return(ret, -EINVAL); @@ -1227,16 +1315,12 @@ int sd_dhcp6_client_new(sd_dhcp6_client **ret) if (!client) return -ENOMEM; - client->n_ref = REFCNT_INIT; - - client->ia_na.type = DHCP6_OPTION_IA_NA; - - client->index = -1; - + client->n_ref = 1; + client->ia_na.type = SD_DHCP6_OPTION_IA_NA; + client->ifindex = -1; client->fd = -1; client->req_opts_len = ELEMENTSOF(default_req_opts); - client->req_opts = new0(be16_t, client->req_opts_len); if (!client->req_opts) return -ENOMEM; diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c index 2442269a3f..5c10a6326a 100644 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -2,7 +2,7 @@ This file is part of systemd. Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Intel Corporation. All rights reserved. + Copyright (C) 2014-2015 Intel Corporation. All rights reserved. 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 @@ -20,9 +20,11 @@ #include <errno.h> -#include "util.h" - +#include "alloc-util.h" #include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "strv.h" +#include "util.h" int dhcp6_lease_clear_timers(DHCP6IA *ia) { assert_return(ia, -EINVAL); @@ -173,20 +175,221 @@ void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { lease->addr_iter = lease->ia.addresses; } +int dhcp6_lease_set_dns(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->dns, + lease->dns_count, + &lease->dns_allocated); + if (r < 0) { + log_dhcp6_client(client, "Invalid DNS server option: %s", + strerror(-r)); + + return r; + } + + lease->dns_count = r; + + return 0; +} + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs) { + assert_return(lease, -EINVAL); + assert_return(addrs, -EINVAL); + + if (lease->dns_count) { + *addrs = lease->dns; + return lease->dns_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_domains(sd_dhcp6_lease *lease, uint8_t *optval, + size_t optlen) { + int r; + char **domains; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + r = dhcp6_option_parse_domainname(optval, optlen, &domains); + if (r < 0) + return 0; + + free(lease->domains); + lease->domains = domains; + lease->domains_count = r; + + return r; +} + +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains) { + assert_return(lease, -EINVAL); + assert_return(domains, -EINVAL); + + if (lease->domains_count) { + *domains = lease->domains; + return lease->domains_count; + } + + return -ENOENT; +} + +int dhcp6_lease_set_ntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + uint16_t subopt; + size_t sublen; + uint8_t *subval; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + lease->ntp = mfree(lease->ntp); + lease->ntp_count = 0; + lease->ntp_allocated = 0; + + while ((r = dhcp6_option_parse(&optval, &optlen, &subopt, &sublen, + &subval)) >= 0) { + int s; + char **servers; + + switch(subopt) { + case DHCP6_NTP_SUBOPTION_SRV_ADDR: + case DHCP6_NTP_SUBOPTION_MC_ADDR: + if (sublen != 16) + return 0; + + s = dhcp6_option_parse_ip6addrs(subval, sublen, + &lease->ntp, + lease->ntp_count, + &lease->ntp_allocated); + if (s < 0) + return s; + + lease->ntp_count = s; + + break; + + case DHCP6_NTP_SUBOPTION_SRV_FQDN: + r = dhcp6_option_parse_domainname(subval, sublen, + &servers); + if (r < 0) + return 0; + + lease->ntp_fqdn = strv_free(lease->ntp_fqdn); + lease->ntp_fqdn = servers; + lease->ntp_fqdn_count = r; + + break; + } + } + + if (r != -ENOMSG) + return r; + + return 0; +} + +int dhcp6_lease_set_sntp(sd_dhcp6_lease *lease, uint8_t *optval, size_t optlen) { + int r; + + assert_return(lease, -EINVAL); + assert_return(optval, -EINVAL); + + if (!optlen) + return 0; + + if (lease->ntp || lease->ntp_fqdn) { + log_dhcp6_client(client, "NTP information already provided"); + + return 0; + } + + log_dhcp6_client(client, "Using deprecated SNTP information"); + + r = dhcp6_option_parse_ip6addrs(optval, optlen, &lease->ntp, + lease->ntp_count, + &lease->ntp_allocated); + if (r < 0) { + log_dhcp6_client(client, "Invalid SNTP server option: %s", + strerror(-r)); + + return r; + } + + lease->ntp_count = r; + + return 0; +} + +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, + struct in6_addr **addrs) { + assert_return(lease, -EINVAL); + assert_return(addrs, -EINVAL); + + if (lease->ntp_count) { + *addrs = lease->ntp; + return lease->ntp_count; + } + + return -ENOENT; +} + +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn) { + assert_return(lease, -EINVAL); + assert_return(ntp_fqdn, -EINVAL); + + if (lease->ntp_fqdn_count) { + *ntp_fqdn = lease->ntp_fqdn; + return lease->ntp_fqdn_count; + } + + return -ENOENT; +} + sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease) { - if (lease) - assert_se(REFCNT_INC(lease->n_ref) >= 2); + + if (!lease) + return NULL; + + assert(lease->n_ref >= 1); + lease->n_ref++; return lease; } sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease) { - if (lease && REFCNT_DEC(lease->n_ref) == 0) { - free(lease->serverid); - dhcp6_lease_free_ia(&lease->ia); - free(lease); - } + if (!lease) + return NULL; + + assert(lease->n_ref >= 1); + lease->n_ref--; + + if (lease->n_ref > 0) + return NULL; + + free(lease->serverid); + dhcp6_lease_free_ia(&lease->ia); + + free(lease->dns); + + lease->domains = strv_free(lease->domains); + + free(lease->ntp); + + lease->ntp_fqdn = strv_free(lease->ntp_fqdn); + free(lease); return NULL; } @@ -198,7 +401,7 @@ int dhcp6_lease_new(sd_dhcp6_lease **ret) { if (!lease) return -ENOMEM; - lease->n_ref = REFCNT_INIT; + lease->n_ref = 1; LIST_HEAD_INIT(lease->ia.addresses); diff --git a/src/libsystemd-network/sd-icmp6-nd.c b/src/libsystemd-network/sd-icmp6-nd.c deleted file mode 100644 index 2f867e8562..0000000000 --- a/src/libsystemd-network/sd-icmp6-nd.c +++ /dev/null @@ -1,715 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - 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/icmp6.h> -#include <netinet/ip6.h> -#include <string.h> -#include <stdbool.h> -#include <netinet/in.h> -#include <sys/ioctl.h> - -#include "socket-util.h" -#include "refcnt.h" -#include "async.h" - -#include "dhcp6-internal.h" -#include "sd-icmp6-nd.h" - -#define ICMP6_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC -#define ICMP6_MAX_ROUTER_SOLICITATIONS 3 - -enum icmp6_nd_state { - ICMP6_NEIGHBOR_DISCOVERY_IDLE = 0, - ICMP6_ROUTER_SOLICITATION_SENT = 10, - ICMP6_ROUTER_ADVERTISMENT_LISTEN = 11, -}; - -#define IP6_MIN_MTU (unsigned)1280 -#define ICMP6_ND_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr)) -#define ICMP6_OPT_LEN_UNITS 8 - -typedef struct ICMP6Prefix ICMP6Prefix; - -struct ICMP6Prefix { - RefCount n_ref; - - LIST_FIELDS(ICMP6Prefix, prefixes); - - uint8_t len; - sd_event_source *timeout_valid; - struct in6_addr addr; -}; - -struct sd_icmp6_nd { - RefCount n_ref; - - enum icmp6_nd_state state; - sd_event *event; - int event_priority; - int index; - struct ether_addr mac_addr; - uint32_t mtu; - ICMP6Prefix *expired_prefix; - LIST_HEAD(ICMP6Prefix, prefixes); - int fd; - sd_event_source *recv; - sd_event_source *timeout; - int nd_sent; - sd_icmp6_nd_callback_t callback; - void *userdata; -}; - -#define log_icmp6_nd(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "ICMPv6 CLIENT: " fmt, ##__VA_ARGS__) - -static ICMP6Prefix *icmp6_prefix_unref(ICMP6Prefix *prefix) { - if (prefix && REFCNT_DEC(prefix->n_ref) <= 0) { - prefix->timeout_valid = - sd_event_source_unref(prefix->timeout_valid); - - free(prefix); - } - - return NULL; -} - -static int icmp6_prefix_new(ICMP6Prefix **ret) { - _cleanup_free_ ICMP6Prefix *prefix = NULL; - - assert(ret); - - prefix = new0(ICMP6Prefix, 1); - if (!prefix) - return -ENOMEM; - - prefix->n_ref = REFCNT_INIT; - LIST_INIT(prefixes, prefix); - - *ret = prefix; - prefix = NULL; - - return 0; -} - -static void icmp6_nd_notify(sd_icmp6_nd *nd, int event) -{ - if (nd->callback) - nd->callback(nd, event, nd->userdata); -} - -int sd_icmp6_nd_set_callback(sd_icmp6_nd *nd, sd_icmp6_nd_callback_t callback, - void *userdata) { - assert(nd); - - nd->callback = callback; - nd->userdata = userdata; - - return 0; -} - -int sd_icmp6_nd_set_index(sd_icmp6_nd *nd, int interface_index) { - assert(nd); - assert(interface_index >= -1); - - nd->index = interface_index; - - return 0; -} - -int sd_icmp6_nd_set_mac(sd_icmp6_nd *nd, const struct ether_addr *mac_addr) { - assert(nd); - - if (mac_addr) - memcpy(&nd->mac_addr, mac_addr, sizeof(nd->mac_addr)); - else - zero(nd->mac_addr); - - return 0; - -} - -int sd_icmp6_nd_attach_event(sd_icmp6_nd *nd, sd_event *event, int priority) { - int r; - - assert_return(nd, -EINVAL); - assert_return(!nd->event, -EBUSY); - - if (event) - nd->event = sd_event_ref(event); - else { - r = sd_event_default(&nd->event); - if (r < 0) - return 0; - } - - nd->event_priority = priority; - - return 0; -} - -int sd_icmp6_nd_detach_event(sd_icmp6_nd *nd) { - assert_return(nd, -EINVAL); - - nd->event = sd_event_unref(nd->event); - - return 0; -} - -sd_event *sd_icmp6_nd_get_event(sd_icmp6_nd *nd) { - assert(nd); - - return nd->event; -} - -sd_icmp6_nd *sd_icmp6_nd_ref(sd_icmp6_nd *nd) { - assert (nd); - - assert_se(REFCNT_INC(nd->n_ref) >= 2); - - return nd; -} - -static int icmp6_nd_init(sd_icmp6_nd *nd) { - assert(nd); - - nd->recv = sd_event_source_unref(nd->recv); - nd->fd = asynchronous_close(nd->fd); - nd->timeout = sd_event_source_unref(nd->timeout); - - return 0; -} - -sd_icmp6_nd *sd_icmp6_nd_unref(sd_icmp6_nd *nd) { - if (nd && REFCNT_DEC(nd->n_ref) == 0) { - ICMP6Prefix *prefix, *p; - - icmp6_nd_init(nd); - sd_icmp6_nd_detach_event(nd); - - LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { - LIST_REMOVE(prefixes, nd->prefixes, prefix); - - prefix = icmp6_prefix_unref(prefix); - } - - free(nd); - } - - return NULL; -} - -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_icmp6_nd*, sd_icmp6_nd_unref); -#define _cleanup_sd_icmp6_nd_free_ _cleanup_(sd_icmp6_nd_unrefp) - -int sd_icmp6_nd_new(sd_icmp6_nd **ret) { - _cleanup_sd_icmp6_nd_free_ sd_icmp6_nd *nd = NULL; - - assert(ret); - - nd = new0(sd_icmp6_nd, 1); - if (!nd) - return -ENOMEM; - - nd->n_ref = REFCNT_INIT; - - nd->index = -1; - nd->fd = -1; - - LIST_HEAD_INIT(nd->prefixes); - - *ret = nd; - nd = NULL; - - return 0; -} - -int sd_icmp6_ra_get_mtu(sd_icmp6_nd *nd, uint32_t *mtu) { - assert_return(nd, -EINVAL); - assert_return(mtu, -EINVAL); - - if (nd->mtu == 0) - return -ENOMSG; - - *mtu = nd->mtu; - - return 0; -} - -static int icmp6_ra_prefix_timeout(sd_event_source *s, uint64_t usec, - void *userdata) { - sd_icmp6_nd *nd = userdata; - ICMP6Prefix *prefix, *p; - - assert(nd); - - LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { - if (prefix->timeout_valid != s) - continue; - - log_icmp6_nd(nd, "Prefix expired "SD_ICMP6_ADDRESS_FORMAT_STR"/%d", - SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len); - - LIST_REMOVE(prefixes, nd->prefixes, prefix); - - nd->expired_prefix = prefix; - icmp6_nd_notify(nd, - ICMP6_EVENT_ROUTER_ADVERTISMENT_PREFIX_EXPIRED); - nd->expired_prefix = NULL; - - prefix = icmp6_prefix_unref(prefix); - - break; - } - - return 0; -} - -static int icmp6_ra_prefix_set_timeout(sd_icmp6_nd *nd, - ICMP6Prefix *prefix, - usec_t valid) { - usec_t time_now; - int r; - - assert_return(prefix, -EINVAL); - - r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - return r; - - prefix->timeout_valid = sd_event_source_unref(prefix->timeout_valid); - - r = sd_event_add_time(nd->event, &prefix->timeout_valid, - clock_boottime_or_monotonic(), time_now + valid, - USEC_PER_SEC, icmp6_ra_prefix_timeout, nd); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(prefix->timeout_valid, - nd->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(prefix->timeout_valid, - "icmp6-prefix-timeout"); - -error: - if (r < 0) - prefix->timeout_valid = - sd_event_source_unref(prefix->timeout_valid); - - return r; -} - -static int icmp6_prefix_match(const struct in6_addr *prefix, uint8_t prefixlen, - const struct in6_addr *addr, - uint8_t addr_prefixlen) { - uint8_t bytes, mask, len; - - assert_return(prefix, -EINVAL); - assert_return(addr, -EINVAL); - - len = MIN(prefixlen, addr_prefixlen); - - bytes = len / 8; - mask = 0xff << (8 - len % 8); - - if (memcmp(prefix, addr, bytes) != 0 || - (prefix->s6_addr[bytes] & mask) != (addr->s6_addr[bytes] & mask)) - return -EADDRNOTAVAIL; - - return 0; -} - -static int icmp6_ra_prefix_match(ICMP6Prefix *head, const struct in6_addr *addr, - uint8_t addr_len, ICMP6Prefix **result) { - ICMP6Prefix *prefix; - - LIST_FOREACH(prefixes, prefix, head) { - if (icmp6_prefix_match(&prefix->addr, prefix->len, addr, - addr_len) >= 0) { - *result = prefix; - return 0; - } - } - - return -EADDRNOTAVAIL; -} - -int sd_icmp6_prefix_match(struct in6_addr *prefix, uint8_t prefixlen, - struct in6_addr *addr) { - return icmp6_prefix_match(prefix, prefixlen, addr, - sizeof(addr->s6_addr) * 8); -} - -int sd_icmp6_ra_get_prefixlen(sd_icmp6_nd *nd, const struct in6_addr *addr, - uint8_t *prefixlen) { - int r; - ICMP6Prefix *prefix; - - assert_return(nd, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(prefixlen, -EINVAL); - - r = icmp6_ra_prefix_match(nd->prefixes, addr, - sizeof(addr->s6_addr) * 8, &prefix); - if (r < 0) - return r; - - *prefixlen = prefix->len; - - return 0; -} - -int sd_icmp6_ra_get_expired_prefix(sd_icmp6_nd *nd, struct in6_addr **addr, - uint8_t *prefixlen) -{ - assert_return(nd, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(prefixlen, -EINVAL); - - if (!nd->expired_prefix) - return -EADDRNOTAVAIL; - - *addr = &nd->expired_prefix->addr; - *prefixlen = nd->expired_prefix->len; - - return 0; -} - -static int icmp6_ra_prefix_update(sd_icmp6_nd *nd, ssize_t len, - const struct nd_opt_prefix_info *prefix_opt) { - int r; - ICMP6Prefix *prefix; - uint32_t lifetime; - char time_string[FORMAT_TIMESPAN_MAX]; - - assert_return(nd, -EINVAL); - assert_return(prefix_opt, -EINVAL); - - if (len < prefix_opt->nd_opt_pi_len) - return -ENOMSG; - - if (!(prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK)) - return 0; - - lifetime = be32toh(prefix_opt->nd_opt_pi_valid_time); - - r = icmp6_ra_prefix_match(nd->prefixes, - &prefix_opt->nd_opt_pi_prefix, - prefix_opt->nd_opt_pi_prefix_len, &prefix); - - if (r < 0 && r != -EADDRNOTAVAIL) - return r; - - /* if router advertisment prefix valid timeout is zero, the timeout - callback will be called immediately to clean up the prefix */ - - if (r == -EADDRNOTAVAIL) { - r = icmp6_prefix_new(&prefix); - if (r < 0) - return r; - - prefix->len = prefix_opt->nd_opt_pi_prefix_len; - - memcpy(&prefix->addr, &prefix_opt->nd_opt_pi_prefix, - sizeof(prefix->addr)); - - log_icmp6_nd(nd, "New prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", - SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len, lifetime, - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - lifetime * USEC_PER_SEC, 0)); - - LIST_PREPEND(prefixes, nd->prefixes, prefix); - - } else { - if (prefix->len != prefix_opt->nd_opt_pi_prefix_len) { - uint8_t prefixlen; - - prefixlen = MIN(prefix->len, prefix_opt->nd_opt_pi_prefix_len); - - log_icmp6_nd(nd, "Prefix length mismatch %d/%d using %d", - prefix->len, - prefix_opt->nd_opt_pi_prefix_len, - prefixlen); - - prefix->len = prefixlen; - } - - log_icmp6_nd(nd, "Update prefix "SD_ICMP6_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", - SD_ICMP6_ADDRESS_FORMAT_VAL(prefix->addr), - prefix->len, lifetime, - format_timespan(time_string, FORMAT_TIMESPAN_MAX, - lifetime * USEC_PER_SEC, 0)); - } - - r = icmp6_ra_prefix_set_timeout(nd, prefix, lifetime * USEC_PER_SEC); - - return r; -} - -static int icmp6_ra_parse(sd_icmp6_nd *nd, struct nd_router_advert *ra, - ssize_t len) { - void *opt; - struct nd_opt_hdr *opt_hdr; - - assert_return(nd, -EINVAL); - assert_return(ra, -EINVAL); - - len -= sizeof(*ra); - if (len < ICMP6_OPT_LEN_UNITS) { - log_icmp6_nd(nd, "Router Advertisement below minimum length"); - - return -ENOMSG; - } - - opt = ra + 1; - opt_hdr = opt; - - while (len != 0 && len >= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS) { - struct nd_opt_mtu *opt_mtu; - uint32_t mtu; - struct nd_opt_prefix_info *opt_prefix; - - if (opt_hdr->nd_opt_len == 0) - return -ENOMSG; - - switch (opt_hdr->nd_opt_type) { - case ND_OPT_MTU: - opt_mtu = opt; - - mtu = be32toh(opt_mtu->nd_opt_mtu_mtu); - - if (mtu != nd->mtu) { - nd->mtu = MAX(mtu, IP6_MIN_MTU); - - log_icmp6_nd(nd, "Router Advertisement link MTU %d using %d", - mtu, nd->mtu); - } - - break; - - case ND_OPT_PREFIX_INFORMATION: - opt_prefix = opt; - - icmp6_ra_prefix_update(nd, len, opt_prefix); - - break; - } - - len -= opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS; - opt = (void *)((char *)opt + - opt_hdr->nd_opt_len * ICMP6_OPT_LEN_UNITS); - opt_hdr = opt; - } - - if (len > 0) - log_icmp6_nd(nd, "Router Advertisement contains %zd bytes of trailing garbage", len); - - return 0; -} - -static int icmp6_router_advertisment_recv(sd_event_source *s, int fd, - uint32_t revents, void *userdata) -{ - sd_icmp6_nd *nd = userdata; - int r, buflen = 0; - ssize_t len; - _cleanup_free_ struct nd_router_advert *ra = NULL; - int event = ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE; - - assert(s); - assert(nd); - assert(nd->event); - - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0 || buflen <= 0) - buflen = ICMP6_ND_RECV_SIZE; - - ra = malloc(buflen); - if (!ra) - return -ENOMEM; - - len = read(fd, ra, buflen); - if (len < 0) { - log_icmp6_nd(nd, "Could not receive message from UDP socket: %m"); - return 0; - } - - if (ra->nd_ra_type != ND_ROUTER_ADVERT) - return 0; - - if (ra->nd_ra_code != 0) - return 0; - - nd->timeout = sd_event_source_unref(nd->timeout); - - nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN; - - if (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER ) - event = ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER; - - if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) - event = ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED; - - log_icmp6_nd(nd, "Received Router Advertisement flags %s/%s", - ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED? "MANAGED": "none", - ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER? "OTHER": "none"); - - if (event != ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE) { - r = icmp6_ra_parse(nd, ra, len); - if (r < 0) { - log_icmp6_nd(nd, "Could not parse Router Advertisement: %s", - strerror(-r)); - return 0; - } - } - - icmp6_nd_notify(nd, event); - - return 0; -} - -static int icmp6_router_solicitation_timeout(sd_event_source *s, uint64_t usec, - void *userdata) -{ - sd_icmp6_nd *nd = userdata; - uint64_t time_now, next_timeout; - struct ether_addr unset = { }; - struct ether_addr *addr = NULL; - int r; - - assert(s); - assert(nd); - assert(nd->event); - - nd->timeout = sd_event_source_unref(nd->timeout); - - if (nd->nd_sent >= ICMP6_MAX_ROUTER_SOLICITATIONS) { - icmp6_nd_notify(nd, ICMP6_EVENT_ROUTER_ADVERTISMENT_TIMEOUT); - nd->state = ICMP6_ROUTER_ADVERTISMENT_LISTEN; - } else { - if (memcmp(&nd->mac_addr, &unset, sizeof(struct ether_addr))) - addr = &nd->mac_addr; - - r = dhcp_network_icmp6_send_router_solicitation(nd->fd, addr); - if (r < 0) - log_icmp6_nd(nd, "Error sending Router Solicitation"); - else { - nd->state = ICMP6_ROUTER_SOLICITATION_SENT; - log_icmp6_nd(nd, "Sent Router Solicitation"); - } - - nd->nd_sent++; - - r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - - next_timeout = time_now + ICMP6_ROUTER_SOLICITATION_INTERVAL; - - r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), - next_timeout, 0, - icmp6_router_solicitation_timeout, nd); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - - r = sd_event_source_set_priority(nd->timeout, - nd->event_priority); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - - r = sd_event_source_set_description(nd->timeout, "icmp6-timeout"); - if (r < 0) { - icmp6_nd_notify(nd, r); - return 0; - } - } - - return 0; -} - -int sd_icmp6_nd_stop(sd_icmp6_nd *nd) { - assert_return(nd, -EINVAL); - assert_return(nd->event, -EINVAL); - - log_icmp6_nd(client, "Stop ICMPv6"); - - icmp6_nd_init(nd); - - nd->state = ICMP6_NEIGHBOR_DISCOVERY_IDLE; - - return 0; -} - -int sd_icmp6_router_solicitation_start(sd_icmp6_nd *nd) { - int r; - - assert(nd); - assert(nd->event); - - if (nd->state != ICMP6_NEIGHBOR_DISCOVERY_IDLE) - return -EINVAL; - - if (nd->index < 1) - return -EINVAL; - - r = dhcp_network_icmp6_bind_router_solicitation(nd->index); - if (r < 0) - return r; - - nd->fd = r; - - r = sd_event_add_io(nd->event, &nd->recv, nd->fd, EPOLLIN, - icmp6_router_advertisment_recv, nd); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(nd->recv, nd->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(nd->recv, "icmp6-receive-message"); - if (r < 0) - goto error; - - r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), - 0, 0, icmp6_router_solicitation_timeout, nd); - if (r < 0) - goto error; - - r = sd_event_source_set_priority(nd->timeout, nd->event_priority); - if (r < 0) - goto error; - - r = sd_event_source_set_description(nd->timeout, "icmp6-timeout"); -error: - if (r < 0) - icmp6_nd_init(nd); - else - log_icmp6_nd(client, "Start Router Solicitation"); - - return r; -} diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c new file mode 100644 index 0000000000..662885840f --- /dev/null +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -0,0 +1,526 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 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 <arpa/inet.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "sd-ipv4acd.h" + +#include "alloc-util.h" +#include "arp-util.h" +#include "ether-addr-util.h" +#include "fd-util.h" +#include "in-addr-util.h" +#include "list.h" +#include "random-util.h" +#include "siphash24.h" +#include "string-util.h" +#include "util.h" + +/* Constants from the RFC */ +#define PROBE_WAIT_USEC (1U * USEC_PER_SEC) +#define PROBE_NUM 3U +#define PROBE_MIN_USEC (1U * USEC_PER_SEC) +#define PROBE_MAX_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_NUM 2U +#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC) +#define MAX_CONFLICTS 10U +#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC) +#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC) + +typedef enum IPv4ACDState { + IPV4ACD_STATE_INIT, + IPV4ACD_STATE_STARTED, + IPV4ACD_STATE_WAITING_PROBE, + IPV4ACD_STATE_PROBING, + IPV4ACD_STATE_WAITING_ANNOUNCE, + IPV4ACD_STATE_ANNOUNCING, + IPV4ACD_STATE_RUNNING, + _IPV4ACD_STATE_MAX, + _IPV4ACD_STATE_INVALID = -1 +} IPv4ACDState; + +struct sd_ipv4acd { + unsigned n_ref; + + IPv4ACDState state; + int ifindex; + int fd; + + unsigned n_iteration; + unsigned n_conflict; + + sd_event_source *receive_message_event_source; + sd_event_source *timer_event_source; + + usec_t defend_window; + be32_t address; + + /* External */ + struct ether_addr mac_addr; + + sd_event *event; + int event_priority; + sd_ipv4acd_callback_t callback; + void* userdata; +}; + +#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__) +#define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__) + +static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) { + assert(acd); + assert(st < _IPV4ACD_STATE_MAX); + + if (st == acd->state && !reset_counter) + acd->n_iteration++; + else { + acd->state = st; + acd->n_iteration = 0; + } +} + +static void ipv4acd_reset(sd_ipv4acd *acd) { + assert(acd); + + acd->timer_event_source = sd_event_source_unref(acd->timer_event_source); + acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source); + + acd->fd = safe_close(acd->fd); + + ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true); +} + +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd) { + if (!acd) + return NULL; + + assert_se(acd->n_ref >= 1); + acd->n_ref++; + + return acd; +} + +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) { + if (!acd) + return NULL; + + assert_se(acd->n_ref >= 1); + acd->n_ref--; + + if (acd->n_ref > 0) + return NULL; + + ipv4acd_reset(acd); + sd_ipv4acd_detach_event(acd); + + free(acd); + + return NULL; +} + +int sd_ipv4acd_new(sd_ipv4acd **ret) { + _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL; + + assert_return(ret, -EINVAL); + + acd = new0(sd_ipv4acd, 1); + if (!acd) + return -ENOMEM; + + acd->n_ref = 1; + acd->state = IPV4ACD_STATE_INIT; + acd->ifindex = -1; + acd->fd = -1; + + *ret = acd; + acd = NULL; + + return 0; +} + +static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) { + assert(acd); + + if (!acd->callback) + return; + + acd->callback(acd, event, acd->userdata); +} + +int sd_ipv4acd_stop(sd_ipv4acd *acd) { + assert_return(acd, -EINVAL); + + ipv4acd_reset(acd); + + log_ipv4acd(acd, "STOPPED"); + + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP); + + return 0; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); + +static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) { + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + usec_t next_timeout, time_now; + int r; + + assert(acd); + + next_timeout = usec; + + if (random_usec > 0) + next_timeout += (usec_t) random_u64() % random_usec; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0); + + r = sd_event_add_time(acd->event, &timer, clock_boottime_or_monotonic(), time_now + next_timeout, 0, ipv4acd_on_timeout, acd); + if (r < 0) + return r; + + r = sd_event_source_set_priority(timer, acd->event_priority); + if (r < 0) + return r; + + (void) sd_event_source_set_description(timer, "ipv4acd-timer"); + + sd_event_source_unref(acd->timer_event_source); + acd->timer_event_source = timer; + timer = NULL; + + return 0; +} + +static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) { + assert(acd); + assert(arp); + + /* see the BPF */ + if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0) + return true; + + /* the TPA matched instead of the SPA, this is not a conflict */ + return false; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ipv4acd *acd = userdata; + int r = 0; + + assert(acd); + + switch (acd->state) { + + case IPV4ACD_STATE_STARTED: + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); + + if (acd->n_conflict >= MAX_CONFLICTS) { + char ts[FORMAT_TIMESPAN_MAX]; + log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); + + r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + + acd->n_conflict = 0; + } else { + r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + /* Send a probe */ + r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); + goto fail; + } else { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Probing %s", strna(address)); + } + + if (acd->n_iteration < PROBE_NUM - 2) { + ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); + + r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); + if (r < 0) + goto fail; + } else { + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_ANNOUNCING: + if (acd->n_iteration >= ANNOUNCE_NUM - 1) { + ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false); + break; + } + + /* fall through */ + + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* Send announcement packet */ + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "ANNOUNCE"); + + ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0); + if (r < 0) + goto fail; + + if (acd->n_iteration == 0) { + acd->n_conflict = 0; + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND); + } + + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +static void ipv4acd_on_conflict(sd_ipv4acd *acd) { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + assert(acd); + + acd->n_conflict++; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); + + ipv4acd_reset(acd); + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); +} + +static int ipv4acd_on_packet( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_ipv4acd *acd = userdata; + struct ether_arp packet; + ssize_t n; + int r; + + assert(s); + assert(acd); + assert(fd >= 0); + + n = recv(fd, &packet, sizeof(struct ether_arp), 0); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m"); + goto fail; + } + if ((size_t) n != sizeof(struct ether_arp)) { + log_ipv4acd(acd, "Ignoring too short ARP packet."); + return 0; + } + + switch (acd->state) { + + case IPV4ACD_STATE_ANNOUNCING: + case IPV4ACD_STATE_RUNNING: + + if (ipv4acd_arp_conflict(acd, &packet)) { + usec_t ts; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* Defend address */ + if (ts > acd->defend_window) { + acd->defend_window = ts + DEFEND_INTERVAL_USEC; + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "DEFEND"); + + } else + ipv4acd_on_conflict(acd); + } + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* BPF ensures this packet indicates a conflict */ + ipv4acd_on_conflict(acd); + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) { + assert_return(acd, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->ifindex = ifindex; + + return 0; +} + +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) { + assert_return(acd, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->mac_addr = *addr; + + return 0; +} + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd) { + assert_return(acd, -EINVAL); + + acd->event = sd_event_unref(acd->event); + + return 0; +} + +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) { + int r; + + assert_return(acd, -EINVAL); + assert_return(!acd->event, -EBUSY); + + if (event) + acd->event = sd_event_ref(event); + else { + r = sd_event_default(&acd->event); + if (r < 0) + return r; + } + + acd->event_priority = priority; + + return 0; +} + +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) { + assert_return(acd, -EINVAL); + + acd->callback = cb; + acd->userdata = userdata; + + return 0; +} + +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) { + assert_return(acd, -EINVAL); + assert_return(address, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->address = address->s_addr; + + return 0; +} + +int sd_ipv4acd_is_running(sd_ipv4acd *acd) { + assert_return(acd, false); + + return acd->state != IPV4ACD_STATE_INIT; +} + +int sd_ipv4acd_start(sd_ipv4acd *acd) { + int r; + + assert_return(acd, -EINVAL); + assert_return(acd->event, -EINVAL); + assert_return(acd->ifindex > 0, -EINVAL); + assert_return(acd->address != 0, -EINVAL); + assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) + return r; + + safe_close(acd->fd); + acd->fd = r; + acd->defend_window = 0; + acd->n_conflict = 0; + + r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message"); + + r = ipv4acd_set_next_wakeup(acd, 0, 0); + if (r < 0) + goto fail; + + ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); + return 0; + +fail: + ipv4acd_reset(acd); + return r; +} diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c index 9e04db96bb..5603a533a5 100644 --- a/src/libsystemd-network/sd-ipv4ll.c +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -2,6 +2,7 @@ This file is part of systemd. Copyright (C) 2014 Axis Communications AB. All rights reserved. + Copyright (C) 2015 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 @@ -17,633 +18,329 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdlib.h> +#include <arpa/inet.h> #include <errno.h> -#include <string.h> #include <stdio.h> -#include <arpa/inet.h> +#include <stdlib.h> +#include <string.h> -#include "util.h" -#include "siphash24.h" +#include "sd-ipv4acd.h" +#include "sd-ipv4ll.h" + +#include "alloc-util.h" +#include "ether-addr-util.h" +#include "in-addr-util.h" #include "list.h" -#include "refcnt.h" #include "random-util.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "string-util.h" +#include "util.h" -#include "ipv4ll-internal.h" -#include "sd-ipv4ll.h" +#define IPV4LL_NETWORK UINT32_C(0xA9FE0000) +#define IPV4LL_NETMASK UINT32_C(0xFFFF0000) -/* Constants from the RFC */ -#define PROBE_WAIT 1 -#define PROBE_NUM 3 -#define PROBE_MIN 1 -#define PROBE_MAX 2 -#define ANNOUNCE_WAIT 2 -#define ANNOUNCE_NUM 2 -#define ANNOUNCE_INTERVAL 2 -#define MAX_CONFLICTS 10 -#define RATE_LIMIT_INTERVAL 60 -#define DEFEND_INTERVAL 10 - -#define IPV4LL_NETWORK 0xA9FE0000L -#define IPV4LL_NETMASK 0xFFFF0000L - -typedef enum IPv4LLTrigger{ - IPV4LL_TRIGGER_NULL, - IPV4LL_TRIGGER_PACKET, - IPV4LL_TRIGGER_TIMEOUT, - _IPV4LL_TRIGGER_MAX, - _IPV4LL_TRIGGER_INVALID = -1 -} IPv4LLTrigger; - -typedef enum IPv4LLState { - IPV4LL_STATE_INIT, - IPV4LL_STATE_WAITING_PROBE, - IPV4LL_STATE_PROBING, - IPV4LL_STATE_WAITING_ANNOUNCE, - IPV4LL_STATE_ANNOUNCING, - IPV4LL_STATE_RUNNING, - IPV4LL_STATE_STOPPED, - _IPV4LL_STATE_MAX, - _IPV4LL_STATE_INVALID = -1 -} IPv4LLState; +#define IPV4LL_DONT_DESTROY(ll) \ + _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll) struct sd_ipv4ll { - RefCount n_ref; - - IPv4LLState state; - int index; - int fd; - union sockaddr_union link; - int iteration; - int conflict; - sd_event_source *receive_message; - sd_event_source *timer; - usec_t next_wakeup; - usec_t defend_window; - int next_wakeup_valid; - be32_t address; - struct random_data *random_data; - char *random_data_state; + unsigned n_ref; + + sd_ipv4acd *acd; + + be32_t address; /* the address pushed to ACD */ + struct ether_addr mac; + + struct { + le64_t value; + le64_t generation; + } seed; + bool seed_set; + /* External */ be32_t claimed_address; - struct ether_addr mac_addr; - sd_event *event; - int event_priority; - sd_ipv4ll_cb_t cb; + + sd_ipv4ll_callback_t callback; void* userdata; }; -static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void *trigger_data); - -static void ipv4ll_set_state(sd_ipv4ll *ll, IPv4LLState st, int reset_counter) { +#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__) +#define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__) - assert(ll); - assert(st < _IPV4LL_STATE_MAX); - - if (st == ll->state && !reset_counter) { - ll->iteration++; - } else { - ll->state = st; - ll->iteration = 0; - } -} +static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata); -static sd_ipv4ll *ipv4ll_client_notify(sd_ipv4ll *ll, int event) { - assert(ll); +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) { + if (!ll) + return NULL; - if (ll->cb) { - ll = sd_ipv4ll_ref(ll); - ll->cb(ll, event, ll->userdata); - ll = sd_ipv4ll_unref(ll); - } + assert(ll->n_ref >= 1); + ll->n_ref++; return ll; } -static sd_ipv4ll *ipv4ll_stop(sd_ipv4ll *ll, int event) { - assert(ll); - - ll->receive_message = sd_event_source_unref(ll->receive_message); - ll->fd = safe_close(ll->fd); - - ll->timer = sd_event_source_unref(ll->timer); +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { + if (!ll) + return NULL; - log_ipv4ll(ll, "STOPPED"); + assert(ll->n_ref >= 1); + ll->n_ref--; - ll = ipv4ll_client_notify(ll, event); + if (ll->n_ref > 0) + return NULL; - if (ll) { - ll->claimed_address = 0; - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1); - } + sd_ipv4acd_unref(ll->acd); + free(ll); - return ll; + return NULL; } -static int ipv4ll_pick_address(sd_ipv4ll *ll, be32_t *address) { - be32_t addr; +int sd_ipv4ll_new(sd_ipv4ll **ret) { + _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL; int r; - int32_t random; - assert(ll); - assert(address); - assert(ll->random_data); + assert_return(ret, -EINVAL); - do { - r = random_r(ll->random_data, &random); - if (r < 0) - return r; - addr = htonl((random & 0x0000FFFF) | IPV4LL_NETWORK); - } while (addr == ll->address || - (ntohl(addr) & IPV4LL_NETMASK) != IPV4LL_NETWORK || - (ntohl(addr) & 0x0000FF00) == 0x0000 || - (ntohl(addr) & 0x0000FF00) == 0xFF00); + ll = new0(sd_ipv4ll, 1); + if (!ll) + return -ENOMEM; - *address = addr; - return 0; -} + ll->n_ref = 1; -static int ipv4ll_timer(sd_event_source *s, uint64_t usec, void *userdata) { - sd_ipv4ll *ll = (sd_ipv4ll*)userdata; + r = sd_ipv4acd_new(&ll->acd); + if (r < 0) + return r; - assert(ll); + r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll); + if (r < 0) + return r; - ll->next_wakeup_valid = 0; - ipv4ll_run_state_machine(ll, IPV4LL_TRIGGER_TIMEOUT, NULL); + *ret = ll; + ll = NULL; return 0; } -static void ipv4ll_set_next_wakeup(sd_ipv4ll *ll, int sec, int random_sec) { - usec_t next_timeout = 0; - usec_t time_now = 0; - - assert(sec >= 0); - assert(random_sec >= 0); - assert(ll); - - next_timeout = sec * USEC_PER_SEC; - - if (random_sec) - next_timeout += random_u32() % (random_sec * USEC_PER_SEC); - - if (sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now) < 0) - time_now = now(clock_boottime_or_monotonic()); - - ll->next_wakeup = time_now + next_timeout; - ll->next_wakeup_valid = 1; -} - -static bool ipv4ll_arp_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { - assert(ll); - assert(arp); - - if (memcmp(arp->arp_spa, &ll->address, sizeof(ll->address)) == 0 && - memcmp(arp->arp_sha, &ll->mac_addr, ETH_ALEN) != 0) - return true; - - return false; -} - -static bool ipv4ll_arp_probe_conflict (sd_ipv4ll *ll, struct ether_arp *arp) { - assert(ll); - assert(arp); - - if (ipv4ll_arp_conflict(ll, arp)) - return true; - - if (memcmp(arp->arp_tpa, &ll->address, sizeof(ll->address)) == 0 && - memcmp(arp->arp_sha, &ll->mac_addr, ETH_ALEN)) - return true; - - return false; -} - -static void ipv4ll_run_state_machine(sd_ipv4ll *ll, IPv4LLTrigger trigger, void *trigger_data) { - struct ether_arp out_packet; - int out_packet_ready = 0; - int r = 0; - - assert(ll); - assert(trigger < _IPV4LL_TRIGGER_MAX); - - if (ll->state == IPV4LL_STATE_INIT) { - - log_ipv4ll(ll, "PROBE"); - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - - } else if ((ll->state == IPV4LL_STATE_WAITING_PROBE && trigger == IPV4LL_TRIGGER_TIMEOUT) || - (ll->state == IPV4LL_STATE_PROBING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration < PROBE_NUM-2)) { - - /* Send a probe */ - arp_packet_probe(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; - ipv4ll_set_state(ll, IPV4LL_STATE_PROBING, 0); - - ipv4ll_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); - - } else if (ll->state == IPV4LL_STATE_PROBING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration >= PROBE_NUM-2) { - - /* Send the last probe */ - arp_packet_probe(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_ANNOUNCE, 1); - - ipv4ll_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); - - } else if ((ll->state == IPV4LL_STATE_WAITING_ANNOUNCE && trigger == IPV4LL_TRIGGER_TIMEOUT) || - (ll->state == IPV4LL_STATE_ANNOUNCING && trigger == IPV4LL_TRIGGER_TIMEOUT && ll->iteration < ANNOUNCE_NUM-1)) { - - /* Send announcement packet */ - arp_packet_announcement(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; - ipv4ll_set_state(ll, IPV4LL_STATE_ANNOUNCING, 0); - - ipv4ll_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); - - if (ll->iteration == 0) { - log_ipv4ll(ll, "ANNOUNCE"); - ll->claimed_address = ll->address; - ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_BIND); - if (!ll || ll->state == IPV4LL_STATE_STOPPED) - goto out; - - ll->conflict = 0; - } - - } else if ((ll->state == IPV4LL_STATE_ANNOUNCING && trigger == IPV4LL_TRIGGER_TIMEOUT && - ll->iteration >= ANNOUNCE_NUM-1)) { - - ipv4ll_set_state(ll, IPV4LL_STATE_RUNNING, 0); - ll->next_wakeup_valid = 0; - - } else if (trigger == IPV4LL_TRIGGER_PACKET) { - - int conflicted = 0; - usec_t time_now; - struct ether_arp* in_packet = (struct ether_arp*)trigger_data; - - assert(in_packet); - - if (IN_SET(ll->state, IPV4LL_STATE_ANNOUNCING, IPV4LL_STATE_RUNNING)) { - - if (ipv4ll_arp_conflict(ll, in_packet)) { - - r = sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now); - if (r < 0) - goto out; - - /* Defend address */ - if (time_now > ll->defend_window) { - ll->defend_window = time_now + DEFEND_INTERVAL * USEC_PER_SEC; - arp_packet_announcement(&out_packet, ll->address, &ll->mac_addr); - out_packet_ready = 1; - } else - conflicted = 1; - } - - } else if (IN_SET(ll->state, IPV4LL_STATE_WAITING_PROBE, - IPV4LL_STATE_PROBING, - IPV4LL_STATE_WAITING_ANNOUNCE)) { - - conflicted = ipv4ll_arp_probe_conflict(ll, in_packet); - } - - if (conflicted) { - log_ipv4ll(ll, "CONFLICT"); - ll = ipv4ll_client_notify(ll, IPV4LL_EVENT_CONFLICT); - if (!ll || ll->state == IPV4LL_STATE_STOPPED) - goto out; - - ll->claimed_address = 0; - - /* Pick a new address */ - r = ipv4ll_pick_address(ll, &ll->address); - if (r < 0) - goto out; - ll->conflict++; - ll->defend_window = 0; - ipv4ll_set_state(ll, IPV4LL_STATE_WAITING_PROBE, 1); - - if (ll->conflict >= MAX_CONFLICTS) { - log_ipv4ll(ll, "MAX_CONFLICTS"); - ipv4ll_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); - } else - ipv4ll_set_next_wakeup(ll, 0, PROBE_WAIT); - - } - } - - if (out_packet_ready) { - r = arp_network_send_raw_socket(ll->fd, &ll->link, &out_packet); - if (r < 0) { - log_ipv4ll(ll, "failed to send arp packet out"); - goto out; - } - } - - if (ll->next_wakeup_valid) { - ll->timer = sd_event_source_unref(ll->timer); - r = sd_event_add_time(ll->event, &ll->timer, clock_boottime_or_monotonic(), - ll->next_wakeup, 0, ipv4ll_timer, ll); - if (r < 0) - goto out; - - r = sd_event_source_set_priority(ll->timer, ll->event_priority); - if (r < 0) - goto out; - - r = sd_event_source_set_description(ll->timer, "ipv4ll-timer"); - if (r < 0) - goto out; - } - -out: - if (r < 0 && ll) - ipv4ll_stop(ll, r); -} - -static int ipv4ll_receive_message(sd_event_source *s, int fd, - uint32_t revents, void *userdata) { - int r; - struct ether_arp arp; - sd_ipv4ll *ll = (sd_ipv4ll*)userdata; - - assert(ll); - - r = read(fd, &arp, sizeof(struct ether_arp)); - if (r < (int) sizeof(struct ether_arp)) - return 0; - - r = arp_packet_verify_headers(&arp); - if (r < 0) - return 0; - - ipv4ll_run_state_machine(ll, IPV4LL_TRIGGER_PACKET, &arp); +int sd_ipv4ll_stop(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); - return 0; + return sd_ipv4acd_stop(ll->acd); } -int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) { assert_return(ll, -EINVAL); - assert_return(interface_index > 0, -EINVAL); - assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT, - IPV4LL_STATE_STOPPED), -EBUSY); - - ll->index = interface_index; + assert_return(ifindex > 0, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - return 0; + return sd_ipv4acd_set_ifindex(ll->acd, ifindex); } int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { - bool need_restart = false; + int r; assert_return(ll, -EINVAL); assert_return(addr, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - if (memcmp(&ll->mac_addr, addr, ETH_ALEN) == 0) - return 0; - - if (!IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED)) { - log_ipv4ll(ll, "Changing MAC address on running IPv4LL " - "client, restarting"); - ll = ipv4ll_stop(ll, IPV4LL_EVENT_STOP); - need_restart = true; - } - - if (!ll) - return 0; - - memcpy(&ll->mac_addr, addr, ETH_ALEN); - - if (need_restart) - sd_ipv4ll_start(ll); + r = sd_ipv4acd_set_mac(ll->acd, addr); + if (r < 0) + return r; + ll->mac = *addr; return 0; } int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { assert_return(ll, -EINVAL); - ll->event = sd_event_unref(ll->event); - - return 0; + return sd_ipv4acd_detach_event(ll->acd); } -int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int priority) { - int r; - +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) { assert_return(ll, -EINVAL); - assert_return(!ll->event, -EBUSY); - - if (event) - ll->event = sd_event_ref(event); - else { - r = sd_event_default(&ll->event); - if (r < 0) { - ipv4ll_stop(ll, IPV4LL_EVENT_STOP); - return r; - } - } - - ll->event_priority = priority; - return 0; + return sd_ipv4acd_attach_event(ll->acd, event, priority); } -int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_cb_t cb, void *userdata) { +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) { assert_return(ll, -EINVAL); - ll->cb = cb; + ll->callback = cb; ll->userdata = userdata; return 0; } -int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address){ +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address) { assert_return(ll, -EINVAL); assert_return(address, -EINVAL); - if (ll->claimed_address == 0) { + if (ll->claimed_address == 0) return -ENOENT; - } address->s_addr = ll->claimed_address; + return 0; } -int sd_ipv4ll_set_address_seed (sd_ipv4ll *ll, uint8_t seed[8]) { - unsigned int entropy; - int r; - +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed) { assert_return(ll, -EINVAL); - assert_return(seed, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - entropy = *seed; + ll->seed.value = htole64(seed); + ll->seed_set = true; - free(ll->random_data); - free(ll->random_data_state); + return 0; +} - ll->random_data = new0(struct random_data, 1); - ll->random_data_state = new0(char, 128); +int sd_ipv4ll_is_running(sd_ipv4ll *ll) { + assert_return(ll, false); - if (!ll->random_data || !ll->random_data_state) { - r = -ENOMEM; - goto error; - } + return sd_ipv4acd_is_running(ll->acd); +} - r = initstate_r((unsigned int)entropy, ll->random_data_state, 128, ll->random_data); - if (r < 0) - goto error; +static bool ipv4ll_address_is_valid(const struct in_addr *address) { + assert(address); -error: - if (r < 0){ - free(ll->random_data); - free(ll->random_data_state); - ll->random_data = NULL; - ll->random_data_state = NULL; - } - return r; + if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address)) + return false; + + return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U); } -bool sd_ipv4ll_is_running(sd_ipv4ll *ll) { +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address) { + int r; + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + assert_return(ipv4ll_address_is_valid(address), -EINVAL); - return !IN_SET(ll->state, IPV4LL_STATE_INIT, IPV4LL_STATE_STOPPED); -} + r = sd_ipv4acd_set_address(ll->acd, address); + if (r < 0) + return r; -#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + ll->address = address->s_addr; -int sd_ipv4ll_start (sd_ipv4ll *ll) { - int r; + return 0; +} - assert_return(ll, -EINVAL); - assert_return(ll->event, -EINVAL); - assert_return(ll->index > 0, -EINVAL); - assert_return(IN_SET(ll->state, IPV4LL_STATE_INIT, - IPV4LL_STATE_STOPPED), -EBUSY); +#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b) - ll->state = IPV4LL_STATE_INIT; +static int ipv4ll_pick_address(sd_ipv4ll *ll) { + _cleanup_free_ char *address = NULL; + be32_t addr; - r = arp_network_bind_raw_socket(ll->index, &ll->link); + assert(ll); - if (r < 0) - goto out; + do { + uint64_t h; - ll->fd = r; - ll->conflict = 0; - ll->defend_window = 0; - ll->claimed_address = 0; + h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes); - if (!ll->random_data) { - uint8_t seed[8]; + /* Increase the generation counter by one */ + ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1); - /* Fallback to mac */ - siphash24(seed, &ll->mac_addr.ether_addr_octet, - ETH_ALEN, HASH_KEY.bytes); + addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK); + } while (addr == ll->address || + IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U)); - r = sd_ipv4ll_set_address_seed(ll, seed); - if (r < 0) - goto out; - } + (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address); + log_ipv4ll(ll, "Picked new IP address %s.", strna(address)); - if (ll->address == 0) { - r = ipv4ll_pick_address(ll, &ll->address); - if (r < 0) - goto out; - } + return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr }); +} - ipv4ll_set_state (ll, IPV4LL_STATE_INIT, 1); +#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) - r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, - EPOLLIN, ipv4ll_receive_message, ll); - if (r < 0) - goto out; +int sd_ipv4ll_start(sd_ipv4ll *ll) { + int r; + bool picked_address = false; - r = sd_event_source_set_priority(ll->receive_message, ll->event_priority); - if (r < 0) - goto out; + assert_return(ll, -EINVAL); + assert_return(!ether_addr_is_null(&ll->mac), -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - r = sd_event_source_set_description(ll->receive_message, "ipv4ll-receive-message"); - if (r < 0) - goto out; + /* If no random seed is set, generate some from the MAC address */ + if (!ll->seed_set) + ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes)); - r = sd_event_add_time(ll->event, - &ll->timer, - clock_boottime_or_monotonic(), - now(clock_boottime_or_monotonic()), 0, - ipv4ll_timer, ll); + /* Restart the generation counter. */ + ll->seed.generation = 0; - if (r < 0) - goto out; + if (ll->address == 0) { + r = ipv4ll_pick_address(ll); + if (r < 0) + return r; - r = sd_event_source_set_priority(ll->timer, ll->event_priority); - if (r < 0) - goto out; + picked_address = true; + } - r = sd_event_source_set_description(ll->timer, "ipv4ll-timer"); -out: - if (r < 0) - ipv4ll_stop(ll, IPV4LL_EVENT_STOP); + r = sd_ipv4acd_start(ll->acd); + if (r < 0) { - return 0; -} + /* We couldn't start? If so, let's forget the picked address again, the user might make a change and + * retry, and we want the new data to take effect when picking an address. */ + if (picked_address) + ll->address = 0; -int sd_ipv4ll_stop(sd_ipv4ll *ll) { - ipv4ll_stop(ll, IPV4LL_EVENT_STOP); - if (ll) - ipv4ll_set_state(ll, IPV4LL_STATE_STOPPED, 1); + return r; + } return 0; } -sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) { - if (ll) - assert_se(REFCNT_INC(ll->n_ref) >= 2); +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { + assert(ll); - return ll; + if (ll->callback) + ll->callback(ll, event, ll->userdata); } -sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { - if (ll && REFCNT_DEC(ll->n_ref) == 0) { - ll->receive_message = - sd_event_source_unref(ll->receive_message); - ll->fd = safe_close(ll->fd); - - ll->timer = sd_event_source_unref(ll->timer); +void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + sd_ipv4ll *ll = userdata; + IPV4LL_DONT_DESTROY(ll); + int r; - sd_ipv4ll_detach_event(ll); + assert(acd); + assert(ll); - free(ll->random_data); - free(ll->random_data_state); - free(ll); + switch (event) { - return NULL; - } + case SD_IPV4ACD_EVENT_STOP: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); + ll->claimed_address = 0; + break; - return ll; -} + case SD_IPV4ACD_EVENT_BIND: + ll->claimed_address = ll->address; + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND); + break; -DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4ll*, sd_ipv4ll_unref); -#define _cleanup_ipv4ll_free_ _cleanup_(sd_ipv4ll_unrefp) + case SD_IPV4ACD_EVENT_CONFLICT: + /* if an address was already bound we must call up to the + user to handle this, otherwise we just try again */ + if (ll->claimed_address != 0) { + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_CONFLICT); -int sd_ipv4ll_new(sd_ipv4ll **ret) { - _cleanup_ipv4ll_free_ sd_ipv4ll *ll = NULL; + ll->claimed_address = 0; + } else { + r = ipv4ll_pick_address(ll); + if (r < 0) + goto error; - assert_return(ret, -EINVAL); + r = sd_ipv4acd_start(ll->acd); + if (r < 0) + goto error; + } - ll = new0(sd_ipv4ll, 1); - if (!ll) - return -ENOMEM; + break; - ll->n_ref = REFCNT_INIT; - ll->state = IPV4LL_STATE_INIT; - ll->index = -1; - ll->fd = -1; + default: + assert_not_reached("Invalid IPv4ACD event."); + } - *ret = ll; - ll = NULL; + return; - return 0; +error: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); } diff --git a/src/libsystemd-network/sd-lldp.c b/src/libsystemd-network/sd-lldp.c index 6a2c05185d..0bd1e66aa0 100644 --- a/src/libsystemd-network/sd-lldp.c +++ b/src/libsystemd-network/sd-lldp.c @@ -1,682 +1,539 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** - This file is part of systemd. + This file is part of systemd. - Copyright (C) 2014 Tom Gundersen - Copyright (C) 2014 Susant Sahani + Copyright (C) 2014 Tom Gundersen + Copyright (C) 2014 Susant Sahani - 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 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. + 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/>. + 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 <arpa/inet.h> -#include "siphash24.h" -#include "hashmap.h" - -#include "lldp-tlv.h" -#include "lldp-port.h" #include "sd-lldp.h" -#include "prioq.h" -#include "lldp-internal.h" -#include "lldp-util.h" - -typedef enum LLDPAgentRXState { - LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL = 4, - LLDP_AGENT_RX_DELETE_AGED_INFO, - LLDP_AGENT_RX_LLDP_INITIALIZE, - LLDP_AGENT_RX_WAIT_FOR_FRAME, - LLDP_AGENT_RX_RX_FRAME, - LLDP_AGENT_RX_DELETE_INFO, - LLDP_AGENT_RX_UPDATE_INFO, - _LLDP_AGENT_RX_STATE_MAX, - _LLDP_AGENT_RX_INVALID = -1, -} LLDPAgentRXState; - -/* Section 10.5.2.2 Reception counters */ -struct lldp_agent_statistics { - uint64_t stats_ageouts_total; - uint64_t stats_frames_discarded_total; - uint64_t stats_frames_in_errors_total; - uint64_t stats_frames_in_total; - uint64_t stats_tlvs_discarded_total; - uint64_t stats_tlvs_unrecognized_total; -}; - -struct sd_lldp { - lldp_port *port; - - Prioq *by_expiry; - Hashmap *neighbour_mib; - - sd_lldp_cb_t cb; - - void *userdata; - - LLDPAgentRXState rx_state; - lldp_agent_statistics statistics; -}; - -static unsigned long chassis_id_hash_func(const void *p, - const uint8_t hash_key[HASH_KEY_SIZE]) { - uint64_t u; - const lldp_chassis_id *id = p; - - assert(id); - - siphash24((uint8_t *) &u, id->data, id->length, hash_key); - - return (unsigned long) u; -} -static int chassis_id_compare_func(const void *_a, const void *_b) { - const lldp_chassis_id *a, *b; - - a = _a; - b = _b; +#include "alloc-util.h" +#include "fd-util.h" +#include "lldp-internal.h" +#include "lldp-neighbor.h" +#include "lldp-network.h" +#include "socket-util.h" +#include "ether-addr-util.h" - assert(!a->length || a->data); - assert(!b->length || b->data); +#define LLDP_DEFAULT_NEIGHBORS_MAX 128U - if (a->type != b->type) - return -1; +static void lldp_flush_neighbors(sd_lldp *lldp) { + sd_lldp_neighbor *n; - if (a->length != b->length) - return a->length < b->length ? -1 : 1; + assert(lldp); - return memcmp(a->data, b->data, a->length); + while ((n = hashmap_first(lldp->neighbor_by_id))) + lldp_neighbor_unlink(n); } -static const struct hash_ops chassis_id_hash_ops = { - .hash = chassis_id_hash_func, - .compare = chassis_id_compare_func -}; - -static void lldp_mib_delete_objects(sd_lldp *lldp); -static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state); -static void lldp_run_state_machine(sd_lldp *ll); - -static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) { - int r; - +static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) { assert(lldp); - assert(tlv); - - /* Remove expired packets */ - if (prioq_size(lldp->by_expiry) > 0) { - lldp_set_state(lldp, LLDP_AGENT_RX_DELETE_INFO); + log_lldp("Invoking callback for '%c'.", event); - lldp_mib_delete_objects(lldp); - } - - r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv); - if (r < 0) - goto out; - - lldp_set_state(lldp, LLDP_AGENT_RX_UPDATE_INFO); - - log_lldp("Packet added. MIB size: %d , PQ size: %d", - hashmap_size(lldp->neighbour_mib), - prioq_size(lldp->by_expiry)); - - lldp->statistics.stats_frames_in_total ++; - - out: - if (r < 0) - log_lldp("Receive frame failed: %s", strerror(-r)); - - lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME); + if (!lldp->callback) + return; - return 0; + lldp->callback(lldp, event, n, lldp->userdata); } -/* 10.3.2 LLDPDU validation: rxProcessFrame() */ -int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { - uint16_t type, len, i, l, t; - bool chassis_id = false; - bool malformed = false; - bool port_id = false; - bool ttl = false; - bool end = false; - lldp_port *port; - uint8_t *p, *q; - sd_lldp *lldp; - int r; - - assert(tlv); - assert(length > 0); +static int lldp_make_space(sd_lldp *lldp, size_t extra) { + usec_t t = USEC_INFINITY; + bool changed = false; - port = (lldp_port *) tlv->userdata; - lldp = (sd_lldp *) port->userdata; - - if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) { - log_lldp("Port is disabled : %s . Dropping ...", - lldp->port->ifname); - goto out; - } - - lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME); - - p = tlv->pdu; - p += sizeof(struct ether_header); + assert(lldp); - for (i = 1, l = 0; l <= length; i++) { + /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries + * are free. */ - memcpy(&t, p, sizeof(uint16_t)); + for (;;) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - type = ntohs(t) >> 9; - len = ntohs(t) & 0x01ff; + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) + break; - if (type == LLDP_TYPE_END) { - if (len != 0) { - log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...", - len); + sd_lldp_neighbor_ref(n); - malformed = true; - goto out; - } + if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra)) + goto remove_one; - end = true; + if (t == USEC_INFINITY) + t = now(clock_boottime_or_monotonic()); + if (n->until > t) break; - } else if (type >=_LLDP_TYPE_MAX) { - log_lldp("TLV type not recognized %d . Dropping ...", - type); - malformed = true; - goto out; - } + remove_one: + lldp_neighbor_unlink(n); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n); + changed = true; + } - /* skip type and lengh encoding */ - p += 2; - q = p; + return changed; +} - p += len; - l += (len + 2); +static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + assert(lldp); + assert(n); - if (i <= 3) { - if (i != type) { - log_lldp("TLV missing or out of order. Dropping ..."); + /* Don't keep data with a zero TTL */ + if (n->ttl <= 0) + return false; - malformed = true; - goto out; - } - } + /* Filter out data from the filter address */ + if (!ether_addr_is_null(&lldp->filter_address) && + ether_addr_equal(&lldp->filter_address, &n->source_address)) + return false; - switch(type) { - case LLDP_TYPE_CHASSIS_ID: + /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with + * no caps field set. */ + if (n->has_capabilities && + (n->enabled_capabilities & lldp->capability_mask) == 0) + return false; - if (len < 2) { - log_lldp("Received malformed Chassis ID TLV len = %d. Dropping", - len); + /* Keep everything else */ + return true; +} - malformed = true; - goto out; - } +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor); - if (chassis_id) { - log_lldp("Duplicate Chassis ID TLV found. Dropping ..."); +static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL; + bool keep; + int r; - malformed = true; - goto out; - } + assert(lldp); + assert(n); + assert(!n->lldp); - /* Look what subtype it has */ - if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED || - *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) { - log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...", - *q); + keep = lldp_keep_neighbor(lldp, n); - malformed = true; - goto out; + /* First retrieve the old entry for this MSAP */ + old = hashmap_get(lldp->neighbor_by_id, &n->id); + if (old) { + sd_lldp_neighbor_ref(old); - } + if (!keep) { + lldp_neighbor_unlink(old); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + return 0; + } - chassis_id = true; + if (lldp_neighbor_equal(n, old)) { + /* Is this equal, then restart the TTL counter, but don't do anyting else. */ + old->timestamp = n->timestamp; + lldp_start_timer(lldp, old); + lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old); + return 0; + } - break; - case LLDP_TYPE_PORT_ID: + /* Data changed, remove the old entry, and add a new one */ + lldp_neighbor_unlink(old); - if (len < 2) { - log_lldp("Received malformed Port ID TLV len = %d. Dropping", - len); + } else if (!keep) + return 0; - malformed = true; - goto out; - } + /* Then, make room for at least one new neighbor */ + lldp_make_space(lldp, 1); - if (port_id) { - log_lldp("Duplicate Port ID TLV found. Dropping ..."); + r = hashmap_put(lldp->neighbor_by_id, &n->id, n); + if (r < 0) + goto finish; - malformed = true; - goto out; - } + r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx); + if (r < 0) { + assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n); + goto finish; + } - /* Look what subtype it has */ - if (*q == LLDP_PORT_SUBTYPE_RESERVED || - *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) { - log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...", - *q); + n->lldp = lldp; - malformed = true; - goto out; + lldp_start_timer(lldp, n); + lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n); - } + return 1; - port_id = true; +finish: + if (old) + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); - break; - case LLDP_TYPE_TTL: + return r; +} - if(len != 2) { - log_lldp( - "Received invalid lenth: %d TTL TLV. Dropping ...", - len); +static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) { + int r; - malformed = true; - goto out; - } + assert(lldp); + assert(n); - if (ttl) { - log_lldp("Duplicate TTL TLV found. Dropping ..."); + r = lldp_neighbor_parse(n); + if (r == -EBADMSG) /* Ignore bad messages */ + return 0; + if (r < 0) + return r; - malformed = true; - goto out; - } + r = lldp_add_neighbor(lldp, n); + if (r < 0) { + log_lldp_errno(r, "Failed to add datagram. Ignoring."); + return 0; + } - ttl = true; + log_lldp("Successfully processed LLDP datagram."); + return 0; +} - break; - default: +static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + ssize_t space, length; + sd_lldp *lldp = userdata; + struct timespec ts; - if (len == 0) { - log_lldp("TLV type = %d's, length 0 received . Dropping ...", - type); + assert(fd >= 0); + assert(lldp); - malformed = true; - goto out; - } - break; - } - } + space = next_datagram_size_fd(fd); + if (space < 0) + return log_lldp_errno(space, "Failed to determine datagram size to read: %m"); - if(!chassis_id || !port_id || !ttl || !end) { - log_lldp( "One or more mandotory TLV missing . Dropping ..."); + n = lldp_neighbor_new(space); + if (!n) + return -ENOMEM; - malformed = true; - goto out; + length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT); + if (length < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + return log_lldp_errno(errno, "Failed to read LLDP datagram: %m"); } - r = tlv_packet_parse_pdu(tlv, length); - if (r < 0) { - log_lldp( "Failed to parse the TLV. Dropping ..."); - - malformed = true; - goto out; + if ((size_t) length != n->raw_size) { + log_lldp("Packet size mismatch."); + return -EINVAL; } - return lldp_receive_frame(lldp, tlv); - - out: - lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME); + /* Try to get the timestamp of this packet if it is known */ + if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0) + triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts)); + else + triple_timestamp_get(&n->timestamp); - if (malformed) { - lldp->statistics.stats_frames_discarded_total ++; - lldp->statistics.stats_frames_in_errors_total ++; - } + return lldp_handle_datagram(lldp, n); +} - tlv_packet_free(tlv); +static void lldp_reset(sd_lldp *lldp) { + assert(lldp); - return 0; + lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); + lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); + lldp->fd = safe_close(lldp->fd); } -static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) { - const lldp_neighbour_port *p = a, *q = b; +_public_ int sd_lldp_start(sd_lldp *lldp) { + int r; - if (p->until < q->until) - return -1; + assert_return(lldp, -EINVAL); + assert_return(lldp->event, -EINVAL); + assert_return(lldp->ifindex > 0, -EINVAL); - if (p->until > q->until) - return 1; + if (lldp->fd >= 0) + return 0; - return 0; -} + assert(!lldp->io_event_source); -static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) { + lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex); + if (lldp->fd < 0) + return lldp->fd; - assert(lldp); - assert(state < _LLDP_AGENT_RX_STATE_MAX); + r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); + if (r < 0) + goto fail; - lldp->rx_state = state; + r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); + if (r < 0) + goto fail; - lldp_run_state_machine(lldp); -} + (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); -static void lldp_run_state_machine(sd_lldp *lldp) { + log_lldp("Started LLDP client"); + return 1; - if (lldp->rx_state == LLDP_AGENT_RX_UPDATE_INFO) - if (lldp->cb) - lldp->cb(lldp, LLDP_AGENT_RX_UPDATE_INFO, lldp->userdata); +fail: + lldp_reset(lldp); + return r; } -/* 10.5.5.2.1 mibDeleteObjects () - * The mibDeleteObjects () procedure deletes all information in the LLDP remote - * systems MIB associated with the MSAP identifier if an LLDPDU is received with - * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */ - -static void lldp_mib_delete_objects(sd_lldp *lldp) { - lldp_neighbour_port *p; - usec_t t = 0; +_public_ int sd_lldp_stop(sd_lldp *lldp) { + assert_return(lldp, -EINVAL); - /* Remove all entries that are past their TTL */ - for (;;) { + if (lldp->fd < 0) + return 0; - if (prioq_size(lldp->by_expiry) <= 0) - break; + log_lldp("Stopping LLDP client"); - p = prioq_peek(lldp->by_expiry); - if (!p) - break; + lldp_reset(lldp); + lldp_flush_neighbors(lldp); - if (t <= 0) - t = now(CLOCK_BOOTTIME); + return 1; +} - if (p->until > t) - break; +_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) { + int r; - lldp_neighbour_port_remove_and_free(p); + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + assert_return(!lldp->event, -EBUSY); - lldp->statistics.stats_ageouts_total ++; + if (event) + lldp->event = sd_event_ref(event); + else { + r = sd_event_default(&lldp->event); + if (r < 0) + return r; } -} -static void lldp_mib_objects_flush(sd_lldp *lldp) { - lldp_neighbour_port *p, *q; - lldp_chassis *c; + lldp->event_priority = priority; - assert(lldp); - assert(lldp->neighbour_mib); - assert(lldp->by_expiry); + return 0; +} - /* Drop all packets */ - while ((c = hashmap_steal_first(lldp->neighbour_mib))) { +_public_ int sd_lldp_detach_event(sd_lldp *lldp) { - LIST_FOREACH_SAFE(port, p, q, c->ports) { - lldp_neighbour_port_remove_and_free(p); - } - } + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); - assert(hashmap_size(lldp->neighbour_mib) == 0); - assert(prioq_size(lldp->by_expiry) == 0); + lldp->event = sd_event_unref(lldp->event); + return 0; } -int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) { - _cleanup_free_ char *temp_path = NULL; - _cleanup_fclose_ FILE *f = NULL; - uint8_t *mac, *port_id, type; - lldp_neighbour_port *p; - uint16_t data = 0, length = 0; - char buf[LINE_MAX]; - lldp_chassis *c; - usec_t time; - Iterator i; - int r; +_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) { + assert_return(lldp, NULL); - assert(lldp); - assert(lldp_file); + return lldp->event; +} - r = fopen_temporary(lldp_file, &f, &temp_path); - if (r < 0) - goto finish; +_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) { + assert_return(lldp, -EINVAL); - fchmod(fileno(f), 0644); + lldp->callback = cb; + lldp->userdata = userdata; - HASHMAP_FOREACH(c, lldp->neighbour_mib, i) { - LIST_FOREACH(port, p, c->ports) { - _cleanup_free_ char *s = NULL; - char *k, *t; + return 0; +} - r = lldp_read_chassis_id(p->packet, &type, &length, &mac); - if (r < 0) - continue; +_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) { + assert_return(lldp, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); - sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ", - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type); + lldp->ifindex = ifindex; + return 0; +} - s = strdup(buf); - if (!s) - return -ENOMEM; +_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) { - r = lldp_read_port_id(p->packet, &type, &length, &port_id); - if (r < 0) - continue; + if (!lldp) + return NULL; - if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) { - k = strndup((char *) port_id, length -1); - if (!k) - return -ENOMEM; + assert(lldp->n_ref > 0); + lldp->n_ref++; - sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type); - free(k); - } else { - mac = port_id; - sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ", - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type); - } + return lldp; +} - k = strappend(s, buf); - if (!k) - return -ENOMEM; +_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) { - free(s); - s = k; + if (!lldp) + return NULL; - time = now(CLOCK_BOOTTIME); + assert(lldp->n_ref > 0); + lldp->n_ref --; - /* Don't write expired packets */ - if (time - p->until <= 0) - continue; + if (lldp->n_ref > 0) + return NULL; - sprintf(buf, "'_TTL="USEC_FMT"' ", p->until); + lldp_reset(lldp); + sd_lldp_detach_event(lldp); + lldp_flush_neighbors(lldp); - k = strappend(s, buf); - if (!k) - return -ENOMEM; + hashmap_free(lldp->neighbor_by_id); + prioq_free(lldp->neighbor_by_expiry); + free(lldp); - free(s); - s = k; + return NULL; +} - r = lldp_read_system_name(p->packet, &length, &k); - if (r < 0) - k = strappend(s, "'_NAME=N/A' "); - else { - t = strndup(k, length); - if (!t) - return -ENOMEM; +_public_ int sd_lldp_new(sd_lldp **ret) { + _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; + int r; - k = strjoin(s, "'_NAME=", t, "' ", NULL); - free(t); - } + assert_return(ret, -EINVAL); - if (!k) - return -ENOMEM; + lldp = new0(sd_lldp, 1); + if (!lldp) + return -ENOMEM; - free(s); - s = k; + lldp->n_ref = 1; + lldp->fd = -1; + lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX; + lldp->capability_mask = (uint16_t) -1; - (void) lldp_read_system_capability(p->packet, &data); + lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops); + if (!lldp->neighbor_by_id) + return -ENOMEM; - sprintf(buf, "'_CAP=%x'", data); + r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func); + if (r < 0) + return r; - k = strappend(s, buf); - if (!k) - return -ENOMEM; + *ret = lldp; + lldp = NULL; - free(s); - s = k; + return 0; +} - fprintf(f, "%s\n", s); - } - } - r = 0; +static int neighbor_compare_func(const void *a, const void *b) { + const sd_lldp_neighbor * const*x = a, * const *y = b; - fflush(f); + return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id); +} - if (ferror(f) || rename(temp_path, lldp_file) < 0) { - r = -errno; - unlink(lldp_file); - unlink(temp_path); - } +static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { + sd_lldp *lldp = userdata; + int r, q; - finish: + r = lldp_make_space(lldp, 0); if (r < 0) - log_error("Failed to save lldp data %s: %s", lldp_file, strerror(-r)); + return log_lldp_errno(r, "Failed to make space: %m"); - return r; + q = lldp_start_timer(lldp, NULL); + if (q < 0) + return log_lldp_errno(q, "Failed to restart timer: %m"); + + return 0; } -int sd_lldp_start(sd_lldp *lldp) { +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) { + sd_lldp_neighbor *n; int r; - assert_return(lldp, -EINVAL); - assert_return(lldp->port, -EINVAL); - - lldp->port->status = LLDP_PORT_STATUS_ENABLED; + assert(lldp); - lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE); + if (neighbor) + lldp_neighbor_start_ttl(neighbor); - r = lldp_port_start(lldp->port); - if (r < 0) { - log_lldp("Failed to start Port : %s , %s", - lldp->port->ifname, - strerror(-r)); + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) { - lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL); + if (lldp->timer_event_source) + return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF); - return r; + return 0; } - lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME); - - return 0; -} - -int sd_lldp_stop(sd_lldp *lldp) { - int r; + if (lldp->timer_event_source) { + r = sd_event_source_set_time(lldp->timer_event_source, n->until); + if (r < 0) + return r; - assert_return(lldp, -EINVAL); - assert_return(lldp->port, -EINVAL); + return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT); + } - lldp->port->status = LLDP_PORT_STATUS_DISABLED; + if (!lldp->event) + return 0; - r = lldp_port_stop(lldp->port); + r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp); if (r < 0) return r; - lldp_mib_objects_flush(lldp); + r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority); + if (r < 0) + return r; + (void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer"); return 0; } -int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) { - int r; +_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) { + sd_lldp_neighbor **l = NULL, *n; + Iterator i; + int k = 0, r; assert_return(lldp, -EINVAL); - assert_return(!lldp->port->event, -EBUSY); + assert_return(ret, -EINVAL); - if (event) - lldp->port->event = sd_event_ref(event); - else { - r = sd_event_default(&lldp->port->event); - if (r < 0) - return r; + if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */ + *ret = NULL; + return 0; } - lldp->port->event_priority = priority; + l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id)); + if (!l) + return -ENOMEM; - return 0; -} + r = lldp_start_timer(lldp, NULL); + if (r < 0) { + free(l); + return r; + } -int sd_lldp_detach_event(sd_lldp *lldp) { + HASHMAP_FOREACH(n, lldp->neighbor_by_id, i) + l[k++] = sd_lldp_neighbor_ref(n); - assert_return(lldp, -EINVAL); + assert((size_t) k == hashmap_size(lldp->neighbor_by_id)); - lldp->port->event = sd_event_unref(lldp->port->event); + /* Return things in a stable order */ + qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func); + *ret = l; - return 0; + return k; } -int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) { +_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) { assert_return(lldp, -EINVAL); + assert_return(m <= 0, -EINVAL); - lldp->cb = cb; - lldp->userdata = userdata; + lldp->neighbors_max = m; + lldp_make_space(lldp, 0); return 0; } -void sd_lldp_free(sd_lldp *lldp) { - - if (!lldp) - return; - - /* Drop all packets */ - lldp_mib_objects_flush(lldp); - - lldp_port_free(lldp->port); +_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) { + assert_return(lldp, -EINVAL); + assert_return(mask != 0, -EINVAL); - hashmap_free(lldp->neighbour_mib); - prioq_free(lldp->by_expiry); + lldp->capability_mask = mask; - free(lldp); + return 0; } -int sd_lldp_new(int ifindex, - const char *ifname, - const struct ether_addr *mac, - sd_lldp **ret) { - _cleanup_lldp_free_ sd_lldp *lldp = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(ifindex > 0, -EINVAL); - assert_return(ifname, -EINVAL); - assert_return(mac, -EINVAL); - - lldp = new0(sd_lldp, 1); - if (!lldp) - return -ENOMEM; - - r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port); - if (r < 0) - return r; - - lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops); - if (!lldp->neighbour_mib) - return -ENOMEM; - - r = prioq_ensure_allocated(&lldp->by_expiry, - ttl_expiry_item_prioq_compare_func); - if (r < 0) - return r; +_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) { + assert_return(lldp, -EINVAL); - lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL; + /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so + * that our own can be filtered out here. */ - *ret = lldp; - lldp = NULL; + if (addr) + lldp->filter_address = *addr; + else + zero(lldp->filter_address); return 0; } diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c new file mode 100644 index 0000000000..07b0d7f704 --- /dev/null +++ b/src/libsystemd-network/sd-ndisc.c @@ -0,0 +1,422 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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/icmp6.h> +#include <netinet/in.h> + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "fd-util.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "ndisc-internal.h" +#include "ndisc-router.h" +#include "socket-util.h" +#include "string-util.h" +#include "util.h" + +#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC) +#define NDISC_MAX_ROUTER_SOLICITATIONS 3U + +static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) { + assert(ndisc); + + log_ndisc("Invoking callback for '%c'.", event); + + if (!ndisc->callback) + return; + + ndisc->callback(ndisc, event, rt, ndisc->userdata); +} + +_public_ int sd_ndisc_set_callback( + sd_ndisc *nd, + sd_ndisc_callback_t callback, + void *userdata) { + + assert_return(nd, -EINVAL); + + nd->callback = callback; + nd->userdata = userdata; + + return 0; +} + +_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { + assert_return(nd, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(nd->fd < 0, -EBUSY); + + nd->ifindex = ifindex; + return 0; +} + +_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { + assert_return(nd, -EINVAL); + + if (mac_addr) + nd->mac_addr = *mac_addr; + else + zero(nd->mac_addr); + + return 0; +} + +_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { + int r; + + assert_return(nd, -EINVAL); + assert_return(nd->fd < 0, -EBUSY); + assert_return(!nd->event, -EBUSY); + + if (event) + nd->event = sd_event_ref(event); + else { + r = sd_event_default(&nd->event); + if (r < 0) + return 0; + } + + nd->event_priority = priority; + + return 0; +} + +_public_ int sd_ndisc_detach_event(sd_ndisc *nd) { + + assert_return(nd, -EINVAL); + assert_return(nd->fd < 0, -EBUSY); + + nd->event = sd_event_unref(nd->event); + return 0; +} + +_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) { + assert_return(nd, NULL); + + return nd->event; +} + +_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) { + + if (!nd) + return NULL; + + assert(nd->n_ref > 0); + nd->n_ref++; + + return nd; +} + +static int ndisc_reset(sd_ndisc *nd) { + assert(nd); + + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + nd->recv_event_source = sd_event_source_unref(nd->recv_event_source); + nd->fd = safe_close(nd->fd); + + return 0; +} + +_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) { + + if (!nd) + return NULL; + + assert(nd->n_ref > 0); + nd->n_ref--; + + if (nd->n_ref > 0) + return NULL; + + ndisc_reset(nd); + sd_ndisc_detach_event(nd); + free(nd); + + return NULL; +} + +_public_ int sd_ndisc_new(sd_ndisc **ret) { + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + + assert_return(ret, -EINVAL); + + nd = new0(sd_ndisc, 1); + if (!nd) + return -ENOMEM; + + nd->n_ref = 1; + nd->fd = -1; + + *ret = nd; + nd = NULL; + + return 0; +} + +_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) { + assert_return(nd, -EINVAL); + assert_return(mtu, -EINVAL); + + if (nd->mtu == 0) + return -ENODATA; + + *mtu = nd->mtu; + return 0; +} + +_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) { + assert_return(nd, -EINVAL); + assert_return(ret, -EINVAL); + + if (nd->hop_limit == 0) + return -ENODATA; + + *ret = nd->hop_limit; + return 0; +} + +static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { + int r; + + assert(nd); + assert(rt); + + r = ndisc_router_parse(rt); + if (r == -EBADMSG) /* Bad packet */ + return 0; + if (r < 0) + return 0; + + /* Update global variables we keep */ + if (rt->mtu > 0) + nd->mtu = rt->mtu; + if (rt->hop_limit > 0) + nd->hop_limit = rt->hop_limit; + + log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec", + rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none", + rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium", + rt->lifetime); + + ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); + return 0; +} + +static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + sd_ndisc *nd = userdata; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */ + CMSG_SPACE(sizeof(struct timeval))]; + } control = {}; + struct iovec iov = {}; + union sockaddr_union sa = {}; + struct msghdr msg = { + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + ssize_t len, buflen; + + assert(s); + assert(nd); + assert(nd->event); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m"); + + rt = ndisc_router_new(buflen); + if (!rt) + return -ENOMEM; + + iov.iov_base = NDISC_ROUTER_RAW(rt); + iov.iov_len = rt->raw_size; + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m"); + } + + if ((size_t) len != rt->raw_size) { + log_ndisc("Packet size mismatch."); + return -EINVAL; + } + + if (msg.msg_namelen == sizeof(struct sockaddr_in6) && + sa.in6.sin6_family == AF_INET6) { + + if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) { + _cleanup_free_ char *addr = NULL; + + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr); + log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr)); + return 0; + } + + rt->address = sa.in6.sin6_addr; + + } else if (msg.msg_namelen > 0) { + log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen); + return -EINVAL; + } + + /* namelen == 0 only happens when running the test-suite over a socketpair */ + + assert(!(msg.msg_flags & MSG_CTRUNC)); + assert(!(msg.msg_flags & MSG_TRUNC)); + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_IPV6 && + cmsg->cmsg_type == IPV6_HOPLIMIT && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + int hops = *(int*) CMSG_DATA(cmsg); + + if (hops != 255) { + log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops); + return 0; + } + } + + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg))); + } + + if (!triple_timestamp_is_set(&rt->timestamp)) + triple_timestamp_get(&rt->timestamp); + + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + + return ndisc_handle_datagram(nd, rt); +} + +static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ndisc *nd = userdata; + usec_t time_now, next_timeout; + int r; + + assert(s); + assert(nd); + assert(nd->event); + + if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) { + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); + return 0; + } + + r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); + if (r < 0) { + log_ndisc_errno(r, "Error sending Router Solicitation: %m"); + goto fail; + } + + log_ndisc("Sent Router Solicitation"); + nd->nd_sent++; + + assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); + next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL; + + r = sd_event_source_set_time(nd->timeout_event_source, next_timeout); + if (r < 0) { + log_ndisc_errno(r, "Error updating timer: %m"); + goto fail; + } + + r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT); + if (r < 0) { + log_ndisc_errno(r, "Error reenabling timer: %m"); + goto fail; + } + + return 0; + +fail: + sd_ndisc_stop(nd); + return 0; +} + +_public_ int sd_ndisc_stop(sd_ndisc *nd) { + assert_return(nd, -EINVAL); + + if (nd->fd < 0) + return 0; + + log_ndisc("Stopping IPv6 Router Solicitation client"); + + ndisc_reset(nd); + return 1; +} + +_public_ int sd_ndisc_start(sd_ndisc *nd) { + int r; + + assert_return(nd, -EINVAL); + assert_return(nd->event, -EINVAL); + assert_return(nd->ifindex > 0, -EINVAL); + + if (nd->fd >= 0) + return 0; + + assert(!nd->recv_event_source); + assert(!nd->timeout_event_source); + + nd->fd = icmp6_bind_router_solicitation(nd->ifindex); + if (nd->fd < 0) + return nd->fd; + + r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message"); + + r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout"); + + log_ndisc("Started IPv6 Router Solicitation client"); + return 1; + +fail: + ndisc_reset(nd); + return r; +} diff --git a/src/libsystemd-network/sd-pppoe.c b/src/libsystemd-network/sd-pppoe.c deleted file mode 100644 index 1de8a5e8bf..0000000000 --- a/src/libsystemd-network/sd-pppoe.c +++ /dev/null @@ -1,802 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - 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/>. -***/ - -/* See RFC 2516 */ - -#include <sys/ioctl.h> -#include <linux/ppp_defs.h> -#include <linux/ppp-ioctl.h> -#include <net/if.h> -#include <netinet/in.h> -#include <linux/if_pppox.h> - -#include "sd-pppoe.h" - -#include "event-util.h" - -#include "util.h" -#include "random-util.h" -#include "socket-util.h" -#include "async.h" -#include "refcnt.h" -#include "utf8.h" - -#define PPPOE_MAX_PACKET_SIZE 1484 -#define PPPOE_MAX_PADR_RESEND 16 - -/* TODO: move this to socket-util.h without getting into - * a mess with the includes */ -union sockaddr_union_pppox { - struct sockaddr sa; - struct sockaddr_pppox pppox; -}; - -typedef enum PPPoEState { - PPPOE_STATE_INITIALIZING, - PPPOE_STATE_REQUESTING, - PPPOE_STATE_RUNNING, - PPPOE_STATE_STOPPED, - _PPPOE_STATE_MAX, - _PPPOE_STATE_INVALID = -1, -} PPPoEState; - -typedef struct PPPoETags { - char *service_name; - char *ac_name; - uint8_t *host_uniq; - size_t host_uniq_len; - uint8_t *cookie; - size_t cookie_len; -} PPPoETags; - -struct sd_pppoe { - RefCount n_ref; - - PPPoEState state; - uint64_t host_uniq; - - int ifindex; - char *ifname; - - sd_event *event; - int event_priority; - int fd; - sd_event_source *io; - sd_event_source *timeout; - int padr_resend_count; - - char *service_name; - struct ether_addr peer_mac; - be16_t session_id; - - int pppoe_fd; - int channel; - - sd_pppoe_cb_t cb; - void *userdata; - - PPPoETags tags; -}; - -#define PPPOE_PACKET_LENGTH(header) \ - be16toh((header)->length) - -#define PPPOE_PACKET_TAIL(packet) \ - (struct pppoe_tag*)((uint8_t*)(packet) + sizeof(struct pppoe_hdr) + PPPOE_PACKET_LENGTH(packet)) - -#define PPPOE_TAG_LENGTH(tag) \ - be16toh((tag)->tag_len) - -#define PPPOE_TAG_TYPE(tag) \ - (tag)->tag_type - -#define PPPOE_TAG_NEXT(tag) \ - (struct pppoe_tag *)((uint8_t *)(tag) + sizeof(struct pppoe_tag) + PPPOE_TAG_LENGTH(tag)) - -#define PPPOE_TAGS_FOREACH(tag, header) \ - for (tag = (header)->tag; \ - ((uint8_t *)(tag) + sizeof(struct pppoe_tag) < (uint8_t*)PPPOE_PACKET_TAIL(header)) && \ - (PPPOE_TAG_NEXT(tag) <= PPPOE_PACKET_TAIL(header)) && \ - (tag >= (header)->tag) && \ - (PPPOE_TAG_TYPE(tag) != PTT_EOL); \ - tag = PPPOE_TAG_NEXT(tag)) - -static void pppoe_tags_clear(PPPoETags *tags) { - free(tags->service_name); - free(tags->ac_name); - free(tags->host_uniq); - free(tags->cookie); - - zero(*tags); -} - -int sd_pppoe_set_ifindex(sd_pppoe *ppp, int ifindex) { - assert_return(ppp, -EINVAL); - assert_return(ifindex > 0, -EINVAL); - - ppp->ifindex = ifindex; - - return 0; -} - -int sd_pppoe_set_ifname(sd_pppoe *ppp, const char *ifname) { - char *name; - - assert_return(ppp, -EINVAL); - assert_return(ifname, -EINVAL); - - if (strlen(ifname) > IFNAMSIZ) - return -EINVAL; - - name = strdup(ifname); - if (!name) - return -ENOMEM; - - free(ppp->ifname); - ppp->ifname = name; - - return 0; -} - -int sd_pppoe_set_service_name(sd_pppoe *ppp, const char *service_name) { - _cleanup_free_ char *name = NULL; - - assert_return(ppp, -EINVAL); - - if (service_name) { - name = strdup(service_name); - if (!name) - return -ENOMEM; - } - - free(ppp->service_name); - ppp->service_name = name; - name = NULL; - - return 0; -} - -int sd_pppoe_attach_event(sd_pppoe *ppp, sd_event *event, int priority) { - int r; - - assert_return(ppp, -EINVAL); - assert_return(!ppp->event, -EBUSY); - - if (event) - ppp->event = sd_event_ref(event); - else { - r = sd_event_default(&ppp->event); - if (r < 0) - return r; - } - - ppp->event_priority = priority; - - return 0; -} - -int sd_pppoe_detach_event(sd_pppoe *ppp) { - assert_return(ppp, -EINVAL); - - ppp->event = sd_event_unref(ppp->event); - - return 0; -} - -sd_pppoe *sd_pppoe_ref(sd_pppoe *ppp) { - if (ppp) - assert_se(REFCNT_INC(ppp->n_ref) >= 2); - - return ppp; -} - -sd_pppoe *sd_pppoe_unref(sd_pppoe *ppp) { - if (ppp && REFCNT_DEC(ppp->n_ref) <= 0) { - pppoe_tags_clear(&ppp->tags); - free(ppp->ifname); - free(ppp->service_name); - sd_pppoe_stop(ppp); - sd_pppoe_detach_event(ppp); - - free(ppp); - } - - return NULL; -} - -int sd_pppoe_new (sd_pppoe **ret) { - sd_pppoe *ppp; - - assert_return(ret, -EINVAL); - - ppp = new0(sd_pppoe, 1); - if (!ppp) - return -ENOMEM; - - ppp->n_ref = REFCNT_INIT; - ppp->state = _PPPOE_STATE_INVALID; - ppp->ifindex = -1; - ppp->fd = -1; - ppp->pppoe_fd = -1; - ppp->padr_resend_count = PPPOE_MAX_PADR_RESEND; - - *ret = ppp; - - return 0; -} - -int sd_pppoe_get_channel(sd_pppoe *ppp, int *channel) { - assert_return(ppp, -EINVAL); - assert_return(channel, -EINVAL); - assert_return(ppp->pppoe_fd != -1, -EUNATCH); - assert_return(ppp->state == PPPOE_STATE_RUNNING, -EUNATCH); - - *channel = ppp->channel; - - return 0; -} - -int sd_pppoe_set_callback(sd_pppoe *ppp, sd_pppoe_cb_t cb, void *userdata) { - assert_return(ppp, -EINVAL); - - ppp->cb = cb; - ppp->userdata = userdata; - - return 0; -} - -static void pppoe_tag_append(struct pppoe_hdr *packet, size_t packet_size, be16_t tag_type, const void *tag_data, uint16_t tag_len) { - struct pppoe_tag *tag; - - assert(packet); - assert(sizeof(struct pppoe_hdr) + PPPOE_PACKET_LENGTH(packet) + sizeof(struct pppoe_tag) + tag_len <= packet_size); - assert(!(!tag_data ^ !tag_len)); - - tag = PPPOE_PACKET_TAIL(packet); - - tag->tag_len = htobe16(tag_len); - tag->tag_type = tag_type; - if (tag_data) - memcpy(tag->tag_data, tag_data, tag_len); - - packet->length = htobe16(PPPOE_PACKET_LENGTH(packet) + sizeof(struct pppoe_tag) + tag_len); -} - -static int pppoe_send(sd_pppoe *ppp, uint8_t code) { - union sockaddr_union link = { - .ll = { - .sll_family = AF_PACKET, - .sll_protocol = htons(ETH_P_PPP_DISC), - .sll_halen = ETH_ALEN, - }, - }; - _cleanup_free_ struct pppoe_hdr *packet = NULL; - int r; - - assert(ppp); - assert(ppp->fd != -1); - assert(IN_SET(code, PADI_CODE, PADR_CODE, PADT_CODE)); - - link.ll.sll_ifindex = ppp->ifindex; - if (code == PADI_CODE) - memset(&link.ll.sll_addr, 0xff, ETH_ALEN); - else - memcpy(&link.ll.sll_addr, &ppp->peer_mac, ETH_ALEN); - - packet = malloc0(PPPOE_MAX_PACKET_SIZE); - if (!packet) - return -ENOMEM; - - packet->ver = 0x1; - packet->type = 0x1; - packet->code = code; - if (code == PADT_CODE) - packet->sid = ppp->session_id; - - /* Service-Name */ - pppoe_tag_append(packet, PPPOE_MAX_PACKET_SIZE, PTT_SRV_NAME, - ppp->service_name, ppp->service_name ? strlen(ppp->service_name) : 0); - - /* AC-Cookie */ - if (code == PADR_CODE && ppp->tags.cookie) - pppoe_tag_append(packet, PPPOE_MAX_PACKET_SIZE, PTT_AC_COOKIE, - ppp->tags.cookie, ppp->tags.cookie_len); - - /* Host-Uniq */ - if (code != PADT_CODE) { - ppp->host_uniq = random_u64(); - - pppoe_tag_append(packet, PPPOE_MAX_PACKET_SIZE, PTT_HOST_UNIQ, - &ppp->host_uniq, sizeof(ppp->host_uniq)); - } - - r = sendto(ppp->fd, packet, sizeof(struct pppoe_hdr) + PPPOE_PACKET_LENGTH(packet), - 0, &link.sa, sizeof(link.ll)); - if (r < 0) - return -errno; - - return 0; -} - -static int pppoe_timeout(sd_event_source *s, uint64_t usec, void *userdata); - -static int pppoe_arm_timeout(sd_pppoe *ppp) { - _cleanup_event_source_unref_ sd_event_source *timeout = NULL; - usec_t next_timeout = 0; - int r; - - assert(ppp); - - r = sd_event_now(ppp->event, clock_boottime_or_monotonic(), &next_timeout); - if (r == -ENODATA) - next_timeout = now(clock_boottime_or_monotonic()); - else if (r < 0) - return r; - - next_timeout += 500 * USEC_PER_MSEC; - - r = sd_event_add_time(ppp->event, &timeout, clock_boottime_or_monotonic(), next_timeout, - 10 * USEC_PER_MSEC, pppoe_timeout, ppp); - if (r < 0) - return r; - - r = sd_event_source_set_priority(timeout, ppp->event_priority); - if (r < 0) - return r; - - sd_event_source_unref(ppp->timeout); - ppp->timeout = timeout; - timeout = NULL; - - return 0; -} - -static int pppoe_send_initiation(sd_pppoe *ppp) { - int r; - - r = pppoe_send(ppp, PADI_CODE); - if (r < 0) - return r; - - log_debug("PPPoE: sent DISCOVER (Service-Name: %s)", - ppp->service_name ? : ""); - - pppoe_arm_timeout(ppp); - - return r; -} - -static int pppoe_send_request(sd_pppoe *ppp) { - int r; - - r = pppoe_send(ppp, PADR_CODE); - if (r < 0) - return r; - - log_debug("PPPoE: sent REQUEST"); - - ppp->padr_resend_count --; - - pppoe_arm_timeout(ppp); - - return 0; -} - -static int pppoe_send_terminate(sd_pppoe *ppp) { - int r; - - r = pppoe_send(ppp, PADT_CODE); - if (r < 0) - return r; - - log_debug("PPPoE: sent TERMINATE"); - - return 0; -} - -static int pppoe_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - sd_pppoe *ppp = userdata; - int r; - - assert(ppp); - - switch (ppp->state) { - case PPPOE_STATE_INITIALIZING: - r = pppoe_send_initiation(ppp); - if (r < 0) - log_warning_errno(r, "PPPoE: sending PADI failed: %m"); - - break; - case PPPOE_STATE_REQUESTING: - if (ppp->padr_resend_count <= 0) { - log_debug("PPPoE: PADR timed out, restarting PADI"); - - r = pppoe_send_initiation(ppp); - if (r < 0) - log_warning_errno(r, "PPPoE: sending PADI failed: %m"); - - ppp->padr_resend_count = PPPOE_MAX_PADR_RESEND; - ppp->state = PPPOE_STATE_INITIALIZING; - } else { - r = pppoe_send_request(ppp); - if (r < 0) - log_warning_errno(r, "PPPoE: sending PADR failed: %m"); - } - - break; - default: - assert_not_reached("timeout in invalid state"); - } - - return 0; -} - -static int pppoe_tag_parse_binary(struct pppoe_tag *tag, uint8_t **ret, size_t *length) { - uint8_t *data; - - assert(ret); - assert(length); - - data = memdup(tag->tag_data, PPPOE_TAG_LENGTH(tag)); - if (!data) - return -ENOMEM; - - free(*ret); - *ret = data; - *length = PPPOE_TAG_LENGTH(tag); - - return 0; -} - -static int pppoe_tag_parse_string(struct pppoe_tag *tag, char **ret) { - char *string; - - assert(ret); - - string = strndup(tag->tag_data, PPPOE_TAG_LENGTH(tag)); - if (!string) - return -ENOMEM; - - free(*ret); - *ret = string; - - return 0; -} - -static int pppoe_payload_parse(PPPoETags *tags, struct pppoe_hdr *header) { - struct pppoe_tag *tag; - int r; - - assert(tags); - - pppoe_tags_clear(tags); - - PPPOE_TAGS_FOREACH(tag, header) { - switch (PPPOE_TAG_TYPE(tag)) { - case PTT_SRV_NAME: - r = pppoe_tag_parse_string(tag, &tags->service_name); - if (r < 0) - return r; - - break; - case PTT_AC_NAME: - r = pppoe_tag_parse_string(tag, &tags->ac_name); - if (r < 0) - return r; - - break; - case PTT_HOST_UNIQ: - r = pppoe_tag_parse_binary(tag, &tags->host_uniq, &tags->host_uniq_len); - if (r < 0) - return r; - - break; - case PTT_AC_COOKIE: - r = pppoe_tag_parse_binary(tag, &tags->cookie, &tags->cookie_len); - if (r < 0) - return r; - - break; - case PTT_SRV_ERR: - case PTT_SYS_ERR: - case PTT_GEN_ERR: - { - _cleanup_free_ char *error = NULL; - - /* TODO: do something more sensible with the error messages */ - r = pppoe_tag_parse_string(tag, &error); - if (r < 0) - return r; - - if (strlen(error) > 0 && utf8_is_valid(error)) - log_debug("PPPoE: error - '%s'", error); - else - log_debug("PPPoE: error"); - - break; - } - default: - log_debug("PPPoE: ignoring unknown PPPoE tag type: 0x%.2x", PPPOE_TAG_TYPE(tag)); - } - } - - return 0; -} - -static int pppoe_open_pppoe_socket(sd_pppoe *ppp) { - int s; - - assert(ppp); - assert(ppp->pppoe_fd == -1); - - s = socket(AF_PPPOX, SOCK_STREAM, 0); - if (s < 0) - return -errno; - - ppp->pppoe_fd = s; - - return 0; -} - -static int pppoe_connect_pppoe_socket(sd_pppoe *ppp) { - union sockaddr_union_pppox link = { - .pppox = { - .sa_family = AF_PPPOX, - .sa_protocol = PX_PROTO_OE, - }, - }; - int r, channel; - - assert(ppp); - assert(ppp->pppoe_fd != -1); - assert(ppp->session_id); - assert(ppp->ifname); - - link.pppox.sa_addr.pppoe.sid = ppp->session_id; - memcpy(link.pppox.sa_addr.pppoe.dev, ppp->ifname, strlen(ppp->ifname)); - memcpy(link.pppox.sa_addr.pppoe.remote, &ppp->peer_mac, ETH_ALEN); - - r = connect(ppp->pppoe_fd, &link.sa, sizeof(link.pppox)); - if (r < 0) - return r; - - r = ioctl(ppp->pppoe_fd, PPPIOCGCHAN, &channel); - if (r < 0) - return -errno; - - ppp->channel = channel; - - return 0; -} - -static int pppoe_handle_message(sd_pppoe *ppp, struct pppoe_hdr *packet, struct ether_addr *mac) { - int r; - - assert(packet); - - if (packet->ver != 0x1 || packet->type != 0x1) - return 0; - - r = pppoe_payload_parse(&ppp->tags, packet); - if (r < 0) - return 0; - - switch (ppp->state) { - case PPPOE_STATE_INITIALIZING: - if (packet->code != PADO_CODE) - return 0; - - if (ppp->tags.host_uniq_len != sizeof(ppp->host_uniq) || - memcmp(ppp->tags.host_uniq, &ppp->host_uniq, sizeof(ppp->host_uniq)) != 0) - return 0; - - log_debug("PPPoE: got OFFER (Peer: " - "%02hhx:%02hhx:%02hhx:%02hhx:%02hhx:%02hhx; " - "Service-Name: '%s'; AC-Name: '%s')", - mac->ether_addr_octet[0], - mac->ether_addr_octet[1], - mac->ether_addr_octet[2], - mac->ether_addr_octet[3], - mac->ether_addr_octet[4], - mac->ether_addr_octet[5], - ppp->tags.service_name ? : "", - ppp->tags.ac_name ? : ""); - - memcpy(&ppp->peer_mac, mac, ETH_ALEN); - - r = pppoe_open_pppoe_socket(ppp); - if (r < 0) { - log_warning("PPPoE: could not open socket"); - return r; - } - - r = pppoe_send_request(ppp); - if (r < 0) - return 0; - - ppp->state = PPPOE_STATE_REQUESTING; - - break; - case PPPOE_STATE_REQUESTING: - if (packet->code != PADS_CODE) - return 0; - - if (ppp->tags.host_uniq_len != sizeof(ppp->host_uniq) || - memcmp(ppp->tags.host_uniq, &ppp->host_uniq, - sizeof(ppp->host_uniq)) != 0) - return 0; - - if (memcmp(&ppp->peer_mac, mac, ETH_ALEN) != 0) - return 0; - - ppp->session_id = packet->sid; - - log_debug("PPPoE: got CONFIRMATION (Session ID: %"PRIu16")", - be16toh(ppp->session_id)); - - r = pppoe_connect_pppoe_socket(ppp); - if (r < 0) { - log_warning("PPPoE: could not connect socket"); - return r; - } - - ppp->state = PPPOE_STATE_RUNNING; - - ppp->timeout = sd_event_source_unref(ppp->timeout); - assert(ppp->cb); - ppp->cb(ppp, PPPOE_EVENT_RUNNING, ppp->userdata); - - break; - case PPPOE_STATE_RUNNING: - if (packet->code != PADT_CODE) - return 0; - - if (memcmp(&ppp->peer_mac, mac, ETH_ALEN) != 0) - return 0; - - if (ppp->session_id != packet->sid) - return 0; - - log_debug("PPPoE: got TERMINATE"); - - ppp->state = PPPOE_STATE_STOPPED; - - assert(ppp->cb); - ppp->cb(ppp, PPPOE_EVENT_STOPPED, ppp->userdata); - - break; - case PPPOE_STATE_STOPPED: - break; - default: - assert_not_reached("PPPoE: invalid state when receiving message"); - } - - return 0; -} - -static int pppoe_receive_message(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - sd_pppoe *ppp = userdata; - _cleanup_free_ struct pppoe_hdr *packet = NULL; - union sockaddr_union link = {}; - socklen_t addrlen = sizeof(link); - int buflen = 0, len, r; - - assert(ppp); - assert(fd != -1); - - r = ioctl(fd, FIONREAD, &buflen); - if (r < 0) - return r; - - if (buflen < 0) - /* this can't be right */ - return -EIO; - - packet = malloc0(buflen); - if (!packet) - return -ENOMEM; - - len = recvfrom(fd, packet, buflen, 0, &link.sa, &addrlen); - if (len < 0) { - log_warning_errno(r, "PPPoE: could not receive message from raw socket: %m"); - return 0; - } else if ((size_t)len < sizeof(struct pppoe_hdr)) - return 0; - else if ((size_t)len != sizeof(struct pppoe_hdr) + PPPOE_PACKET_LENGTH(packet)) - return 0; - - if (link.ll.sll_halen != ETH_ALEN) - /* not ethernet? */ - return 0; - - r = pppoe_handle_message(ppp, packet, (struct ether_addr*)&link.ll.sll_addr); - if (r < 0) - return r; - - return 1; -} - -int sd_pppoe_start(sd_pppoe *ppp) { - union sockaddr_union link = { - .ll = { - .sll_family = AF_PACKET, - .sll_protocol = htons(ETH_P_PPP_DISC), - }, - }; - _cleanup_close_ int s = -1; - _cleanup_event_source_unref_ sd_event_source *io = NULL; - int r; - - assert_return(ppp, -EINVAL); - assert_return(ppp->fd == -1, -EBUSY); - assert_return(!ppp->io, -EBUSY); - assert_return(ppp->ifindex > 0, -EUNATCH); - assert_return(ppp->ifname, -EUNATCH); - assert_return(ppp->event, -EUNATCH); - assert_return(ppp->cb, -EUNATCH); - - s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - link.ll.sll_ifindex = ppp->ifindex; - - r = bind(s, &link.sa, sizeof(link.ll)); - if (r < 0) - return r; - - r = sd_event_add_io(ppp->event, &io, - s, EPOLLIN, pppoe_receive_message, - ppp); - if (r < 0) - return r; - - r = sd_event_source_set_priority(io, ppp->event_priority); - if (r < 0) - return r; - - ppp->fd = s; - s = -1; - ppp->io = io; - io = NULL; - - r = pppoe_send_initiation(ppp); - if (r < 0) - return r; - - ppp->state = PPPOE_STATE_INITIALIZING; - - return 0; -} - -int sd_pppoe_stop(sd_pppoe *ppp) { - assert_return(ppp, -EINVAL); - - if (ppp->state == PPPOE_STATE_RUNNING) - pppoe_send_terminate(ppp); - - ppp->io = sd_event_source_unref(ppp->io); - ppp->timeout = sd_event_source_unref(ppp->timeout); - ppp->fd = asynchronous_close(ppp->fd); - ppp->pppoe_fd = asynchronous_close(ppp->pppoe_fd); - - return 0; -} diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c new file mode 100644 index 0000000000..27fcc332a3 --- /dev/null +++ b/src/libsystemd-network/test-acd.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen <teg@jklm.no> + + 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 <errno.h> +#include <stdlib.h> +#include <unistd.h> + +#include <linux/veth.h> +#include <net/if.h> + +#include "sd-event.h" +#include "sd-ipv4acd.h" +#include "sd-netlink.h" + +#include "in-addr-util.h" +#include "netlink-util.h" +#include "util.h" + +static void acd_handler(sd_ipv4acd *acd, int event, void *userdata) { + assert_se(acd); + + switch (event) { + case SD_IPV4ACD_EVENT_BIND: + log_info("bound"); + break; + case SD_IPV4ACD_EVENT_CONFLICT: + log_info("conflict"); + break; + case SD_IPV4ACD_EVENT_STOP: + log_error("the client was stopped"); + break; + default: + assert_not_reached("invalid ACD event"); + } +} + +static int client_run(int ifindex, const struct in_addr *pa, const struct ether_addr *ha, sd_event *e) { + sd_ipv4acd *acd; + + assert_se(sd_ipv4acd_new(&acd) >= 0); + assert_se(sd_ipv4acd_attach_event(acd, e, 0) >= 0); + + assert_se(sd_ipv4acd_set_ifindex(acd, ifindex) >= 0); + assert_se(sd_ipv4acd_set_mac(acd, ha) >= 0); + assert_se(sd_ipv4acd_set_address(acd, pa) >= 0); + assert_se(sd_ipv4acd_set_callback(acd, acd_handler, NULL) >= 0); + + log_info("starting IPv4ACD client"); + + assert_se(sd_ipv4acd_start(acd) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(!sd_ipv4acd_unref(acd)); + + return EXIT_SUCCESS; +} + +static int test_acd(const char *ifname, const char *address) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + union in_addr_union pa; + struct ether_addr ha; + int ifindex; + + assert_se(in_addr_from_string(AF_INET, address, &pa) >= 0); + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); + assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); + + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); + + client_run(ifindex, &pa.in, &ha, e); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc == 3) + return test_acd(argv[1], argv[2]); + else { + log_error("This program takes two arguments.\n" + "\t %s <ifname> <IPv4 address>", program_invocation_short_name); + return EXIT_FAILURE; + } +} diff --git a/src/libsystemd-network/test-dhcp-client.c b/src/libsystemd-network/test-dhcp-client.c index d341210887..2a101cb1fe 100644 --- a/src/libsystemd-network/test-dhcp-client.c +++ b/src/libsystemd-network/test-dhcp-client.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -24,14 +22,15 @@ #include <sys/socket.h> #include <unistd.h> -#include "util.h" +#include "sd-dhcp-client.h" #include "sd-event.h" -#include "event-util.h" +#include "alloc-util.h" #include "dhcp-identifier.h" -#include "dhcp-protocol.h" #include "dhcp-internal.h" -#include "sd-dhcp-client.h" +#include "dhcp-protocol.h" +#include "fd-util.h" +#include "util.h" static uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; @@ -43,16 +42,13 @@ static test_callback_recv_t callback_recv; static be32_t xid; static sd_event_source *test_hangcheck; -static int test_dhcp_hangcheck(sd_event_source *s, uint64_t usec, - void *userdata) -{ +static int test_dhcp_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) { assert_not_reached("Test case should have completed in 2 seconds"); return 0; } -static void test_request_basic(sd_event *e) -{ +static void test_request_basic(sd_event *e) { int r; sd_dhcp_client *client; @@ -70,38 +66,35 @@ static void test_request_basic(sd_event *e) assert_se(sd_dhcp_client_set_request_option(NULL, 0) == -EINVAL); assert_se(sd_dhcp_client_set_request_address(NULL, NULL) == -EINVAL); - assert_se(sd_dhcp_client_set_index(NULL, 0) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(NULL, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_index(client, 15) == 0); - assert_se(sd_dhcp_client_set_index(client, -42) == -EINVAL); - assert_se(sd_dhcp_client_set_index(client, -1) == -EINVAL); - assert_se(sd_dhcp_client_set_index(client, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_index(client, 1) == 0); + assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); + assert_se(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_SUBNET_MASK) == -EEXIST); + SD_DHCP_OPTION_SUBNET_MASK) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_ROUTER) == -EEXIST); + SD_DHCP_OPTION_ROUTER) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_HOST_NAME) == -EEXIST); + SD_DHCP_OPTION_HOST_NAME) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_DOMAIN_NAME) == -EEXIST); + SD_DHCP_OPTION_DOMAIN_NAME) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_DOMAIN_NAME_SERVER) - == -EEXIST); - assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_NTP_SERVER) == -EEXIST); + SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == -EEXIST); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_PAD) == -EINVAL); + SD_DHCP_OPTION_PAD) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_END) == -EINVAL); + SD_DHCP_OPTION_END) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); + SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_OVERLOAD) == -EINVAL); + SD_DHCP_OPTION_OVERLOAD) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, - DHCP_OPTION_PARAMETER_REQUEST_LIST) + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) == -EINVAL); assert_se(sd_dhcp_client_set_request_option(client, 33) == 0); @@ -112,8 +105,7 @@ static void test_request_basic(sd_event *e) sd_dhcp_client_unref(client); } -static void test_checksum(void) -{ +static void test_checksum(void) { uint8_t buf[20] = { 0x45, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x40, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, @@ -126,11 +118,9 @@ static void test_checksum(void) assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae)); } -static int check_options(uint8_t code, uint8_t len, const uint8_t *option, - void *user_data) -{ +static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) { switch(code) { - case DHCP_OPTION_CLIENT_IDENTIFIER: + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: { uint32_t iaid; struct duid duid; @@ -141,10 +131,10 @@ static int check_options(uint8_t code, uint8_t len, const uint8_t *option, assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len); assert_se(len == 19); - assert_se(option[0] == 0xff); + assert_se(((uint8_t*) option)[0] == 0xff); - assert_se(memcmp(&option[1], &iaid, sizeof(iaid)) == 0); - assert_se(memcmp(&option[5], &duid, duid_len) == 0); + assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0); + assert_se(memcmp((uint8_t*) option + 5, &duid, duid_len) == 0); break; } @@ -155,9 +145,7 @@ static int check_options(uint8_t code, uint8_t len, const uint8_t *option, return 0; } -int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, - const void *packet, size_t len) -{ +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, const void *packet, size_t len) { size_t size; _cleanup_free_ DHCPPacket *discover; uint16_t ip_check, udp_check; @@ -202,18 +190,20 @@ int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, return 575; } -int dhcp_network_bind_raw_socket(int index, union sockaddr_union *link, - uint32_t id, const uint8_t *addr, - size_t addr_len, uint16_t arp_type) -{ +int dhcp_network_bind_raw_socket( + int index, + union sockaddr_union *link, + uint32_t id, + const uint8_t *addr, size_t addr_len, + uint16_t arp_type) { + if (socketpair(AF_UNIX, SOCK_STREAM, 0, test_fd) < 0) return -errno; return test_fd[0]; } -int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) -{ +int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) { int fd; fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0); @@ -223,17 +213,14 @@ int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) return fd; } -int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, - const void *packet, size_t len) -{ +int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, const void *packet, size_t len) { return 0; } -static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) -{ +static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) { int res; - res = dhcp_option_parse(dhcp, size, check_options, NULL); + res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL); assert_se(res == DHCP_DISCOVER); if (verbose) @@ -242,8 +229,7 @@ static int test_discover_message_verify(size_t size, struct DHCPMessage *dhcp) return 0; } -static void test_discover_message(sd_event *e) -{ +static void test_discover_message(sd_event *e) { sd_dhcp_client *client; int res, r; @@ -257,7 +243,7 @@ static void test_discover_message(sd_event *e) r = sd_dhcp_client_attach_event(client, e, 0); assert_se(r >= 0); - assert_se(sd_dhcp_client_set_index(client, 42) >= 0); + assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0); @@ -373,7 +359,7 @@ static void test_addr_acq_acquired(sd_dhcp_client *client, int event, struct in_addr addr; assert_se(client); - assert_se(event == DHCP_EVENT_IP_ACQUIRE); + assert_se(event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0); assert_se(lease); @@ -393,7 +379,6 @@ static void test_addr_acq_acquired(sd_dhcp_client *client, int event, if (verbose) printf(" DHCP address acquired\n"); - sd_dhcp_lease_unref(lease); sd_event_exit(e, 0); } @@ -402,11 +387,11 @@ static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { uint8_t *msg_bytes = (uint8_t *)request; int res; - res = dhcp_option_parse(request, size, check_options, NULL); + res = dhcp_option_parse(request, size, check_options, NULL, NULL); assert_se(res == DHCP_REQUEST); assert_se(xid == request->xid); - assert_se(msg_bytes[size - 1] == DHCP_OPTION_END); + assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); if (verbose) printf(" recv DHCP Request 0x%08x\n", be32toh(xid)); @@ -432,10 +417,10 @@ static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { uint8_t *msg_bytes = (uint8_t *)discover; int res; - res = dhcp_option_parse(discover, size, check_options, NULL); + res = dhcp_option_parse(discover, size, check_options, NULL, NULL); assert_se(res == DHCP_DISCOVER); - assert_se(msg_bytes[size - 1] == DHCP_OPTION_END); + assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); xid = discover->xid; @@ -473,7 +458,7 @@ static void test_addr_acq(sd_event *e) { r = sd_dhcp_client_attach_event(client, e, 0); assert_se(r >= 0); - assert_se(sd_dhcp_client_set_index(client, 42) >= 0); + assert_se(sd_dhcp_client_set_ifindex(client, 42) >= 0); assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0); @@ -503,7 +488,7 @@ static void test_addr_acq(sd_event *e) { } int main(int argc, char *argv[]) { - _cleanup_event_unref_ sd_event *e; + _cleanup_(sd_event_unrefp) sd_event *e; log_set_max_level(LOG_DEBUG); log_parse_environment(); diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c index a63a4ea738..d84859c053 100644 --- a/src/libsystemd-network/test-dhcp-option.c +++ b/src/libsystemd-network/test-dhcp-option.c @@ -1,15 +1,13 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -#include <stdio.h> -#include <stdbool.h> #include <errno.h> +#include <stdbool.h> +#include <stdio.h> #include <string.h> -#include "util.h" -#include "macro.h" - -#include "dhcp-protocol.h" +#include "alloc-util.h" #include "dhcp-internal.h" +#include "dhcp-protocol.h" +#include "macro.h" +#include "util.h" struct option_desc { uint8_t sname[64]; @@ -29,7 +27,7 @@ static bool verbose = false; static struct option_desc option_tests[] = { { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69 }, 7, false, }, { {}, 0, {}, 0, { 42, 5, 65, 66, 67, 68, 69, 0, 0, - DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, }, + SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 12, true, }, { {}, 0, {}, 0, { 8, 255, 70, 71, 72 }, 5, false, }, { {}, 0, {}, 0, { 0x35, 0x01, 0x05, 0x36, 0x04, 0x01, 0x00, 0xa8, 0xc0, 0x33, 0x04, 0x00, 0x01, 0x51, 0x80, 0x01, @@ -37,17 +35,17 @@ static struct option_desc option_tests[] = { 0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, 40, true, }, - { {}, 0, {}, 0, { DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER, + { {}, 0, {}, 0, { SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_OFFER, 42, 3, 0, 0, 0 }, 8, true, }, { {}, 0, {}, 0, { 42, 2, 1, 2, 44 }, 5, false, }, { {}, 0, - { 222, 3, 1, 2, 3, DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8, - { DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, }, + { 222, 3, 1, 2, 3, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_NAK }, 8, + { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE }, 3, true, }, - { { 1, 4, 1, 2, 3, 4, DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9, + { { 1, 4, 1, 2, 3, 4, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9, { 222, 3, 1, 2, 3 }, 5, - { DHCP_OPTION_OVERLOAD, 1, + { SD_DHCP_OPTION_OVERLOAD, 1, DHCP_OVERLOAD_FILE|DHCP_OVERLOAD_SNAME }, 3, true, }, }; @@ -75,9 +73,8 @@ static const char *dhcp_type(int type) { static void test_invalid_buffer_length(void) { DHCPMessage message; - assert_se(dhcp_option_parse(&message, 0, NULL, NULL) == -EINVAL); - assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL) - == -EINVAL); + assert_se(dhcp_option_parse(&message, 0, NULL, NULL, NULL) == -EINVAL); + assert_se(dhcp_option_parse(&message, sizeof(DHCPMessage) - 1, NULL, NULL, NULL) == -EINVAL); } static void test_message_init(void) { @@ -101,7 +98,7 @@ static void test_message_init(void) { assert_se(magic[2] == 83); assert_se(magic[3] == 99); - assert_se(dhcp_option_parse(message, len, NULL, NULL) >= 0); + assert_se(dhcp_option_parse(message, len, NULL, NULL, NULL) >= 0); } static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, @@ -113,14 +110,9 @@ static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, message = malloc0(len); assert_se(message); - if (options && optlen) - memcpy(&message->options, options, optlen); - - if (file && filelen <= 128) - memcpy(&message->file, file, filelen); - - if (sname && snamelen <= 64) - memcpy(&message->sname, sname, snamelen); + memcpy_safe(&message->options, options, optlen); + memcpy_safe(&message->file, file, filelen); + memcpy_safe(&message->sname, sname, snamelen); return message; } @@ -130,12 +122,12 @@ static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { while (*descpos < *desclen) { switch(descoption[*descpos]) { - case DHCP_OPTION_PAD: + case SD_DHCP_OPTION_PAD: *descpos += 1; break; - case DHCP_OPTION_MESSAGE_TYPE: - case DHCP_OPTION_OVERLOAD: + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_OVERLOAD: *descpos += 3; break; @@ -145,8 +137,8 @@ static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { } } -static int test_options_cb(uint8_t code, uint8_t len, const uint8_t *option, void *user_data) { - struct option_desc *desc = user_data; +static int test_options_cb(uint8_t code, uint8_t len, const void *option, void *userdata) { + struct option_desc *desc = userdata; uint8_t *descoption = NULL; int *desclen = NULL, *descpos = NULL; uint8_t optcode = 0; @@ -158,10 +150,10 @@ static int test_options_cb(uint8_t code, uint8_t len, const uint8_t *option, voi if (!desc) return -EINVAL; - assert_se(code != DHCP_OPTION_PAD); - assert_se(code != DHCP_OPTION_END); - assert_se(code != DHCP_OPTION_MESSAGE_TYPE); - assert_se(code != DHCP_OPTION_OVERLOAD); + assert_se(code != SD_DHCP_OPTION_PAD); + assert_se(code != SD_DHCP_OPTION_END); + assert_se(code != SD_DHCP_OPTION_MESSAGE_TYPE); + assert_se(code != SD_DHCP_OPTION_OVERLOAD); while (desc->pos >= 0 || desc->filepos >= 0 || desc->snamepos >= 0) { @@ -207,10 +199,10 @@ static int test_options_cb(uint8_t code, uint8_t len, const uint8_t *option, voi for (i = 0; i < len; i++) { if (verbose) - printf("0x%02x(0x%02x) ", option[i], + printf("0x%02x(0x%02x) ", ((uint8_t*) option)[i], descoption[*descpos + 2 + i]); - assert_se(option[i] == descoption[*descpos + 2 + i]); + assert_se(((uint8_t*) option)[i] == descoption[*descpos + 2 + i]); } if (verbose) @@ -264,19 +256,12 @@ static void test_options(struct option_desc *desc) { buflen = sizeof(DHCPMessage) + optlen; if (!desc) { - assert_se((res = dhcp_option_parse(message, buflen, - test_options_cb, - NULL)) == -ENOMSG); + assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, NULL, NULL)) == -ENOMSG); } else if (desc->success) { - assert_se((res = dhcp_option_parse(message, buflen, - test_options_cb, - desc)) >= 0); - assert_se(desc->pos == -1 && desc->filepos == -1 && - desc->snamepos == -1); + assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) >= 0); + assert_se(desc->pos == -1 && desc->filepos == -1 && desc->snamepos == -1); } else - assert_se((res = dhcp_option_parse(message, buflen, - test_options_cb, - desc)) < 0); + assert_se((res = dhcp_option_parse(message, buflen, test_options_cb, desc, NULL)) < 0); if (verbose) printf("DHCP type %s\n", dhcp_type(res)); @@ -306,27 +291,27 @@ static void test_option_set(void) { result->options[2] = 'C'; result->options[3] = 'D'; - assert_se(dhcp_option_append(result, 0, &offset, 0, DHCP_OPTION_PAD, + assert_se(dhcp_option_append(result, 0, &offset, 0, SD_DHCP_OPTION_PAD, 0, NULL) == -ENOBUFS); assert_se(offset == 0); offset = 4; - assert_se(dhcp_option_append(result, 5, &offset, 0, DHCP_OPTION_PAD, + assert_se(dhcp_option_append(result, 5, &offset, 0, SD_DHCP_OPTION_PAD, 0, NULL) == -ENOBUFS); assert_se(offset == 4); - assert_se(dhcp_option_append(result, 6, &offset, 0, DHCP_OPTION_PAD, + assert_se(dhcp_option_append(result, 6, &offset, 0, SD_DHCP_OPTION_PAD, 0, NULL) >= 0); assert_se(offset == 5); offset = pos = 4; len = 11; - while (pos < len && options[pos] != DHCP_OPTION_END) { + while (pos < len && options[pos] != SD_DHCP_OPTION_END) { assert_se(dhcp_option_append(result, len, &offset, DHCP_OVERLOAD_SNAME, options[pos], options[pos + 1], &options[pos + 2]) >= 0); - if (options[pos] == DHCP_OPTION_PAD) + if (options[pos] == SD_DHCP_OPTION_PAD) pos++; else pos += 2 + options[pos + 1]; @@ -344,15 +329,15 @@ static void test_option_set(void) { if (verbose) printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9], - DHCP_OPTION_END); + SD_DHCP_OPTION_END); - assert_se(result->options[9] == DHCP_OPTION_END); + assert_se(result->options[9] == SD_DHCP_OPTION_END); if (verbose) printf("%2d: 0x%02x(0x%02x) (options)\n", 10, result->options[10], - DHCP_OPTION_PAD); + SD_DHCP_OPTION_PAD); - assert_se(result->options[10] == DHCP_OPTION_PAD); + assert_se(result->options[10] == SD_DHCP_OPTION_PAD); for (i = 0; i < pos - 8; i++) { if (verbose) diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c index 9f60ab761e..e81c508c7f 100644 --- a/src/libsystemd-network/test-dhcp-server.c +++ b/src/libsystemd-network/test-dhcp-server.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -22,14 +20,21 @@ #include <errno.h> +#include "sd-dhcp-server.h" #include "sd-event.h" -#include "event-util.h" -#include "sd-dhcp-server.h" #include "dhcp-server-internal.h" +static void test_pool(struct in_addr *address, unsigned size, int ret) { + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + + assert_se(sd_dhcp_server_new(&server, 1) >= 0); + + assert_se(sd_dhcp_server_configure_pool(server, address, 8, 0, size) == ret); +} + static int test_basic(sd_event *event) { - _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL; + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct in_addr address_lo = { .s_addr = htonl(INADDR_LOOPBACK), }; @@ -54,15 +59,14 @@ static int test_basic(sd_event *event) { 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, 28) == -EINVAL); - assert_se(sd_dhcp_server_set_address(server, &address_lo, 38) == -ERANGE); - assert_se(sd_dhcp_server_set_address(server, &address_lo, 8) >= 0); - assert_se(sd_dhcp_server_set_address(server, &address_lo, 8) == -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_configure_pool(server, &address_any, 28, 0, 0) == -EINVAL); + assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 38, 0, 0) == -ERANGE); + assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 0); + assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) == -EBUSY); + + test_pool(&address_any, 1, -EINVAL); + test_pool(&address_lo, 1, 0); r = sd_dhcp_server_start(server); @@ -79,7 +83,7 @@ static int test_basic(sd_event *event) { } static void test_message_handler(void) { - _cleanup_dhcp_server_unref_ sd_dhcp_server *server = NULL; + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; struct { DHCPMessage message; struct { @@ -109,35 +113,33 @@ static void test_message_handler(void) { .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.code = SD_DHCP_OPTION_MESSAGE_TYPE, .option_type.length = 1, .option_type.type = DHCP_DISCOVER, - .end = DHCP_OPTION_END, + .end = SD_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, 8) >= 0); + assert_se(sd_dhcp_server_configure_pool(server, &address_lo, 8, 0, 0) >= 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)) == 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; /* TODO, shouldn't this fail? */ assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); - test.end = DHCP_OPTION_END; + test.end = SD_DHCP_OPTION_END; assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); 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.code = SD_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)) == DHCP_OFFER); @@ -159,11 +161,11 @@ static void test_message_handler(void) { 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.code = SD_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)) == DHCP_NAK); - test.option_server_id.code = DHCP_OPTION_SERVER_IDENTIFIER; + test.option_server_id.code = SD_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); @@ -178,7 +180,7 @@ static void test_message_handler(void) { 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.code = SD_DHCP_OPTION_CLIENT_IDENTIFIER; test.option_client_id.length = 7; test.option_client_id.id[0] = 0x01; test.option_client_id.id[1] = 'A'; @@ -193,6 +195,15 @@ static void test_message_handler(void) { assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); } +static uint64_t client_id_hash_helper(DHCPClientId *id, uint8_t key[HASH_KEY_SIZE]) { + struct siphash state; + + siphash24_init(&state, key); + client_id_hash_func(id, &state); + + return htole64(siphash24_finalize(&state)); +} + static void test_client_id_hash(void) { DHCPClientId a = { .length = 4, @@ -208,18 +219,18 @@ static void test_client_id_hash(void) { 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)); + assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&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)); + assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&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)); + assert_se(client_id_hash_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key)); free(b.data); b.data = (uint8_t*)strdup("abce"); @@ -230,7 +241,7 @@ static void test_client_id_hash(void) { } int main(int argc, char *argv[]) { - _cleanup_event_unref_ sd_event *e; + _cleanup_(sd_event_unrefp) sd_event *e; int r; log_set_max_level(LOG_DEBUG); diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c index 761854714b..bd289fa802 100644 --- a/src/libsystemd-network/test-dhcp6-client.c +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -19,23 +17,23 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <net/ethernet.h> #include <stdbool.h> #include <stdio.h> -#include <sys/types.h> #include <sys/socket.h> +#include <sys/types.h> #include <unistd.h> -#include <net/ethernet.h> -#include "socket-util.h" -#include "macro.h" +#include "sd-dhcp6-client.h" #include "sd-event.h" -#include "event-util.h" -#include "virt.h" -#include "sd-dhcp6-client.h" -#include "dhcp6-protocol.h" #include "dhcp6-internal.h" #include "dhcp6-lease-internal.h" +#include "dhcp6-protocol.h" +#include "fd-util.h" +#include "macro.h" +#include "socket-util.h" +#include "virt.h" static struct ether_addr mac_addr = { .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} @@ -61,20 +59,20 @@ static int test_client_basic(sd_event *e) { assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); - assert_se(sd_dhcp6_client_set_index(client, 15) == 0); - assert_se(sd_dhcp6_client_set_index(client, -42) == -EINVAL); - assert_se(sd_dhcp6_client_set_index(client, -1) == 0); - assert_se(sd_dhcp6_client_set_index(client, 42) >= 0); + assert_se(sd_dhcp6_client_set_ifindex(client, 15) == 0); + assert_se(sd_dhcp6_client_set_ifindex(client, -42) == -EINVAL); + assert_se(sd_dhcp6_client_set_ifindex(client, -1) == 0); + assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0); assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, sizeof (mac_addr), ARPHRD_ETHER) >= 0); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_CLIENTID) == -EINVAL); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_DNS_SERVERS) == -EEXIST); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_NTP_SERVER) == -EEXIST); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_SNTP_SERVERS) == 0); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_DOMAIN_LIST) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_CLIENTID) == -EINVAL); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_NTP_SERVER) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_SNTP_SERVERS) == -EEXIST); + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DOMAIN_LIST) == -EEXIST); assert_se(sd_dhcp6_client_set_request_option(client, 10) == -EINVAL); assert_se(sd_dhcp6_client_set_callback(client, NULL, NULL) >= 0); @@ -88,9 +86,9 @@ static int test_client_basic(sd_event *e) { static int test_option(sd_event *e) { uint8_t packet[] = { 'F', 'O', 'O', - 0x00, DHCP6_OPTION_ORO, 0x00, 0x07, + 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x07, 'A', 'B', 'C', 'D', 'E', 'F', 'G', - 0x00, DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09, + 0x00, SD_DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09, '1', '2', '3', '4', '5', '6', '7', '8', '9', 'B', 'A', 'R', }; @@ -124,7 +122,7 @@ static int test_option(sd_event *e) { &optval) >= 0); pos += 4 + optlen; assert_se(buf == &packet[pos]); - assert_se(optcode == DHCP6_OPTION_ORO); + assert_se(optcode == SD_DHCP6_OPTION_ORO); assert_se(optlen == 7); assert_se(buflen + pos == sizeof(packet)); @@ -137,7 +135,7 @@ static int test_option(sd_event *e) { &optval) >= 0); pos += 4 + optlen; assert_se(buf == &packet[pos]); - assert_se(optcode == DHCP6_OPTION_VENDOR_CLASS); + assert_se(optcode == SD_DHCP6_OPTION_VENDOR_CLASS); assert_se(optlen == 9); assert_se(buflen + pos == sizeof(packet)); @@ -205,7 +203,7 @@ static uint8_t msg_reply[173] = { }; static int test_advertise_option(sd_event *e) { - _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; DHCP6Message *advertise = (DHCP6Message *)msg_advertise; uint8_t *optval, *opt = msg_advertise + sizeof(DHCP6Message); uint16_t optcode; @@ -216,6 +214,8 @@ static int test_advertise_option(sd_event *e) { uint32_t lt_pref, lt_valid; int r; bool opt_clientid = false; + struct in6_addr *addrs; + char **domains; if (verbose) printf("* %s\n", __FUNCTION__); @@ -230,13 +230,13 @@ static int test_advertise_option(sd_event *e) { &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(optlen == 14); opt_clientid = true; break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_se(optlen == 94); assert_se(!memcmp(optval, &msg_advertise[26], optlen)); @@ -255,7 +255,7 @@ static int test_advertise_option(sd_event *e) { break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: assert_se(optlen == 14); assert_se(!memcmp(optval, &msg_advertise[179], optlen)); @@ -263,7 +263,7 @@ static int test_advertise_option(sd_event *e) { optlen) >= 0); break; - case DHCP6_OPTION_PREFERENCE: + case SD_DHCP6_OPTION_PREFERENCE: assert_se(optlen == 1); assert_se(!*optval); @@ -271,11 +271,29 @@ static int test_advertise_option(sd_event *e) { *optval) >= 0); break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(optlen == 2); break; + case SD_DHCP6_OPTION_DNS_SERVERS: + assert_se(optlen == 16); + assert_se(dhcp6_lease_set_dns(lease, optval, + optlen) >= 0); + break; + + case SD_DHCP6_OPTION_DOMAIN_LIST: + assert_se(optlen == 11); + assert_se(dhcp6_lease_set_domains(lease, optval, + optlen) >= 0); + break; + + case SD_DHCP6_OPTION_SNTP_SERVERS: + assert_se(optlen == 16); + assert_se(dhcp6_lease_set_sntp(lease, optval, + optlen) >= 0); + break; + default: break; } @@ -315,6 +333,19 @@ static int test_advertise_option(sd_event *e) { assert_se(dhcp6_lease_get_preference(lease, &preference) >= 0); assert_se(preference == 0); + r = sd_dhcp6_lease_get_dns(lease, &addrs); + assert_se(r == 1); + assert_se(!memcmp(addrs, &msg_advertise[124], r * 16)); + + r = sd_dhcp6_lease_get_domains(lease, &domains); + assert_se(r == 1); + assert_se(!strcmp("lab.intra", domains[0])); + assert_se(domains[1] == NULL); + + r = sd_dhcp6_lease_get_ntp_addrs(lease, &addrs); + assert_se(r == 1); + assert_se(!memcmp(addrs, &msg_advertise[159], r * 16)); + return 0; } @@ -324,26 +355,29 @@ static int test_hangcheck(sd_event_source *s, uint64_t usec, void *userdata) { return 0; } -int detect_vm(const char **id) { - return 1; -} - -int detect_container(const char **id) { - return 1; -} - -int detect_virtualization(const char **id) { - return 1; -} - static void test_client_solicit_cb(sd_dhcp6_client *client, int event, void *userdata) { sd_event *e = userdata; + sd_dhcp6_lease *lease; + struct in6_addr *addrs; + char **domains; assert_se(e); - assert_se(event == DHCP6_EVENT_IP_ACQUIRE); + assert_se(event == SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); + + assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); - assert_se(sd_dhcp6_client_set_request_option(client, DHCP6_OPTION_DNS_SERVERS) == -EBUSY); + assert_se(sd_dhcp6_lease_get_domains(lease, &domains) == 1); + assert_se(!strcmp("lab.intra", domains[0])); + assert_se(domains[1] == NULL); + + assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1); + assert_se(!memcmp(addrs, &msg_advertise[124], 16)); + + assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1); + assert_se(!memcmp(addrs, &msg_advertise[159], 16)); + + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EBUSY); if (verbose) printf(" got DHCPv6 event %d\n", event); @@ -371,7 +405,7 @@ static int test_client_send_reply(DHCP6Message *request) { static int test_client_verify_request(DHCP6Message *request, uint8_t *option, size_t len) { - _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; uint8_t *optval; uint16_t optcode; size_t optlen; @@ -389,7 +423,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); found_clientid = true; @@ -398,7 +432,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_se(!found_iana); found_iana = true; @@ -417,7 +451,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: assert_se(!found_serverid); found_serverid = true; @@ -426,7 +460,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(!found_elapsed_time); found_elapsed_time = true; @@ -453,8 +487,7 @@ static int test_client_verify_request(DHCP6Message *request, uint8_t *option, return 0; } -static int test_client_send_advertise(DHCP6Message *solicit) -{ +static int test_client_send_advertise(DHCP6Message *solicit) { DHCP6Message advertise; advertise.transaction_id = solicit->transaction_id; @@ -486,7 +519,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); found_clientid = true; @@ -495,7 +528,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_se(!found_iana); found_iana = true; @@ -505,7 +538,7 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(!found_elapsed_time); found_elapsed_time = true; @@ -524,24 +557,46 @@ static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, static void test_client_information_cb(sd_dhcp6_client *client, int event, void *userdata) { sd_event *e = userdata; + sd_dhcp6_lease *lease; + struct in6_addr *addrs; + struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; + char **domains; assert_se(e); - assert_se(event == DHCP6_EVENT_INFORMATION_REQUEST); + assert_se(event == SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); + + assert_se(sd_dhcp6_client_get_lease(client, &lease) >= 0); + + assert_se(sd_dhcp6_lease_get_domains(lease, &domains) == 1); + assert_se(!strcmp("lab.intra", domains[0])); + assert_se(domains[1] == NULL); + + assert_se(sd_dhcp6_lease_get_dns(lease, &addrs) == 1); + assert_se(!memcmp(addrs, &msg_advertise[124], 16)); + + assert_se(sd_dhcp6_lease_get_ntp_addrs(lease, &addrs) == 1); + assert_se(!memcmp(addrs, &msg_advertise[159], 16)); if (verbose) printf(" got DHCPv6 event %d\n", event); + assert_se(sd_dhcp6_client_set_information_request(client, false) == -EBUSY); + assert_se(sd_dhcp6_client_set_callback(client, NULL, e) >= 0); + assert_se(sd_dhcp6_client_stop(client) >= 0); assert_se(sd_dhcp6_client_set_information_request(client, false) >= 0); + assert_se(sd_dhcp6_client_set_callback(client, test_client_solicit_cb, e) >= 0); + assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); + assert_se(sd_dhcp6_client_start(client) >= 0); } static int test_client_verify_information_request(DHCP6Message *information_request, uint8_t *option, size_t len) { - _cleanup_dhcp6_lease_free_ sd_dhcp6_lease *lease = NULL; + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; uint8_t *optval; uint16_t optcode; size_t optlen; @@ -557,7 +612,7 @@ static int test_client_verify_information_request(DHCP6Message *information_requ while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, &optval)) >= 0) { switch(optcode) { - case DHCP6_OPTION_CLIENTID: + case SD_DHCP6_OPTION_CLIENTID: assert_se(!found_clientid); found_clientid = true; @@ -566,17 +621,17 @@ static int test_client_verify_information_request(DHCP6Message *information_requ break; - case DHCP6_OPTION_IA_NA: + case SD_DHCP6_OPTION_IA_NA: assert_not_reached("IA TA option must not be present"); break; - case DHCP6_OPTION_SERVERID: + case SD_DHCP6_OPTION_SERVERID: assert_not_reached("Server ID option must not be present"); break; - case DHCP6_OPTION_ELAPSED_TIME: + case SD_DHCP6_OPTION_ELAPSED_TIME: assert_se(!found_elapsed_time); found_elapsed_time = true; @@ -646,7 +701,8 @@ int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { static int test_client_solicit(sd_event *e) { sd_dhcp6_client *client; usec_t time_now = now(clock_boottime_or_monotonic()); - bool val = true; + struct in6_addr address = { { { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01 } } }; + int val = true; if (verbose) printf("* %s\n", __FUNCTION__); @@ -656,7 +712,7 @@ static int test_client_solicit(sd_event *e) { assert_se(sd_dhcp6_client_attach_event(client, e, 0) >= 0); - assert_se(sd_dhcp6_client_set_index(client, test_index) == 0); + assert_se(sd_dhcp6_client_set_ifindex(client, test_index) == 0); assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, sizeof (mac_addr), ARPHRD_ETHER) >= 0); @@ -674,6 +730,8 @@ static int test_client_solicit(sd_event *e) { time_now + 2 * USEC_PER_SEC, 0, test_hangcheck, NULL) >= 0); + assert_se(sd_dhcp6_client_set_local_address(client, &address) >= 0); + assert_se(sd_dhcp6_client_start(client) >= 0); sd_event_loop(e); @@ -688,7 +746,7 @@ static int test_client_solicit(sd_event *e) { } int main(int argc, char *argv[]) { - _cleanup_event_unref_ sd_event *e; + _cleanup_(sd_event_unrefp) sd_event *e; assert_se(sd_event_new(&e) >= 0); diff --git a/src/libsystemd-network/test-icmp6-rs.c b/src/libsystemd-network/test-icmp6-rs.c deleted file mode 100644 index 8ba21106a7..0000000000 --- a/src/libsystemd-network/test-icmp6-rs.c +++ /dev/null @@ -1,357 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - 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/icmp6.h> - -#include "socket-util.h" - -#include "dhcp6-internal.h" -#include "sd-icmp6-nd.h" - -static struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} -}; - -static bool verbose = false; -static sd_event_source *test_hangcheck; -static int test_fd[2]; - -typedef int (*send_ra_t)(uint8_t flags); -static send_ra_t send_ra_function; - -static int test_rs_hangcheck(sd_event_source *s, uint64_t usec, - void *userdata) { - assert_se(false); - - return 0; -} - -int dhcp_network_icmp6_bind_router_solicitation(int index) { - assert_se(index == 42); - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, test_fd) < 0) - return -errno; - - return test_fd[0]; -} - -static int send_ra_short_prefix(uint8_t flags) { - uint8_t advertisement[] = { - 0x86, 0x00, 0xbe, 0xd7, 0x40, 0xc0, 0x00, 0xb4, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - - 0x03, 0x04, 0x34, 0xc0, 0x00, 0x00, 0x01, 0xf4, - 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 - }; - - assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == - sizeof(advertisement)); - - return 0; -} - -static void test_short_prefix_cb(sd_icmp6_nd *nd, int event, void *userdata) { - sd_event *e = userdata; - struct { - struct in6_addr addr; - uint8_t prefixlen; - bool success; - } addrs[] = { - { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 52, true }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0x0d, 0xad, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 64, false }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 60, true }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x9d, 0xab, 0xcd, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 64, true }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xed, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }, - 52, true }, - }; - uint8_t prefixlen; - unsigned int i; - - for (i = 0; i < ELEMENTSOF(addrs); i++) { - printf(" %s prefix %02x%02x:%02x%02x:%02x%02x:%02x%02x", - __FUNCTION__, - addrs[i].addr.s6_addr[0], addrs[i].addr.s6_addr[1], - addrs[i].addr.s6_addr[2], addrs[i].addr.s6_addr[3], - addrs[i].addr.s6_addr[4], addrs[i].addr.s6_addr[5], - addrs[i].addr.s6_addr[6], addrs[i].addr.s6_addr[7]); - - if (addrs[i].success) { - assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr, - &prefixlen) >= 0); - assert_se(addrs[i].prefixlen == prefixlen); - printf("/%d onlink\n", prefixlen); - } else { - assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr, - &prefixlen) == -EADDRNOTAVAIL); - printf("/128 offlink\n"); - } - } - - sd_event_exit(e, 0); -} - -static int send_ra_prefixes(uint8_t flags) { - uint8_t advertisement[] = { - 0x86, 0x00, 0xbe, 0xd7, 0x40, 0xc0, 0x00, 0xb4, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x04, 0x3f, 0xc0, 0x00, 0x00, 0x01, 0xf4, - 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x04, 0x40, 0x00, 0x00, 0x00, 0x02, 0x58, - 0x00, 0x00, 0x02, 0x1c, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0x0d, 0xad, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x04, 0x3c, 0x80, 0x00, 0x00, 0x03, 0x84, - 0x00, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x03, 0x84, - 0x00, 0x00, 0x03, 0x20, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x9d, 0xab, 0xcd, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53 - }; - - assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == - sizeof(advertisement)); - - return 0; -} - -static void test_prefixes_cb(sd_icmp6_nd *nd, int event, void *userdata) { - sd_event *e = userdata; - struct { - struct in6_addr addr; - uint8_t prefixlen; - bool success; - } addrs[] = { - { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 63, true }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0x0d, 0xad, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 64, false }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0x0b, 0x16, 0xd0, 0x0d, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 60, true }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x9d, 0xab, 0xcd, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } } }, - 64, true }, - { { { { 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xed, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } }, - 63, false }, - }; - uint8_t prefixlen; - unsigned int i; - - for (i = 0; i < ELEMENTSOF(addrs); i++) { - printf(" %s prefix %02x%02x:%02x%02x:%02x%02x:%02x%02x", - __FUNCTION__, - addrs[i].addr.s6_addr[0], addrs[i].addr.s6_addr[1], - addrs[i].addr.s6_addr[2], addrs[i].addr.s6_addr[3], - addrs[i].addr.s6_addr[4], addrs[i].addr.s6_addr[5], - addrs[i].addr.s6_addr[6], addrs[i].addr.s6_addr[7]); - - if (addrs[i].success) { - assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr, - &prefixlen) >= 0); - assert_se(addrs[i].prefixlen == prefixlen); - printf("/%d onlink\n", prefixlen); - } else { - assert_se(sd_icmp6_ra_get_prefixlen(nd, &addrs[i].addr, - &prefixlen) == -EADDRNOTAVAIL); - printf("/128 offlink\n"); - } - } - - send_ra_function = send_ra_short_prefix; - assert_se(sd_icmp6_nd_set_callback(nd, test_short_prefix_cb, e) >= 0); - assert_se(sd_icmp6_nd_stop(nd) >= 0); - assert_se(sd_icmp6_router_solicitation_start(nd) >= 0); -} - -static void test_prefixes(void) { - sd_event *e; - sd_icmp6_nd *nd; - - if (verbose) - printf("* %s\n", __FUNCTION__); - - send_ra_function = send_ra_prefixes; - - assert_se(sd_event_new(&e) >= 0); - - assert_se(sd_icmp6_nd_new(&nd) >= 0); - assert_se(nd); - - assert_se(sd_icmp6_nd_attach_event(nd, e, 0) >= 0); - - assert_se(sd_icmp6_nd_set_index(nd, 42) >= 0); - assert_se(sd_icmp6_nd_set_mac(nd, &mac_addr) >= 0); - assert_se(sd_icmp6_nd_set_callback(nd, test_prefixes_cb, e) >= 0); - - assert_se(sd_icmp6_router_solicitation_start(nd) >= 0); - - sd_event_loop(e); - - nd = sd_icmp6_nd_unref(nd); - assert_se(!nd); - - close(test_fd[1]); - - sd_event_unref(e); -} - -static int send_ra(uint8_t flags) { - uint8_t advertisement[] = { - 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, - 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, - }; - - advertisement[5] = flags; - - assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == - sizeof(advertisement)); - - if (verbose) - printf(" sent RA with flag 0x%02x\n", flags); - - return 0; -} - -int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - return send_ra_function(0); -} - -static void test_rs_done(sd_icmp6_nd *nd, int event, void *userdata) { - sd_event *e = userdata; - static int idx = 0; - struct { - uint8_t flag; - int event; - } flag_event[] = { - { 0, ICMP6_EVENT_ROUTER_ADVERTISMENT_NONE }, - { ND_RA_FLAG_OTHER, ICMP6_EVENT_ROUTER_ADVERTISMENT_OTHER }, - { ND_RA_FLAG_MANAGED, ICMP6_EVENT_ROUTER_ADVERTISMENT_MANAGED } - }; - uint32_t mtu; - - assert_se(nd); - - assert_se(event == flag_event[idx].event); - idx++; - - if (verbose) - printf(" got event %d\n", event); - - if (idx < 3) { - send_ra(flag_event[idx].flag); - return; - } - - assert_se(sd_icmp6_ra_get_mtu(nd, &mtu) == -ENOMSG); - - sd_event_exit(e, 0); -} - -static void test_rs(void) { - sd_event *e; - sd_icmp6_nd *nd; - usec_t time_now = now(clock_boottime_or_monotonic()); - - if (verbose) - printf("* %s\n", __FUNCTION__); - - send_ra_function = send_ra; - - assert_se(sd_event_new(&e) >= 0); - - assert_se(sd_icmp6_nd_new(&nd) >= 0); - assert_se(nd); - - assert_se(sd_icmp6_nd_attach_event(nd, e, 0) >= 0); - - assert_se(sd_icmp6_nd_set_index(nd, 42) >= 0); - assert_se(sd_icmp6_nd_set_mac(nd, &mac_addr) >= 0); - assert_se(sd_icmp6_nd_set_callback(nd, test_rs_done, e) >= 0); - - assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(), - time_now + 2 *USEC_PER_SEC, 0, - test_rs_hangcheck, NULL) >= 0); - - assert_se(sd_icmp6_nd_stop(nd) >= 0); - assert_se(sd_icmp6_router_solicitation_start(nd) >= 0); - assert_se(sd_icmp6_nd_stop(nd) >= 0); - - assert_se(sd_icmp6_router_solicitation_start(nd) >= 0); - - sd_event_loop(e); - - test_hangcheck = sd_event_source_unref(test_hangcheck); - - nd = sd_icmp6_nd_unref(nd); - assert_se(!nd); - - close(test_fd[1]); - - sd_event_unref(e); -} - -int main(int argc, char *argv[]) { - - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - log_open(); - - test_rs(); - test_prefixes(); - - return 0; -} diff --git a/src/libsystemd-network/test-ipv4ll-manual.c b/src/libsystemd-network/test-ipv4ll-manual.c new file mode 100644 index 0000000000..2b1387fa91 --- /dev/null +++ b/src/libsystemd-network/test-ipv4ll-manual.c @@ -0,0 +1,128 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen <teg@jklm.no> + + 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 <errno.h> +#include <net/if.h> +#include <stdlib.h> +#include <unistd.h> +#include <linux/veth.h> + +#include "sd-event.h" +#include "sd-ipv4ll.h" +#include "sd-netlink.h" + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "netlink-util.h" +#include "parse-util.h" +#include "string-util.h" +#include "util.h" + +static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) { + _cleanup_free_ char *address = NULL; + struct in_addr addr = {}; + + assert_se(ll); + + if (sd_ipv4ll_get_address(ll, &addr) >= 0) + assert_se(in_addr_to_string(AF_INET, (const union in_addr_union*) &addr, &address) >= 0); + + switch (event) { + case SD_IPV4LL_EVENT_BIND: + log_info("bound %s", strna(address)); + break; + case SD_IPV4LL_EVENT_CONFLICT: + log_info("conflict on %s", strna(address)); + break; + case SD_IPV4LL_EVENT_STOP: + log_error("the client was stopped with address %s", strna(address)); + break; + default: + assert_not_reached("invalid LL event"); + } +} + +static int client_run(int ifindex, const char *seed_str, const struct ether_addr *ha, sd_event *e) { + sd_ipv4ll *ll; + + assert_se(sd_ipv4ll_new(&ll) >= 0); + assert_se(sd_ipv4ll_attach_event(ll, e, 0) >= 0); + + assert_se(sd_ipv4ll_set_ifindex(ll, ifindex) >= 0); + assert_se(sd_ipv4ll_set_mac(ll, ha) >= 0); + assert_se(sd_ipv4ll_set_callback(ll, ll_handler, NULL) >= 0); + + if (seed_str) { + unsigned seed; + + assert_se(safe_atou(seed_str, &seed) >= 0); + + assert_se(sd_ipv4ll_set_address_seed(ll, seed) >= 0); + } + + log_info("starting IPv4LL client"); + + assert_se(sd_ipv4ll_start(ll) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(!sd_ipv4ll_unref(ll)); + + return EXIT_SUCCESS; +} + +static int test_ll(const char *ifname, const char *seed) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + struct ether_addr ha; + int ifindex; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); + assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); + + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); + + client_run(ifindex, seed, &ha, e); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc == 2) + return test_ll(argv[1], NULL); + else if (argc == 3) + return test_ll(argv[1], argv[2]); + else { + log_error("This program takes one or two arguments.\n" + "\t %s <ifname> [<seed>]", program_invocation_short_name); + return EXIT_FAILURE; + } +} diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c index 5677bfb2d2..fe70697075 100644 --- a/src/libsystemd-network/test-ipv4ll.c +++ b/src/libsystemd-network/test-ipv4ll.c @@ -1,4 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ /*** This file is part of systemd. @@ -18,20 +17,20 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ -#include <stdlib.h> #include <assert.h> #include <errno.h> #include <stdio.h> -#include <sys/types.h> +#include <stdlib.h> #include <sys/socket.h> +#include <sys/types.h> #include <unistd.h> -#include "util.h" -#include "socket-util.h" -#include "event-util.h" - #include "sd-ipv4ll.h" -#include "ipv4ll-internal.h" + +#include "arp-util.h" +#include "fd-util.h" +#include "socket-util.h" +#include "util.h" static bool verbose = false; static bool extended = false; @@ -39,15 +38,16 @@ static int test_fd[2]; static int basic_request_handler_bind = 0; static int basic_request_handler_stop = 0; -static void* basic_request_handler_user_data = (void*)0xCABCAB; +static void* basic_request_handler_userdata = (void*) 0xCABCAB; + static void basic_request_handler(sd_ipv4ll *ll, int event, void *userdata) { - assert_se(userdata == basic_request_handler_user_data); + assert_se(userdata == basic_request_handler_userdata); switch(event) { - case IPV4LL_EVENT_STOP: + case SD_IPV4LL_EVENT_STOP: basic_request_handler_stop = 1; break; - case IPV4LL_EVENT_BIND: + case SD_IPV4LL_EVENT_BIND: basic_request_handler_bind = 1; break; default: @@ -56,10 +56,10 @@ static void basic_request_handler(sd_ipv4ll *ll, int event, void *userdata) { } } -int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, - const struct ether_arp *arp) { +static int arp_network_send_raw_socket(int fd, int ifindex, + const struct ether_arp *arp) { assert_se(arp); - assert_se(link); + assert_se(ifindex > 0); assert_se(fd >= 0); if (send(fd, arp, sizeof(struct ether_arp), 0) < 0) @@ -68,55 +68,40 @@ int arp_network_send_raw_socket(int fd, const union sockaddr_union *link, return 0; } -int arp_network_bind_raw_socket(int index, union sockaddr_union *link) { - if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0) - return -errno; +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + struct ether_arp ea = {}; - return test_fd[0]; -} + assert(fd >= 0); + assert(ifindex > 0); + assert(pa != 0); + assert(ha); -static void test_arp_header(struct ether_arp *arp) { - assert_se(arp); - assert_se(arp->ea_hdr.ar_hrd == htons(ARPHRD_ETHER)); /* HTYPE */ - assert_se(arp->ea_hdr.ar_pro == htons(ETHERTYPE_IP)); /* PTYPE */ - assert_se(arp->ea_hdr.ar_hln == ETH_ALEN); /* HLEN */ - assert_se(arp->ea_hdr.ar_pln == sizeof arp->arp_spa); /* PLEN */ - assert_se(arp->ea_hdr.ar_op == htons(ARPOP_REQUEST)); /* REQUEST */ + return arp_network_send_raw_socket(fd, ifindex, &ea); } -static void test_arp_probe(void) { - struct ether_arp arp; - struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; - be32_t pa = 0x3030; +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + struct ether_arp ea = {}; - if (verbose) - printf("* %s\n", __FUNCTION__); + assert(fd >= 0); + assert(ifindex > 0); + assert(pa != 0); + assert(ha); - arp_packet_probe(&arp, pa, &mac_addr); - test_arp_header(&arp); - assert_se(memcmp(arp.arp_sha, &mac_addr, ETH_ALEN) == 0); - assert_se(memcmp(arp.arp_tpa, &pa, sizeof(pa)) == 0); + return arp_network_send_raw_socket(fd, ifindex, &ea); } -static void test_arp_announce(void) { - struct ether_arp arp; - struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; - be32_t pa = 0x3131; - - if (verbose) - printf("* %s\n", __FUNCTION__); +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac) { + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0) + return -errno; - arp_packet_announcement(&arp, pa, &mac_addr); - test_arp_header(&arp); - assert_se(memcmp(arp.arp_sha, &mac_addr, ETH_ALEN) == 0); - assert_se(memcmp(arp.arp_tpa, &pa, sizeof(pa)) == 0); - assert_se(memcmp(arp.arp_spa, &pa, sizeof(pa)) == 0); + return test_fd[0]; } static void test_public_api_setters(sd_event *e) { - uint8_t seed[8]; + struct in_addr address = {}; + uint64_t seed = 0; sd_ipv4ll *ll; struct ether_addr mac_addr = { .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; @@ -134,22 +119,31 @@ static void test_public_api_setters(sd_event *e) { assert_se(sd_ipv4ll_set_callback(NULL, NULL, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_callback(ll, NULL, NULL) == 0); - assert_se(sd_ipv4ll_set_address_seed(NULL, NULL) == -EINVAL); - assert_se(sd_ipv4ll_set_address_seed(ll, NULL) == -EINVAL); + assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + address.s_addr |= htobe32(169U << 24 | 254U << 16); + assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + address.s_addr |= htobe32(0x00FF); + assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + address.s_addr |= htobe32(0xF000); + assert_se(sd_ipv4ll_set_address(ll, &address) == 0); + address.s_addr |= htobe32(0x0F00); + assert_se(sd_ipv4ll_set_address(ll, &address) == -EINVAL); + + assert_se(sd_ipv4ll_set_address_seed(NULL, seed) == -EINVAL); assert_se(sd_ipv4ll_set_address_seed(ll, seed) == 0); assert_se(sd_ipv4ll_set_mac(NULL, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_mac(ll, NULL) == -EINVAL); assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0); - assert_se(sd_ipv4ll_set_index(NULL, -1) == -EINVAL); - assert_se(sd_ipv4ll_set_index(ll, -1) == -EINVAL); - assert_se(sd_ipv4ll_set_index(ll, -99) == -EINVAL); - assert_se(sd_ipv4ll_set_index(ll, 1) == 0); - assert_se(sd_ipv4ll_set_index(ll, 99) == 0); + assert_se(sd_ipv4ll_set_ifindex(NULL, -1) == -EINVAL); + assert_se(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL); + assert_se(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL); + assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0); + assert_se(sd_ipv4ll_set_ifindex(ll, 99) == 0); assert_se(sd_ipv4ll_ref(ll) == ll); - assert_se(sd_ipv4ll_unref(ll) == ll); + assert_se(sd_ipv4ll_unref(ll) == NULL); /* Cleanup */ assert_se(sd_ipv4ll_unref(ll) == NULL); @@ -175,30 +169,29 @@ static void test_basic_request(sd_event *e) { assert_se(sd_ipv4ll_start(ll) == -EINVAL); assert_se(sd_ipv4ll_set_callback(ll, basic_request_handler, - basic_request_handler_user_data) == 0); + basic_request_handler_userdata) == 0); assert_se(sd_ipv4ll_start(ll) == -EINVAL); - assert_se(sd_ipv4ll_set_index(ll, 1) == 0); + assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0); assert_se(sd_ipv4ll_start(ll) == 0); sd_event_run(e, (uint64_t) -1); assert_se(sd_ipv4ll_start(ll) == -EBUSY); + assert_se(sd_ipv4ll_is_running(ll)); + /* PROBE */ sd_event_run(e, (uint64_t) -1); - assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); - test_arp_header(&arp); + assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); if (extended) { /* PROBE */ sd_event_run(e, (uint64_t) -1); - assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); - test_arp_header(&arp); + assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); /* PROBE */ sd_event_run(e, (uint64_t) -1); - assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); - test_arp_header(&arp); + assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); sd_event_run(e, (uint64_t) -1); assert_se(basic_request_handler_bind == 1); @@ -213,13 +206,15 @@ static void test_basic_request(sd_event *e) { } int main(int argc, char *argv[]) { - _cleanup_event_unref_ sd_event *e = NULL; + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); assert_se(sd_event_new(&e) >= 0); test_public_api_setters(e); - test_arp_probe(); - test_arp_announce(); test_basic_request(e); return 0; diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c index 06545aee59..6bcd65de0a 100644 --- a/src/libsystemd-network/test-lldp.c +++ b/src/libsystemd-network/test-lldp.c @@ -1,5 +1,3 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - /*** This file is part of systemd. @@ -20,212 +18,244 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include <arpa/inet.h> +#include <net/ethernet.h> #include <stdio.h> #include <string.h> -#include <net/ethernet.h> -#include <arpa/inet.h> +#include <unistd.h> + +#include "sd-event.h" +#include "sd-lldp.h" +#include "alloc-util.h" +#include "fd-util.h" +#include "lldp-network.h" #include "macro.h" -#include "lldp.h" -#include "lldp-tlv.h" +#include "string-util.h" #define TEST_LLDP_PORT "em1" #define TEST_LLDP_TYPE_SYSTEM_NAME "systemd-lldp" #define TEST_LLDP_TYPE_SYSTEM_DESC "systemd-lldp-desc" -static struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} -}; - -static int lldp_build_tlv_packet(tlv_packet **ret) { - _cleanup_tlv_packet_free_ tlv_packet *m = NULL; - const uint8_t lldp_dst[] = LLDP_MULTICAST_ADDR; - struct ether_header ether = { - .ether_type = htons(ETHERTYPE_LLDP), - }; - - /* Append ethernet header */ - memcpy(ðer.ether_dhost, lldp_dst, ETHER_ADDR_LEN); - memcpy(ðer.ether_shost, &mac_addr, ETHER_ADDR_LEN); - - assert_se(tlv_packet_new(&m) >= 0); - - assert_se(tlv_packet_append_bytes(m, ðer, sizeof(struct ether_header)) >= 0); - - assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_CHASSIS_ID) >= 0); - - assert_se(tlv_packet_append_u8(m, LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS) >= 0); - assert_se(tlv_packet_append_bytes(m, &mac_addr, ETHER_ADDR_LEN) >= 0); - - assert_se(lldp_tlv_packet_close_container(m) >= 0); - - /* port name */ - assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_PORT_ID) >= 0); - - assert_se(tlv_packet_append_u8(m, LLDP_PORT_SUBTYPE_INTERFACE_NAME) >= 0); - assert_se(tlv_packet_append_bytes(m, TEST_LLDP_PORT, strlen(TEST_LLDP_PORT) + 1) >= 0); - - assert_se(lldp_tlv_packet_close_container(m) >= 0); - - /* ttl */ - assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_TTL) >= 0); - - assert_se(tlv_packet_append_u16(m, 170) >= 0); - - assert_se(lldp_tlv_packet_close_container(m) >= 0); - - /* system name */ - assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_SYSTEM_NAME) >= 0); - - assert_se(tlv_packet_append_bytes(m, TEST_LLDP_TYPE_SYSTEM_NAME, - strlen(TEST_LLDP_TYPE_SYSTEM_NAME)) >= 0); - assert_se(lldp_tlv_packet_close_container(m) >= 0); +static int test_fd[2] = { -1, -1 }; +static int lldp_handler_calls; - /* system descrition */ - assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_SYSTEM_DESCRIPTION) >= 0); +int lldp_network_bind_raw_socket(int ifindex) { + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0) + return -errno; - assert_se(tlv_packet_append_bytes(m, TEST_LLDP_TYPE_SYSTEM_DESC, - strlen(TEST_LLDP_TYPE_SYSTEM_DESC)) >= 0); - - assert_se(lldp_tlv_packet_close_container(m) >= 0); - - /* Mark end of packet */ - assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_END) >= 0); - assert_se(lldp_tlv_packet_close_container(m) >= 0); - - *ret = m; - - m = NULL; - - return 0; + return test_fd[0]; } -static int lldp_parse_chassis_tlv(tlv_packet *m, uint8_t *type) { - uint8_t *p, subtype; - uint16_t length; +static void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) { + lldp_handler_calls++; +} - assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_CHASSIS_ID) >= 0); - assert_se(tlv_packet_read_u8(m, &subtype) >= 0); +static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) { + int r; - switch (subtype) { - case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + r = sd_lldp_new(lldp); + if (r < 0) + return r; - *type = LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS; - assert_se(tlv_packet_read_bytes(m, &p, &length) >= 0); + r = sd_lldp_set_ifindex(*lldp, 42); + if (r < 0) + return r; - assert_se(memcmp(p, &mac_addr.ether_addr_octet, ETHER_ADDR_LEN) == 0); + r = sd_lldp_set_callback(*lldp, cb, cb_data); + if (r < 0) + return r; - break; - default: - assert_not_reached("Unhandled option"); - } + r = sd_lldp_attach_event(*lldp, e, 0); + if (r < 0) + return r; - assert_se(lldp_tlv_packet_exit_container(m) >= 0); + r = sd_lldp_start(*lldp); + if (r < 0) + return r; return 0; } -static int lldp_parse_port_id_tlv(tlv_packet *m) { - _cleanup_free_ char *p = NULL; - char *str = NULL; - uint16_t length; - uint8_t subtype; - - assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_PORT_ID) >= 0); - - assert_se(tlv_packet_read_u8(m, &subtype) >= 0); +static int stop_lldp(sd_lldp *lldp) { + int r; - switch (subtype) { - case LLDP_PORT_SUBTYPE_INTERFACE_NAME: - assert_se(tlv_packet_read_string(m, &str, &length) >= 0); + r = sd_lldp_stop(lldp); + if (r < 0) + return r; - p = strndup(str, length-1); - assert_se(p); + r = sd_lldp_detach_event(lldp); + if (r < 0) + return r; - assert_se(streq(p, TEST_LLDP_PORT) == 1); - break; - default: - assert_not_reached("Unhandled option"); - } - - assert_se(lldp_tlv_packet_exit_container(m) >= 0); + sd_lldp_unref(lldp); + safe_close(test_fd[1]); return 0; } -static int lldp_parse_system_name_tlv(tlv_packet *m) { - _cleanup_free_ char *p = NULL; - char *str = NULL; - uint16_t length; +static void test_receive_basic_packet(sd_event *e) { + + static const uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ + 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds*/ + /* LLDP optional TLVs */ + 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, /* Port Description: "Port" */ + 0x0a, 0x03, 0x53, 0x59, 0x53, /* System Name: "SYS" */ + 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, /* System Description: "foo" (NULL-terminated) */ + 0x00, 0x00 /* End Of LLDPDU */ + }; - assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_SYSTEM_NAME) >= 0); - assert_se(tlv_packet_read_string(m, &str, &length) >= 0); + sd_lldp *lldp; + sd_lldp_neighbor **neighbors; + uint8_t type; + const void *data; + uint16_t ttl; + size_t length; + const char *str; - p = strndup(str, length); - assert_se(p); + lldp_handler_calls = 0; + assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); - assert_se(streq(p, TEST_LLDP_TYPE_SYSTEM_NAME) == 1); + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(lldp_handler_calls == 1); + assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1); - assert_se(lldp_tlv_packet_exit_container(m) >= 0); + assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0); + assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS); + assert_se(length == ETH_ALEN); + assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN)); - return 1; -} + assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0); + assert_se(type == SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME); + assert_se(length == 3); + assert_se(strneq((char *) data, "1/3", 3)); -static int lldp_parse_system_desc_tlv(tlv_packet *m) { - _cleanup_free_ char *p = NULL; - char *str = NULL; - uint16_t length; + assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0); + assert_se(streq(str, "Port")); - assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_SYSTEM_DESCRIPTION) >= 0); - assert_se(tlv_packet_read_string(m, &str, &length) >= 0); + assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0); + assert_se(streq(str, "SYS")); - p = strndup(str, length); - assert_se(p); + assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0); + assert_se(streq(str, "foo")); - assert_se(streq(p, TEST_LLDP_TYPE_SYSTEM_DESC) == 1); + assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0); + assert_se(ttl == 120); - assert_se(lldp_tlv_packet_exit_container(m) >= 0); + sd_lldp_neighbor_unref(neighbors[0]); + free(neighbors); - return 0; + assert_se(stop_lldp(lldp) == 0); } -static int lldp_parse_ttl_tlv(tlv_packet *m) { - uint16_t ttl; - - assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_TTL) >= 0); - assert_se(tlv_packet_read_u16(m, &ttl) >= 0); +static void test_receive_incomplete_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_neighbor **neighbors; + uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ + /* Missing TTL */ + 0x00, 0x00 /* End Of LLDPDU */ + }; - assert_se(ttl == 170); + lldp_handler_calls = 0; + assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); - assert_se(lldp_tlv_packet_exit_container(m) >= 0); + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(lldp_handler_calls == 0); + assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 0); - return 0; + assert_se(stop_lldp(lldp) == 0); } -static int lldp_parse_tlv_packet(tlv_packet *m, int len) { - uint8_t subtype; - - assert_se(tlv_packet_parse_pdu(m, len) >= 0); - assert_se(lldp_parse_chassis_tlv(m, &subtype) >= 0); - assert_se(lldp_parse_port_id_tlv(m) >= 0); - assert_se(lldp_parse_system_name_tlv(m) >= 0); - assert_se(lldp_parse_ttl_tlv(m) >= 0); - assert_se(lldp_parse_system_desc_tlv(m) >= 0); +static void test_receive_oui_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_neighbor **neighbors; + uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port TLV: interface name, "1/3" */ + 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds*/ + /* LLDP optional TLVs */ + 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, /* Port VLAN ID: 0x1234 */ + 0x12, 0x34, + 0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* Port and protocol: flag 1, PPVID 0x7788 */ + 0x01, 0x77, 0x88, + 0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03, /* VLAN Name: ID 0x1234, name "Vlan51" */ + 0x12, 0x34, 0x06, 0x56, 0x6c, 0x61, + 0x6e, 0x35, 0x31, + 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06, /* Management VID: 0x0102 */ + 0x01, 0x02, + 0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07, /* Link aggregation: status 1, ID 0x00140012 */ + 0x01, 0x00, 0x14, 0x00, 0x12, + 0x00, 0x00 /* End of LLDPDU */ + }; - return 0; + lldp_handler_calls = 0; + assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); + + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(lldp_handler_calls == 1); + assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1); + + assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_CHASSIS_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_PORT_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_TTL) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_END) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0); + + sd_lldp_neighbor_unref(neighbors[0]); + free(neighbors); + + assert_se(stop_lldp(lldp) == 0); } int main(int argc, char *argv[]) { - _cleanup_tlv_packet_free_ tlv_packet *tlv = NULL; - - /* form a packet */ - lldp_build_tlv_packet(&tlv); + _cleanup_(sd_event_unrefp) sd_event *e = NULL; - /* parse the packet */ - tlv_packet_parse_pdu(tlv, tlv->length); + log_set_max_level(LOG_DEBUG); - /* verify */ - lldp_parse_tlv_packet(tlv, tlv->length); + /* LLDP reception tests */ + assert_se(sd_event_new(&e) == 0); + test_receive_basic_packet(e); + test_receive_incomplete_packet(e); + test_receive_oui_packet(e); return 0; } diff --git a/src/libsystemd-network/test-ndisc-rs.c b/src/libsystemd-network/test-ndisc-rs.c new file mode 100644 index 0000000000..d9669488be --- /dev/null +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -0,0 +1,317 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + 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/icmp6.h> +#include <arpa/inet.h> + +#include "sd-ndisc.h" + +#include "alloc-util.h" +#include "hexdecoct.h" +#include "icmp6-util.h" +#include "socket-util.h" +#include "strv.h" + +static struct ether_addr mac_addr = { + .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} +}; + +static bool verbose = false; +static sd_event_source *test_hangcheck; +static int test_fd[2]; + +typedef int (*send_ra_t)(uint8_t flags); +static send_ra_t send_ra_function; + +static void router_dump(sd_ndisc_router *rt) { + struct in6_addr addr; + char buf[FORMAT_TIMESTAMP_MAX]; + uint8_t hop_limit; + uint64_t t, flags; + uint32_t mtu; + uint16_t lifetime; + unsigned preference; + int r; + + assert_se(rt); + + log_info("--"); + assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA); + + assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0); + log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0); + log_info("Monotonic: %" PRIu64, t); + + if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0) + log_info("No hop limit set"); + else + log_info("Hop limit: %u", hop_limit); + + assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); + log_info("Flags: <%s|%s>", + flags & ND_RA_FLAG_OTHER ? "OTHER" : "", + flags & ND_RA_FLAG_MANAGED ? "MANAGED" : ""); + + assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0); + log_info("Preference: %s", + preference == SD_NDISC_PREFERENCE_LOW ? "low" : + preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium"); + + assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0); + log_info("Lifetime: %" PRIu16, lifetime); + + if (sd_ndisc_router_get_mtu(rt, &mtu) < 0) + log_info("No MTU set"); + else + log_info("MTU: %" PRIu32, mtu); + + r = sd_ndisc_router_option_rewind(rt); + for (;;) { + uint8_t type; + + assert_se(r >= 0); + + if (r == 0) + break; + + assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0); + + log_info(">> Option %u", type); + + switch (type) { + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: { + _cleanup_free_ char *c = NULL; + const void *p; + size_t n; + + assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0); + assert_se(n > 2); + assert_se(c = hexmem((uint8_t*) p + 2, n - 2)); + + log_info("Address: %s", c); + break; + } + + case SD_NDISC_OPTION_PREFIX_INFORMATION: { + uint32_t lifetime_valid, lifetime_preferred; + unsigned prefix_len; + uint8_t pfl; + struct in6_addr a; + char buff[INET6_ADDRSTRLEN]; + + assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0); + log_info("Valid Lifetime: %" PRIu32, lifetime_valid); + + assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0); + log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred); + + assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0); + log_info("Flags: <%s|%s>", + pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "", + pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : ""); + + assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0); + log_info("Prefix Length: %u", prefix_len); + + assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0); + log_info("Prefix: %s", inet_ntop(AF_INET6, &a, buff, sizeof(buff))); + + break; + } + + case SD_NDISC_OPTION_RDNSS: { + const struct in6_addr *a; + uint32_t lt; + int n, i; + + n = sd_ndisc_router_rdnss_get_addresses(rt, &a); + assert_se(n > 0); + + for (i = 0; i < n; i++) { + char buff[INET6_ADDRSTRLEN]; + log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff))); + } + + assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, <) >= 0); + log_info("Lifetime: %" PRIu32, lt); + break; + } + + case SD_NDISC_OPTION_DNSSL: { + _cleanup_strv_free_ char **l = NULL; + uint32_t lt; + int n, i; + + n = sd_ndisc_router_dnssl_get_domains(rt, &l); + assert_se(n > 0); + + for (i = 0; i < n; i++) + log_info("Domain: %s", l[i]); + + assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, <) >= 0); + log_info("Lifetime: %" PRIu32, lt); + break; + }} + + r = sd_ndisc_router_option_next(rt); + } +} + +static int test_rs_hangcheck(sd_event_source *s, uint64_t usec, + void *userdata) { + assert_se(false); + + return 0; +} + +int icmp6_bind_router_solicitation(int index) { + assert_se(index == 42); + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, test_fd) < 0) + return -errno; + + return test_fd[0]; +} + +static int send_ra(uint8_t flags) { + uint8_t advertisement[] = { + 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, + }; + + advertisement[5] = flags; + + assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == + sizeof(advertisement)); + + if (verbose) + printf(" sent RA with flag 0x%02x\n", flags); + + return 0; +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + return send_ra_function(0); +} + +static void test_callback(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) { + sd_event *e = userdata; + static unsigned idx = 0; + uint64_t flags_array[] = { + 0, + 0, + 0, + ND_RA_FLAG_OTHER, + ND_RA_FLAG_MANAGED + }; + uint64_t flags; + uint32_t mtu; + + assert_se(nd); + + if (event != SD_NDISC_EVENT_ROUTER) + return; + + router_dump(rt); + + assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); + assert_se(flags == flags_array[idx]); + idx++; + + if (verbose) + printf(" got event 0x%02" PRIx64 "\n", flags); + + if (idx < ELEMENTSOF(flags_array)) { + send_ra(flags_array[idx]); + return; + } + + assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA); + + sd_event_exit(e, 0); +} + +static void test_rs(void) { + sd_event *e; + sd_ndisc *nd; + usec_t time_now = now(clock_boottime_or_monotonic()); + + if (verbose) + printf("* %s\n", __FUNCTION__); + + send_ra_function = send_ra; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(nd); + + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0); + + assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(), + time_now + 2 *USEC_PER_SEC, 0, + test_rs_hangcheck, NULL) >= 0); + + assert_se(sd_ndisc_stop(nd) >= 0); + assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_ndisc_stop(nd) >= 0); + + assert_se(sd_ndisc_start(nd) >= 0); + + sd_event_loop(e); + + test_hangcheck = sd_event_source_unref(test_hangcheck); + + nd = sd_ndisc_unref(nd); + assert_se(!nd); + + close(test_fd[1]); + + sd_event_unref(e); +} + +int main(int argc, char *argv[]) { + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + test_rs(); + + return 0; +} diff --git a/src/libsystemd-network/test-pppoe.c b/src/libsystemd-network/test-pppoe.c deleted file mode 100644 index 72878f4b51..0000000000 --- a/src/libsystemd-network/test-pppoe.c +++ /dev/null @@ -1,176 +0,0 @@ -/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ - -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen <teg@jklm.no> - - 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 <stdlib.h> -#include <errno.h> -#include <unistd.h> - -#include <linux/veth.h> -#include <net/if.h> - -#include "util.h" -#include "sd-event.h" -#include "event-util.h" -#include "sd-netlink.h" -#include "sd-pppoe.h" -#include "process-util.h" - -static void pppoe_handler(sd_pppoe *ppp, int event, void *userdata) { - static int pppoe_state = -1; - sd_event *e = userdata; - - assert_se(ppp); - assert_se(e); - - switch (event) { - case PPPOE_EVENT_RUNNING: - assert_se(pppoe_state == -1); - log_info("running"); - break; - case PPPOE_EVENT_STOPPED: - assert_se(pppoe_state == PPPOE_EVENT_RUNNING); - log_info("stopped"); - assert_se(sd_event_exit(e, 0) >= 0); - break; - default: - assert_not_reached("invalid pppoe event"); - } - - pppoe_state = event; -} - -static int client_run(const char *client_name, sd_event *e) { - sd_pppoe *pppoe; - int client_ifindex; - - client_ifindex = (int) if_nametoindex(client_name); - assert_se(client_ifindex > 0); - - assert_se(sd_pppoe_new(&pppoe) >= 0); - assert_se(sd_pppoe_attach_event(pppoe, e, 0) >= 0); - - assert_se(sd_pppoe_set_ifname(pppoe, "pppoe-client") >= 0); - assert_se(sd_pppoe_set_ifindex(pppoe, client_ifindex) >= 0); - assert_se(sd_pppoe_set_callback(pppoe, pppoe_handler, e) >= 0); - - log_info("starting PPPoE client, it will exit when the server times out and sends PADT"); - - assert_se(sd_pppoe_start(pppoe) >= 0); - - assert_se(sd_event_loop(e) >= 0); - - assert_se(!sd_pppoe_unref(pppoe)); - - return EXIT_SUCCESS; -} - -static int test_pppoe_server(sd_event *e) { - sd_netlink *rtnl; - sd_netlink_message *m; - pid_t pid; - int r, client_ifindex, server_ifindex; - - r = unshare(CLONE_NEWNET); - if (r < 0 && errno == EPERM) - return EXIT_TEST_SKIP; - - assert_se(r >= 0); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0) >= 0); - assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, "pppoe-server") >= 0); - assert_se(sd_netlink_message_open_container(m, IFLA_LINKINFO) >= 0); - assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "veth") >= 0); - assert_se(sd_netlink_message_open_container(m, VETH_INFO_PEER) >= 0); - assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, "pppoe-client") >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_message_close_container(m) >= 0); - assert_se(sd_netlink_call(rtnl, m, 0, NULL) >= 0); - - client_ifindex = (int) if_nametoindex("pppoe-client"); - assert_se(client_ifindex > 0); - server_ifindex = (int) if_nametoindex("pppoe-server"); - assert_se(server_ifindex > 0); - - m = sd_netlink_message_unref(m); - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, client_ifindex) >= 0); - assert_se(sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP) >= 0); - assert_se(sd_netlink_call(rtnl, m, 0, NULL) >= 0); - - m = sd_netlink_message_unref(m); - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_SETLINK, server_ifindex) >= 0); - assert_se(sd_rtnl_message_link_set_flags(m, IFF_UP, IFF_UP) >= 0); - assert_se(sd_netlink_call(rtnl, m, 0, NULL) >= 0); - - pid = fork(); - assert_se(pid >= 0); - if (pid == 0) { - /* let the client send some discover messages before the server is started */ - sleep(2); - - /* TODO: manage pppoe-server-options */ - execlp("pppoe-server", "pppoe-server", "-F", - "-I", "pppoe-server", - "-C", "Test-AC", - "-S", "Service-Default", - "-S", "Service-First-Auxiliary", - "-S", "Service-Second-Auxiliary", - NULL); - assert_not_reached("failed to execute pppoe-server. not installed?"); - } - - client_run("pppoe-client", e); - - assert_se(kill(pid, SIGTERM) >= 0); - assert_se(wait_for_terminate(pid, NULL) >= 0); - - assert_se(!sd_netlink_message_unref(m)); - assert_se(!sd_netlink_unref(rtnl)); - - return EXIT_SUCCESS; -} - -int main(int argc, char *argv[]) { - _cleanup_event_unref_ sd_event *e = NULL; - - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - log_open(); - - assert_se(sd_event_new(&e) >= 0); - - if (argc == 1) { - log_info("running PPPoE client against local server"); - - return test_pppoe_server(e); - } else if (argc == 2) { - log_info("running PPPoE client over '%s'", argv[1]); - - return client_run(argv[1], e); - } else { - log_error("This program takes one or no arguments.\n" - "\t %s [<ifname>]", program_invocation_short_name); - return EXIT_FAILURE; - } -} |