diff options
Diffstat (limited to 'src/libsystemd-network')
48 files changed, 15973 insertions, 0 deletions
diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile new file mode 100644 index 0000000000..3ca5e10caf --- /dev/null +++ b/src/libsystemd-network/Makefile @@ -0,0 +1,181 @@ +# -*- Mode: makefile; indent-tabs-mode: t -*- +# +# This file is part of systemd. +# +# Copyright 2010-2012 Lennart Poettering +# Copyright 2010-2012 Kay Sievers +# Copyright 2013 Zbigniew JÄ™drzejewski-Szmek +# Copyright 2013 David Strauss +# Copyright 2016 Luke Shumaker +# +# 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 $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libsystemd-network.la + +libsystemd_network_la_CFLAGS = \ + $(AM_CFLAGS) \ + $(KMOD_CFLAGS) + +libsystemd_network_la_SOURCES = \ + src/systemd/sd-dhcp-client.h \ + src/systemd/sd-dhcp-server.h \ + src/systemd/sd-dhcp-lease.h \ + src/systemd/sd-ipv4ll.h \ + src/systemd/sd-ipv4acd.h \ + src/systemd/sd-ndisc.h \ + src/systemd/sd-dhcp6-client.h \ + src/systemd/sd-dhcp6-lease.h \ + src/systemd/sd-lldp.h \ + src/libsystemd-network/sd-dhcp-client.c \ + src/libsystemd-network/sd-dhcp-server.c \ + src/libsystemd-network/dhcp-network.c \ + src/libsystemd-network/dhcp-option.c \ + src/libsystemd-network/dhcp-packet.c \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/dhcp-server-internal.h \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-lease-internal.h \ + src/libsystemd-network/sd-dhcp-lease.c \ + src/libsystemd-network/sd-ipv4ll.c \ + src/libsystemd-network/sd-ipv4acd.c \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/arp-util.c \ + src/libsystemd-network/network-internal.c \ + src/libsystemd-network/network-internal.h \ + src/libsystemd-network/sd-ndisc.c \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/icmp6-util.c \ + src/libsystemd-network/sd-dhcp6-client.c \ + src/libsystemd-network/dhcp6-internal.h \ + src/libsystemd-network/dhcp6-protocol.h \ + src/libsystemd-network/dhcp6-network.c \ + src/libsystemd-network/dhcp6-option.c \ + src/libsystemd-network/dhcp6-lease-internal.h \ + src/libsystemd-network/sd-dhcp6-lease.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c \ + src/libsystemd-network/lldp.h \ + src/libsystemd-network/lldp-tlv.h \ + src/libsystemd-network/lldp-tlv.c \ + src/libsystemd-network/lldp-network.h \ + src/libsystemd-network/lldp-network.c \ + src/libsystemd-network/lldp-port.h \ + src/libsystemd-network/lldp-port.c \ + src/libsystemd-network/lldp-internal.h \ + src/libsystemd-network/lldp-internal.c \ + src/libsystemd-network/sd-lldp.c + +libsystemd_network_la_LIBADD = \ + $(KMOD_LIBS) + +test_dhcp_option_SOURCES = \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/test-dhcp-option.c + +test_dhcp_option_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_dhcp_client_SOURCES = \ + src/systemd/sd-dhcp-client.h \ + src/libsystemd-network/dhcp-protocol.h \ + src/libsystemd-network/dhcp-internal.h \ + src/libsystemd-network/test-dhcp-client.c + +test_dhcp_client_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_dhcp_server_SOURCES = \ + src/libsystemd-network/test-dhcp-server.c + +test_dhcp_server_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ipv4ll_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/arp-util.h \ + src/libsystemd-network/test-ipv4ll.c + +test_ipv4ll_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ipv4ll_manual_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/test-ipv4ll-manual.c + +test_ipv4ll_manual_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_acd_SOURCES = \ + src/systemd/sd-ipv4acd.h \ + src/libsystemd-network/test-acd.c + +test_acd_LDADD = \ + libsystemd-network.la \ + libshared.la + +test_ndisc_rs_SOURCES = \ + src/systemd/sd-dhcp6-client.h \ + src/systemd/sd-ndisc.h \ + src/libsystemd-network/icmp6-util.h \ + src/libsystemd-network/test-ndisc-rs.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c + +test_ndisc_rs_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libshared.la + +test_dhcp6_client_SOURCES = \ + src/systemd/sd-dhcp6-client.h \ + src/libsystemd-network/dhcp6-internal.h \ + src/libsystemd-network/test-dhcp6-client.c \ + src/libsystemd-network/dhcp-identifier.h \ + src/libsystemd-network/dhcp-identifier.c + +test_dhcp6_client_LDADD = \ + libsystemd-network.la \ + libudev.la \ + libshared.la + +test_lldp_SOURCES = \ + src/libsystemd-network/lldp.h \ + src/libsystemd-network/lldp-tlv.h \ + src/libsystemd-network/lldp-tlv.c \ + src/libsystemd-network/test-lldp.c + +test_lldp_LDADD = \ + libsystemd-network.la \ + libshared.la + +tests += \ + test-dhcp-option \ + test-dhcp-client \ + test-dhcp-server \ + test-ipv4ll \ + test-ndisc-rs \ + test-dhcp6-client \ + test-lldp + +$(eval $(value automake2autothing)) +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd-network/arp-util.c b/src/libsystemd-network/arp-util.c new file mode 100644 index 0000000000..4660c7ea09 --- /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 = htons(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 = htons(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 = htons(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htons(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ + .ea_hdr.ar_op = htons(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/arp-util.h b/src/libsystemd-network/arp-util.h new file mode 100644 index 0000000000..3ef56b002a --- /dev/null +++ b/src/libsystemd-network/arp-util.h @@ -0,0 +1,32 @@ +#pragma once + +/*** + 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 <netinet/if_ether.h> + +#include "socket-util.h" +#include "sparse-endian.h" + +int arp_network_bind_raw_socket(int index, be32_t address, const struct ether_addr *eth_mac); + +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 new file mode 100644 index 0000000000..d58f9029cc --- /dev/null +++ b/src/libsystemd-network/dhcp-identifier.c @@ -0,0 +1,98 @@ +/*** + This file is part of systemd. + + Copyright (C) 2015 Tom Gundersen <teg@jklmen> + + 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 "libudev.h" +#include <systemd/sd-id128.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_identifier_set_duid_en(struct duid *duid, size_t *len) { + sd_id128_t machine_id; + uint64_t hash; + int r; + + assert(duid); + assert(len); + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + unaligned_write_be16(&duid->type, DHCP6_DUID_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; 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 */ + _cleanup_udev_device_unref_ struct udev_device *device = NULL; + const char *name = NULL; + uint64_t id; + + 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)]; + + udev = udev_new(); + if (!udev) + return -ENOMEM; + + sprintf(ifindex_str, "n%d", ifindex); + device = udev_device_new_from_device_id(udev, ifindex_str); + if (device) { + if (udev_device_get_is_initialized(device) <= 0) + /* not yet ready */ + return -EBUSY; + + name = net_get_name(device); + } + } + + if (name) + id = siphash24(name, strlen(name), HASH_KEY.bytes); + else + /* fall back to MAC address if no predictable name available */ + id = siphash24(mac, mac_len, HASH_KEY.bytes); + + id = htole64(id); + + /* fold into 32 bits */ + unaligned_write_be32(_id, (id & 0xffffffff) ^ (id >> 32)); + + return 0; +} diff --git a/src/libsystemd-network/dhcp-identifier.h b/src/libsystemd-network/dhcp-identifier.h new file mode 100644 index 0000000000..31f461abd1 --- /dev/null +++ b/src/libsystemd-network/dhcp-identifier.h @@ -0,0 +1,63 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2015 Tom Gundersen <teg@jklmen> + + 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 <systemd/sd-id128.h> + +#include "macro.h" +#include "sparse-endian.h" +#include "unaligned.h" + +/* 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 + +struct duid { + uint16_t type; + union { + struct { + /* DHCP6_DUID_LLT */ + uint16_t htype; + uint32_t time; + uint8_t haddr[0]; + } _packed_ llt; + struct { + /* DHCP6_DUID_EN */ + uint32_t pen; + uint8_t id[8]; + } _packed_ en; + struct { + /* DHCP6_DUID_LL */ + int16_t htype; + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* DHCP6_DUID_UUID */ + sd_id128_t uuid; + } _packed_ uuid; + struct { + uint8_t data[MAX_DUID_LEN]; + } _packed_ raw; + }; +} _packed_; + +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 new file mode 100644 index 0000000000..51cb193847 --- /dev/null +++ b/src/libsystemd-network/dhcp-internal.h @@ -0,0 +1,68 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <linux/if_packet.h> +#include <net/ethernet.h> +#include <net/if_arp.h> +#include <stdint.h> + +#include <systemd/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, + size_t mac_addr_len, uint16_t arp_type); +int dhcp_network_bind_udp_socket(be32_t address, uint16_t port); +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, + 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); + +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 void *option, void *userdata); + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_cb_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, + size_t *optoffset); + +uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len); + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, + uint16_t source, be32_t destination_addr, + uint16_t destination, uint16_t len); + +int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum); + +/* 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_(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__) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h new file mode 100644 index 0000000000..646e612cee --- /dev/null +++ b/src/libsystemd-network/dhcp-lease-internal.h @@ -0,0 +1,102 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <stdint.h> +#include <linux/if_packet.h> + +#include <systemd/sd-dhcp-client.h> + +#include "dhcp-protocol.h" +#include "list.h" +#include "util.h" + +struct sd_dhcp_route { + struct in_addr dst_addr; + struct in_addr gw_addr; + 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 { + unsigned n_ref; + + /* each 0 if unset */ + uint32_t t1; + uint32_t t2; + uint32_t lifetime; + + /* each 0 if unset */ + be32_t address; + be32_t server_address; + 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 sd_dhcp_route *static_route; + size_t static_route_size, static_route_allocated; + + uint16_t mtu; /* 0 if unset */ + + char *domainname; + char *hostname; + char *root_path; + + void *client_id; + size_t client_id_len; + + 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 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 void *client_id, size_t client_id_len); + +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 new file mode 100644 index 0000000000..fac25e0fa2 --- /dev/null +++ b/src/libsystemd-network/dhcp-network.c @@ -0,0 +1,235 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 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 <net/ethernet.h> +#include <net/if_arp.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <linux/filter.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, + size_t mac_addr_len, + const uint8_t *bcast_addr, + const struct ether_addr *eth_mac, + uint16_t arp_type, uint8_t dhcp_hlen) { + 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(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */ + BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_PORT_CLIENT, 1, 0), /* UDP destination port == DHCP client port ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, dhcp_hlen, 1, 0), /* address length == dhcp_hlen ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((unsigned int *) 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(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((unsigned short *) (((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(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + _cleanup_close_ int s = -1; + int r, on = 1; + + assert(ifindex > 0); + assert(link); + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_PACKET, PACKET_AUXDATA, &on, sizeof(on)); + if (r < 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_IP); + link->ll.sll_ifindex = ifindex; + link->ll.sll_hatype = htons(arp_type); + link->ll.sll_halen = mac_addr_len; + memcpy(link->ll.sll_addr, bcast_addr, mac_addr_len); + + r = bind(s, &link->sa, sizeof(link->ll)); + if (r < 0) + return -errno; + + r = s; + s = -1; + + return r; +} + +int dhcp_network_bind_raw_socket(int ifindex, union sockaddr_union *link, + uint32_t xid, const uint8_t *mac_addr, + size_t mac_addr_len, uint16_t arp_type) { + static const uint8_t eth_bcast[] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; + /* Default broadcast address for IPoIB */ + static const uint8_t ib_bcast[] = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }; + struct ether_addr eth_mac = { { 0, 0, 0, 0, 0, 0 } }; + const uint8_t *bcast_addr = NULL; + uint8_t dhcp_hlen = 0; + + assert_return(mac_addr_len > 0, -EINVAL); + + if (arp_type == ARPHRD_ETHER) { + assert_return(mac_addr_len == ETH_ALEN, -EINVAL); + memcpy(ð_mac, mac_addr, ETH_ALEN); + bcast_addr = eth_bcast; + dhcp_hlen = ETH_ALEN; + } else if (arp_type == ARPHRD_INFINIBAND) { + assert_return(mac_addr_len == INFINIBAND_ALEN, -EINVAL); + bcast_addr = ib_bcast; + } else + return -EINVAL; + + return _bind_raw_socket(ifindex, link, xid, mac_addr, mac_addr_len, + bcast_addr, ð_mac, arp_type, dhcp_hlen); +} + +int dhcp_network_bind_udp_socket(be32_t address, uint16_t port) { + union sockaddr_union src = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(port), + .in.sin_addr.s_addr = address, + }; + _cleanup_close_ int s = -1; + int r, on = 1, tos = IPTOS_CLASS_CS6; + + s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (r < 0) + return -errno; + + if (address == INADDR_ANY) { + r = setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)); + if (r < 0) + return -errno; + } else { + r = setsockopt(s, IPPROTO_IP, IP_FREEBIND, &on, sizeof(on)); + if (r < 0) + return -errno; + } + + r = bind(s, &src.sa, sizeof(src.in)); + if (r < 0) + return -errno; + + r = s; + s = -1; + + return r; +} + +int dhcp_network_send_raw_socket(int s, const union sockaddr_union *link, + const void *packet, size_t len) { + int r; + + assert(link); + assert(packet); + assert(len); + + r = sendto(s, packet, len, 0, &link->sa, sizeof(link->ll)); + if (r < 0) + return -errno; + + return 0; +} + +int dhcp_network_send_udp_socket(int s, be32_t address, uint16_t port, + const void *packet, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(port), + .in.sin_addr.s_addr = address, + }; + int r; + + assert(s >= 0); + assert(packet); + assert(len); + + r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/dhcp-option.c b/src/libsystemd-network/dhcp-option.c new file mode 100644 index 0000000000..df1996c8ce --- /dev/null +++ b/src/libsystemd-network/dhcp-option.c @@ -0,0 +1,268 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 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 <stdint.h> +#include <stdio.h> +#include <string.h> + +#include "alloc-util.h" +#include "utf8.h" + +#include "dhcp-internal.h" + +static int option_append(uint8_t options[], size_t size, size_t *offset, + uint8_t code, size_t optlen, const void *optval) { + assert(options); + assert(offset); + + if (code != SD_DHCP_OPTION_END) + /* always make sure there is space for an END option */ + size --; + + switch (code) { + + case SD_DHCP_OPTION_PAD: + case SD_DHCP_OPTION_END: + if (size < *offset + 1) + return -ENOBUFS; + + options[*offset] = code; + *offset += 1; + break; + + default: + if (size < *offset + optlen + 2) + return -ENOBUFS; + + options[*offset] = code; + options[*offset + 1] = optlen; + + if (optlen) { + assert(optval); + + memcpy(&options[*offset + 2], optval, optlen); + } + + *offset += optlen + 2; + + break; + } + + return 0; +} + +int dhcp_option_append(DHCPMessage *message, size_t size, size_t *offset, + uint8_t overload, + uint8_t code, size_t optlen, const void *optval) { + size_t file_offset = 0, sname_offset =0; + bool file, sname; + int r; + + assert(message); + assert(offset); + + file = overload & DHCP_OVERLOAD_FILE; + sname = overload & DHCP_OVERLOAD_SNAME; + + if (*offset < size) { + /* still space in the options array */ + r = option_append(message->options, size, offset, code, optlen, optval); + if (r >= 0) + return 0; + 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, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + *offset = size; + } else + return r; + } + + if (overload & DHCP_OVERLOAD_FILE) { + file_offset = *offset - size; + + if (file_offset < sizeof(message->file)) { + /* still space in the 'file' array */ + r = option_append(message->file, sizeof(message->file), &file_offset, code, optlen, optval); + if (r >= 0) { + *offset = size + file_offset; + return 0; + } 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, SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + *offset = size + sizeof(message->file); + } else + return r; + } + } + + if (overload & DHCP_OVERLOAD_SNAME) { + sname_offset = *offset - size - (file ? sizeof(message->file) : 0); + + if (sname_offset < sizeof(message->sname)) { + /* still space in the 'sname' array */ + r = option_append(message->sname, sizeof(message->sname), &sname_offset, code, optlen, optval); + if (r >= 0) { + *offset = size + (file ? sizeof(message->file) : 0) + sname_offset; + return 0; + } else { + /* no space, or other error, give up */ + return r; + } + } + } + + return -ENOBUFS; +} + +static int parse_options(const uint8_t options[], size_t buflen, uint8_t *overload, + uint8_t *message_type, char **error_message, dhcp_option_cb_t cb, + void *userdata) { + uint8_t code, len; + const uint8_t *option; + size_t offset = 0; + + while (offset < buflen) { + code = options[offset ++]; + + switch (code) { + case SD_DHCP_OPTION_PAD: + continue; + + case SD_DHCP_OPTION_END: + return 0; + } + + if (buflen < offset + 1) + return -ENOBUFS; + + 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 = *option; + + break; + + case SD_DHCP_OPTION_ERROR_MESSAGE: + if (len == 0) + return -EINVAL; + + if (error_message) { + _cleanup_free_ char *string = NULL; + + /* Accept a trailing NUL byte */ + if (memchr(option, 0, len - 1)) + return -EINVAL; + + string = strndup((const char *) option, len); + if (!string) + return -ENOMEM; + + if (!ascii_is_valid(string)) + return -EINVAL; + + free(*error_message); + *error_message = string; + string = NULL; + } + + break; + case SD_DHCP_OPTION_OVERLOAD: + if (len != 1) + return -EINVAL; + + if (overload) + *overload = *option; + + break; + + default: + if (cb) + cb(code, len, option, userdata); + + break; + } + + offset += len; + } + + if (offset < buflen) + return -EINVAL; + + return 0; +} + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_cb_t cb, void *userdata, char **_error_message) { + _cleanup_free_ char *error_message = NULL; + uint8_t overload = 0; + uint8_t message_type = 0; + int r; + + if (!message) + return -EINVAL; + + if (len < sizeof(DHCPMessage)) + return -EINVAL; + + len -= sizeof(DHCPMessage); + + 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, &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, &error_message, cb, userdata); + if (r < 0) + return r; + } + + 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 message_type; +} diff --git a/src/libsystemd-network/dhcp-packet.c b/src/libsystemd-network/dhcp-packet.c new file mode 100644 index 0000000000..8d75d49691 --- /dev/null +++ b/src/libsystemd-network/dhcp-packet.c @@ -0,0 +1,191 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> +#include <net/ethernet.h> +#include <net/if_arp.h> +#include <string.h> + +#include "dhcp-internal.h" +#include "dhcp-protocol.h" + +#define DHCP_CLIENT_MIN_OPTIONS_SIZE 312 + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, + uint8_t type, uint16_t arp_type, size_t optlen, + size_t *optoffset) { + size_t offset = 0; + int r; + + assert(op == BOOTREQUEST || op == BOOTREPLY); + assert(arp_type == ARPHRD_ETHER || arp_type == ARPHRD_INFINIBAND); + + message->op = op; + message->htype = arp_type; + message->hlen = (arp_type == ARPHRD_ETHER) ? ETHER_ADDR_LEN : 0; + message->xid = htobe32(xid); + message->magic = htobe32(DHCP_MAGIC_COOKIE); + + r = dhcp_option_append(message, optlen, &offset, 0, + SD_DHCP_OPTION_MESSAGE_TYPE, 1, &type); + if (r < 0) + return r; + + *optoffset = offset; + + return 0; +} + +uint16_t dhcp_packet_checksum(uint8_t *buf, size_t len) { + uint64_t *buf_64 = (uint64_t*)buf; + uint64_t *end_64 = buf_64 + (len / sizeof(uint64_t)); + uint64_t sum = 0; + + /* See RFC1071 */ + + while (buf_64 < end_64) { + sum += *buf_64; + if (sum < *buf_64) + /* wrap around in one's complement */ + sum++; + + buf_64 ++; + } + + if (len % sizeof(uint64_t)) { + /* If the buffer is not aligned to 64-bit, we need + to zero-pad the last few bytes and add them in */ + uint64_t buf_tail = 0; + + memcpy(&buf_tail, buf_64, len % sizeof(uint64_t)); + + sum += buf_tail; + if (sum < buf_tail) + /* wrap around */ + sum++; + } + + while (sum >> 16) + sum = (sum & 0xffff) + (sum >> 16); + + return ~sum; +} + +void dhcp_packet_append_ip_headers(DHCPPacket *packet, be32_t source_addr, + uint16_t source_port, be32_t destination_addr, + uint16_t destination_port, uint16_t len) { + packet->ip.version = IPVERSION; + packet->ip.ihl = DHCP_IP_SIZE / 4; + packet->ip.tot_len = htobe16(len); + + packet->ip.tos = IPTOS_CLASS_CS6; + + packet->ip.protocol = IPPROTO_UDP; + packet->ip.saddr = source_addr; + packet->ip.daddr = destination_addr; + + packet->udp.source = htobe16(source_port); + packet->udp.dest = htobe16(destination_port); + + packet->udp.len = htobe16(len - DHCP_IP_SIZE); + + packet->ip.check = packet->udp.len; + packet->udp.check = dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, len - 8); + + packet->ip.ttl = IPDEFTTL; + packet->ip.check = 0; + packet->ip.check = dhcp_packet_checksum((uint8_t*)&packet->ip, DHCP_IP_SIZE); +} + +int dhcp_packet_verify_headers(DHCPPacket *packet, size_t len, bool checksum) { + size_t hdrlen; + + assert(packet); + + /* IP */ + + if (packet->ip.version != IPVERSION) { + log_debug("ignoring packet: not IPv4"); + return -EINVAL; + } + + if (packet->ip.ihl < 5) { + log_debug("ignoring packet: IPv4 IHL (%u words) invalid", + packet->ip.ihl); + return -EINVAL; + } + + hdrlen = packet->ip.ihl * 4; + if (hdrlen < 20) { + log_debug("ignoring packet: IPv4 IHL (%zu bytes) " + "smaller than minimum (20 bytes)", hdrlen); + return -EINVAL; + } + + if (len < hdrlen) { + log_debug("ignoring packet: packet (%zu bytes) " + "smaller than expected (%zu) by IP header", len, + hdrlen); + return -EINVAL; + } + + /* UDP */ + + if (packet->ip.protocol != IPPROTO_UDP) { + log_debug("ignoring packet: not UDP"); + return -EINVAL; + } + + if (len < hdrlen + be16toh(packet->udp.len)) { + log_debug("ignoring packet: packet (%zu bytes) " + "smaller than expected (%zu) by UDP header", len, + hdrlen + be16toh(packet->udp.len)); + return -EINVAL; + } + + if (be16toh(packet->udp.dest) != DHCP_PORT_CLIENT) { + log_debug("ignoring packet: to port %u, which " + "is not the DHCP client port (%u)", + be16toh(packet->udp.dest), DHCP_PORT_CLIENT); + return -EINVAL; + } + + /* checksums - computing these is relatively expensive, so only do it + if all the other checks have passed + */ + + if (dhcp_packet_checksum((uint8_t*)&packet->ip, hdrlen)) { + log_debug("ignoring packet: invalid IP checksum"); + return -EINVAL; + } + + if (checksum && packet->udp.check) { + packet->ip.check = packet->udp.len; + packet->ip.ttl = 0; + + if (dhcp_packet_checksum((uint8_t*)&packet->ip.ttl, + be16toh(packet->udp.len) + 12)) { + log_debug("ignoring packet: invalid UDP checksum"); + return -EINVAL; + } + } + + return 0; +} diff --git a/src/libsystemd-network/dhcp-protocol.h b/src/libsystemd-network/dhcp-protocol.h new file mode 100644 index 0000000000..3e32484c1d --- /dev/null +++ b/src/libsystemd-network/dhcp-protocol.h @@ -0,0 +1,113 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 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/ip.h> +#include <netinet/udp.h> +#include <stdint.h> + +#include "macro.h" +#include "sparse-endian.h" + +struct DHCPMessage { + uint8_t op; + uint8_t htype; + uint8_t hlen; + uint8_t hops; + be32_t xid; + be16_t secs; + be16_t flags; + be32_t ciaddr; + be32_t yiaddr; + be32_t siaddr; + be32_t giaddr; + uint8_t chaddr[16]; + uint8_t sname[64]; + uint8_t file[128]; + be32_t magic; + uint8_t options[0]; +} _packed_; + +typedef struct DHCPMessage DHCPMessage; + +struct DHCPPacket { + struct iphdr ip; + struct udphdr udp; + DHCPMessage dhcp; +} _packed_; + +typedef struct DHCPPacket DHCPPacket; + +#define DHCP_IP_SIZE (int32_t)(sizeof(struct iphdr)) +#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_MAGIC_COOKIE (uint32_t)(0x63825363) + +enum { + DHCP_PORT_SERVER = 67, + DHCP_PORT_CLIENT = 68, +}; + +enum DHCPState { + DHCP_STATE_INIT = 0, + DHCP_STATE_SELECTING = 1, + DHCP_STATE_INIT_REBOOT = 2, + DHCP_STATE_REBOOTING = 3, + DHCP_STATE_REQUESTING = 4, + DHCP_STATE_BOUND = 5, + DHCP_STATE_RENEWING = 6, + DHCP_STATE_REBINDING = 7, + DHCP_STATE_STOPPED = 8, +}; + +typedef enum DHCPState DHCPState; + +enum { + BOOTREQUEST = 1, + BOOTREPLY = 2, +}; + +enum { + DHCP_DISCOVER = 1, + DHCP_OFFER = 2, + DHCP_REQUEST = 3, + DHCP_DECLINE = 4, + DHCP_ACK = 5, + DHCP_NAK = 6, + DHCP_RELEASE = 7, + DHCP_INFORM = 8, + DHCP_FORCERENEW = 9, +}; + +enum { + DHCP_OVERLOAD_FILE = 1, + DHCP_OVERLOAD_SNAME = 2, +}; + +#define DHCP_MAX_FQDN_LENGTH 255 + +enum { + 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 new file mode 100644 index 0000000000..a5306b2907 --- /dev/null +++ b/src/libsystemd-network/dhcp-server-internal.h @@ -0,0 +1,94 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#pragma once + +#include <systemd/sd-dhcp-server.h> +#include <systemd/sd-event.h> + +#include "dhcp-internal.h" +#include "hashmap.h" +#include "log.h" +#include "util.h" + +typedef struct DHCPClientId { + size_t length; + void *data; +} DHCPClientId; + +typedef struct DHCPLease { + DHCPClientId client_id; + + be32_t address; + be32_t gateway; + uint8_t chaddr[16]; + usec_t expiration; +} DHCPLease; + +struct sd_dhcp_server { + unsigned n_ref; + + sd_event *event; + int event_priority; + sd_event_source *receive_message; + int fd; + int fd_raw; + + int ifindex; + be32_t address; + be32_t netmask; + be32_t subnet; + uint32_t pool_offset; + uint32_t pool_size; + + char *timezone; + + struct in_addr *ntp, *dns; + unsigned n_ntp, n_dns; + + Hashmap *leases_by_client_id; + DHCPLease **bound_leases; + DHCPLease invalid_lease; + + uint32_t max_lease_time, default_lease_time; +}; + +typedef struct DHCPRequest { + /* received message */ + DHCPMessage *message; + + /* options */ + DHCPClientId client_id; + size_t max_optlen; + be32_t server_id; + be32_t requested_ip; + uint32_t lifetime; +} DHCPRequest; + +#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, + size_t length); +int dhcp_server_send_packet(sd_dhcp_server *server, + DHCPRequest *req, DHCPPacket *packet, + int type, size_t optoffset); + +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 new file mode 100644 index 0000000000..228feb49d8 --- /dev/null +++ b/src/libsystemd-network/dhcp6-internal.h @@ -0,0 +1,80 @@ +#pragma once + +/*** + This file is part of systemd. + + 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 + 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 <netinet/in.h> + +#include <systemd/sd-event.h> + +#include "list.h" +#include "macro.h" +#include "sparse-endian.h" + +typedef struct DHCP6Address DHCP6Address; + +struct DHCP6Address { + LIST_FIELDS(DHCP6Address, addresses); + + struct { + struct in6_addr address; + be32_t lifetime_preferred; + be32_t lifetime_valid; + } iaaddr _packed_; +}; + +struct DHCP6IA { + uint16_t type; + struct { + be32_t id; + be32_t lifetime_t1; + be32_t lifetime_t2; + } _packed_; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + + LIST_HEAD(DHCP6Address, addresses); +}; + +typedef struct DHCP6IA DHCP6IA; + +#define log_dhcp6_client(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__) + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval); +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia); +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, + const void *packet, size_t len); + +const char *dhcp6_message_type_to_string(int s) _const_; +int dhcp6_message_type_from_string(const char *s) _pure_; +const char *dhcp6_message_status_to_string(int s) _const_; +int dhcp6_message_status_from_string(const char *s) _pure_; diff --git a/src/libsystemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/dhcp6-lease-internal.h new file mode 100644 index 0000000000..8e9a6f8e8c --- /dev/null +++ b/src/libsystemd-network/dhcp6-lease-internal.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + 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 + 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 <stdint.h> + +#include <systemd/sd-dhcp6-lease.h> + +#include "dhcp6-internal.h" + +struct sd_dhcp6_lease { + unsigned n_ref; + + uint8_t *serverid; + size_t serverid_len; + uint8_t preference; + bool rapid_commit; + + 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); +int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire); +DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia); + +int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, + size_t len); +int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len); +int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference); +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference); +int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease); +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_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) ; + +int dhcp6_lease_new(sd_dhcp6_lease **ret); diff --git a/src/libsystemd-network/dhcp6-network.c b/src/libsystemd-network/dhcp6-network.c new file mode 100644 index 0000000000..fd2d60c9d5 --- /dev/null +++ b/src/libsystemd-network/dhcp6-network.c @@ -0,0 +1,91 @@ +/*** + 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/in.h> +#include <netinet/ip6.h> +#include <stdio.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> +#include <linux/if_packet.h> + +#include "dhcp6-internal.h" +#include "dhcp6-protocol.h" +#include "fd-util.h" +#include "socket-util.h" + +int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { + union sockaddr_union src = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(DHCP6_PORT_CLIENT), + .in6.sin6_scope_id = index, + }; + _cleanup_close_ int s = -1; + int r, off = 0, on = 1; + + assert(index > 0); + assert(local_address); + + 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_V6ONLY, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &off, sizeof(off)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); + if (r < 0) + return -errno; + + r = bind(s, &src.sa, sizeof(src.in6)); + if (r < 0) + return -errno; + + r = s; + s = -1; + return r; +} + +int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, + const void *packet, size_t len) { + union sockaddr_union dest = { + .in6.sin6_family = AF_INET6, + .in6.sin6_port = htobe16(DHCP6_PORT_SERVER), + }; + int r; + + assert(server_address); + + memcpy(&dest.in6.sin6_addr, server_address, sizeof(dest.in6.sin6_addr)); + + r = sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in6)); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c new file mode 100644 index 0000000000..91d7f8a7da --- /dev/null +++ b/src/libsystemd-network/dhcp6-option.c @@ -0,0 +1,414 @@ +/*** + This file is part of systemd. + + 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 + 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/in.h> +#include <string.h> + +#include <systemd/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 + +typedef struct DHCP6Option { + be16_t code; + be16_t len; + uint8_t data[]; +} _packed_ DHCP6Option; + +static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode, + size_t optlen) { + DHCP6Option *option = (DHCP6Option*) *buf; + + assert_return(buf, -EINVAL); + assert_return(*buf, -EINVAL); + assert_return(buflen, -EINVAL); + + if (optlen > 0xffff || *buflen < optlen + sizeof(DHCP6Option)) + return -ENOBUFS; + + option->code = htobe16(optcode); + option->len = htobe16(optlen); + + *buf += sizeof(DHCP6Option); + *buflen -= sizeof(DHCP6Option); + + return 0; +} + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval) { + int r; + + assert_return(optval || optlen == 0, -EINVAL); + + r = option_append_hdr(buf, buflen, code, optlen); + if (r < 0) + return r; + + if (optval) + memcpy(*buf, optval, optlen); + + *buf += optlen; + *buflen -= optlen; + + return 0; +} + +int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) { + uint16_t len; + uint8_t *ia_hdr; + size_t ia_buflen, ia_addrlen = 0; + DHCP6Address *addr; + int r; + + assert_return(buf && *buf && buflen && ia, -EINVAL); + + switch (ia->type) { + case SD_DHCP6_OPTION_IA_NA: + len = DHCP6_OPTION_IA_NA_LEN; + break; + + case SD_DHCP6_OPTION_IA_TA: + len = DHCP6_OPTION_IA_TA_LEN; + break; + + default: + return -EINVAL; + } + + if (*buflen < len) + return -ENOBUFS; + + ia_hdr = *buf; + ia_buflen = *buflen; + + *buf += sizeof(DHCP6Option); + *buflen -= sizeof(DHCP6Option); + + memcpy(*buf, &ia->id, len); + + *buf += len; + *buflen -= len; + + LIST_FOREACH(addresses, addr, ia->addresses) { + r = option_append_hdr(buf, buflen, SD_DHCP6_OPTION_IAADDR, + sizeof(addr->iaaddr)); + if (r < 0) + return r; + + memcpy(*buf, &addr->iaaddr, sizeof(addr->iaaddr)); + + *buf += sizeof(addr->iaaddr); + *buflen -= sizeof(addr->iaaddr); + + ia_addrlen += sizeof(DHCP6Option) + sizeof(addr->iaaddr); + } + + r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen); + if (r < 0) + return r; + + return 0; +} + + +static int option_parse_hdr(uint8_t **buf, size_t *buflen, uint16_t *optcode, size_t *optlen) { + DHCP6Option *option = (DHCP6Option*) *buf; + uint16_t len; + + assert_return(buf, -EINVAL); + assert_return(optcode, -EINVAL); + assert_return(optlen, -EINVAL); + + if (*buflen < sizeof(DHCP6Option)) + return -ENOMSG; + + len = be16toh(option->len); + + if (len > *buflen) + return -ENOMSG; + + *optcode = be16toh(option->code); + *optlen = len; + + *buf += 4; + *buflen -= 4; + + return 0; +} + +int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode, + size_t *optlen, uint8_t **optvalue) { + int r; + + assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL); + + r = option_parse_hdr(buf, buflen, optcode, optlen); + if (r < 0) + return r; + + if (*optlen > *buflen) + return -ENOBUFS; + + *optvalue = *buf; + *buflen -= *optlen; + *buf += *optlen; + + return 0; +} + +int dhcp6_option_parse_ia(uint8_t **buf, size_t *buflen, uint16_t iatype, + DHCP6IA *ia) { + int r; + uint16_t opt, status; + size_t optlen; + size_t iaaddr_offset; + DHCP6Address *addr; + uint32_t lt_t1, lt_t2, lt_valid, lt_pref, lt_min = ~0; + + assert_return(ia, -EINVAL); + assert_return(!ia->addresses, -EINVAL); + + switch (iatype) { + case SD_DHCP6_OPTION_IA_NA: + + if (*buflen < DHCP6_OPTION_IA_NA_LEN + sizeof(DHCP6Option) + + sizeof(addr->iaaddr)) { + r = -ENOBUFS; + goto error; + } + + iaaddr_offset = DHCP6_OPTION_IA_NA_LEN; + memcpy(&ia->id, *buf, iaaddr_offset); + + lt_t1 = be32toh(ia->lifetime_t1); + lt_t2 = be32toh(ia->lifetime_t2); + + if (lt_t1 && lt_t2 && lt_t1 > lt_t2) { + log_dhcp6_client(client, "IA T1 %ds > T2 %ds", + lt_t1, lt_t2); + r = -EINVAL; + goto error; + } + + break; + + case SD_DHCP6_OPTION_IA_TA: + if (*buflen < DHCP6_OPTION_IA_TA_LEN + sizeof(DHCP6Option) + + sizeof(addr->iaaddr)) { + r = -ENOBUFS; + goto error; + } + + iaaddr_offset = DHCP6_OPTION_IA_TA_LEN; + memcpy(&ia->id, *buf, iaaddr_offset); + + ia->lifetime_t1 = 0; + ia->lifetime_t2 = 0; + + break; + + default: + r = -ENOMSG; + goto error; + } + + ia->type = iatype; + + *buflen -= iaaddr_offset; + *buf += iaaddr_offset; + + while ((r = option_parse_hdr(buf, buflen, &opt, &optlen)) >= 0) { + + switch (opt) { + case SD_DHCP6_OPTION_IAADDR: + + addr = new0(DHCP6Address, 1); + if (!addr) { + r = -ENOMEM; + goto error; + } + + LIST_INIT(addresses, addr); + + memcpy(&addr->iaaddr, *buf, sizeof(addr->iaaddr)); + + lt_valid = be32toh(addr->iaaddr.lifetime_valid); + lt_pref = be32toh(addr->iaaddr.lifetime_valid); + + if (!lt_valid || lt_pref > lt_valid) { + log_dhcp6_client(client, "IA preferred %ds > valid %ds", + lt_pref, lt_valid); + free(addr); + } else { + LIST_PREPEND(addresses, ia->addresses, addr); + if (lt_valid < lt_min) + lt_min = lt_valid; + } + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: + if (optlen < sizeof(status)) + break; + + status = (*buf)[0] << 8 | (*buf)[1]; + if (status) { + log_dhcp6_client(client, "IA status %d", + status); + r = -EINVAL; + goto error; + } + + break; + + default: + log_dhcp6_client(client, "Unknown IA option %d", opt); + break; + } + + *buflen -= optlen; + *buf += optlen; + } + + if (r == -ENOMSG) + r = 0; + + if (!ia->lifetime_t1 && !ia->lifetime_t2) { + lt_t1 = lt_min / 2; + lt_t2 = lt_min / 10 * 8; + ia->lifetime_t1 = htobe32(lt_t1); + ia->lifetime_t2 = htobe32(lt_t2); + + log_dhcp6_client(client, "Computed IA T1 %ds and T2 %ds as both were zero", + lt_t1, lt_t2); + } + + if (*buflen) + r = -ENOMSG; + +error: + *buf += *buflen; + *buflen = 0; + + 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 new file mode 100644 index 0000000000..ee4bdfb07f --- /dev/null +++ b/src/libsystemd-network/dhcp6-protocol.h @@ -0,0 +1,113 @@ +#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 <netinet/ip6.h> +#include <netinet/udp.h> + +#include "macro.h" +#include "sparse-endian.h" + +struct DHCP6Message { + union { + struct { + uint8_t type; + uint8_t _pad[3]; + } _packed_; + be32_t transaction_id; + }; +} _packed_; + +typedef struct DHCP6Message DHCP6Message; + +#define DHCP6_MIN_OPTIONS_SIZE \ + 1280 - sizeof(struct ip6_hdr) - sizeof(struct udphdr) + +#define IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02 } } } + +enum { + DHCP6_PORT_SERVER = 547, + DHCP6_PORT_CLIENT = 546, +}; + +#define DHCP6_INF_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_INF_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_SOL_MAX_DELAY 1 * USEC_PER_SEC +#define DHCP6_SOL_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_SOL_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_REQ_TIMEOUT 1 * USEC_PER_SEC +#define DHCP6_REQ_MAX_RT 120 * USEC_PER_SEC +#define DHCP6_REQ_MAX_RC 10 +#define DHCP6_REN_TIMEOUT 10 * USEC_PER_SEC +#define DHCP6_REN_MAX_RT 600 * USEC_PER_SEC +#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, + DHCP6_STATE_SOLICITATION = 2, + DHCP6_STATE_REQUEST = 3, + DHCP6_STATE_BOUND = 4, + DHCP6_STATE_RENEW = 5, + DHCP6_STATE_REBIND = 6, +}; + +enum { + DHCP6_SOLICIT = 1, + DHCP6_ADVERTISE = 2, + DHCP6_REQUEST = 3, + DHCP6_CONFIRM = 4, + DHCP6_RENEW = 5, + DHCP6_REBIND = 6, + DHCP6_REPLY = 7, + DHCP6_RELEASE = 8, + DHCP6_DECLINE = 9, + DHCP6_RECONFIGURE = 10, + DHCP6_INFORMATION_REQUEST = 11, + DHCP6_RELAY_FORW = 12, + DHCP6_RELAY_REPL = 13, + _DHCP6_MESSAGE_MAX = 14, +}; + +enum { + DHCP6_NTP_SUBOPTION_SRV_ADDR = 1, + DHCP6_NTP_SUBOPTION_MC_ADDR = 2, + DHCP6_NTP_SUBOPTION_SRV_FQDN = 3, +}; + +enum { + DHCP6_STATUS_SUCCESS = 0, + DHCP6_STATUS_UNSPEC_FAIL = 1, + DHCP6_STATUS_NO_ADDRS_AVAIL = 2, + DHCP6_STATUS_NO_BINDING = 3, + DHCP6_STATUS_NOT_ON_LINK = 4, + DHCP6_STATUS_USE_MULTICAST = 5, + _DHCP6_STATUS_MAX = 6, +}; diff --git a/src/libsystemd-network/icmp6-util.c b/src/libsystemd-network/icmp6-util.c new file mode 100644 index 0000000000..acad9d7d6a --- /dev/null +++ b/src/libsystemd-network/icmp6-util.c @@ -0,0 +1,127 @@ +/*** + 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 <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; + int r, zero = 0, one = 1, 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 = setsockopt(s, SOL_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + 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/icmp6-util.h b/src/libsystemd-network/icmp6-util.h new file mode 100644 index 0000000000..2b4dbc76ce --- /dev/null +++ b/src/libsystemd-network/icmp6-util.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + This file is part of systemd. + + 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 + 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> + +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/lldp-internal.c b/src/libsystemd-network/lldp-internal.c new file mode 100644 index 0000000000..902a262f39 --- /dev/null +++ b/src/libsystemd-network/lldp-internal.c @@ -0,0 +1,360 @@ +/*** + 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 <systemd/sd-lldp.h> + +#include "alloc-util.h" +#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 + +/* 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 = sd_lldp_packet_read_port_id(tlv, &type, &data, &length); + 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 = sd_lldp_packet_read_ttl(tlv, &ttl); + if (r < 0) + return r; + + p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic()); + + sd_lldp_packet_unref(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 = sd_lldp_packet_read_port_id(tlv, &type, &data, &length); + 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 = sd_lldp_packet_read_chassis_id(tlv, &subtype, &data, &length); + if (r < 0) + goto drop; + + r = sd_lldp_packet_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 (c->n_ref >= LLDP_MIB_MAX_PORT_PER_CHASSIS) { + log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...", 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); + c->n_ref ++; + + p = NULL; + c = NULL; + + return 0; + + drop: + sd_lldp_packet_unref(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 */ + c->n_ref --; + if (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; + + sd_lldp_packet_unref(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 = sd_lldp_packet_read_port_id(tlv, &type, &data, &length); + if (r < 0) + return r; + + r = sd_lldp_packet_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 (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 = sd_lldp_packet_read_chassis_id(tlv, &type, &data, &length); + if (r < 0) + return r; + + c = new0(lldp_chassis, 1); + if (!c) + return -ENOMEM; + + c->n_ref = 1; + 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; +} + +int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_lldp_packet_unrefp) 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-internal.h b/src/libsystemd-network/lldp-internal.h new file mode 100644 index 0000000000..565149fbcf --- /dev/null +++ b/src/libsystemd-network/lldp-internal.h @@ -0,0 +1,91 @@ +/*** + 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 <systemd/sd-event.h> + +#include "list.h" +#include "lldp-tlv.h" +#include "log.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; + + unsigned prioq_idx; + + lldp_chassis *c; + tlv_packet *packet; + + LIST_FIELDS(lldp_neighbour_port, port); +}; + +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); + +DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_neighbour_port *, lldp_neighbour_port_free); +#define _cleanup_lldp_neighbour_port_free_ _cleanup_(lldp_neighbour_port_freep) + +struct lldp_chassis_id { + uint8_t type; + uint16_t length; + + uint8_t *data; +}; + +struct lldp_chassis { + unsigned 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_handle_packet(tlv_packet *m, uint16_t length); +int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata); +#define log_lldp(fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c new file mode 100644 index 0000000000..42058c4449 --- /dev/null +++ b/src/libsystemd-network/lldp-network.c @@ -0,0 +1,83 @@ +/*** + 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 <linux/filter.h> +#include <linux/if_ether.h> + +#include "fd-util.h" +#include "lldp-internal.h" +#include "lldp-network.h" +#include "lldp-tlv.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 */ + 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_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_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 = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + + _cleanup_close_ int s = -1; + + union sockaddr_union saddrll = { + .ll.sll_family = AF_PACKET, + .ll.sll_ifindex = ifindex, + }; + + int r; + + assert(ifindex > 0); + + s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL)); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(s, &saddrll.sa, sizeof(saddrll.ll)); + if (r < 0) + return -errno; + + r = s; + s = -1; + + return r; +} diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h new file mode 100644 index 0000000000..3eb03a102d --- /dev/null +++ b/src/libsystemd-network/lldp-network.h @@ -0,0 +1,25 @@ +/*** + 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 <systemd/sd-event.h> + +int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/libsystemd-network/lldp-port.c b/src/libsystemd-network/lldp-port.c new file mode 100644 index 0000000000..c86f62a6c2 --- /dev/null +++ b/src/libsystemd-network/lldp-port.c @@ -0,0 +1,116 @@ +/*** + 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 "alloc-util.h" +#include "async.h" +#include "lldp-internal.h" +#include "lldp-network.h" +#include "lldp-port.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_errno(r, "Failed to allocate event source: %m"); + goto fail; + } + + r = sd_event_source_set_priority(p->lldp_port_rx, p->event_priority); + if (r < 0) { + log_debug_errno(r, "Failed to set event priority: %m"); + goto fail; + } + + r = sd_event_source_set_description(p->lldp_port_rx, "lldp-port-rx"); + if (r < 0) { + log_debug_errno(r, "Failed to set event name: %m"); + 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 new file mode 100644 index 0000000000..36e1edc17c --- /dev/null +++ b/src/libsystemd-network/lldp-port.h @@ -0,0 +1,69 @@ +/*** + 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 <systemd/sd-event.h> +#include <systemd/sd-lldp.h> + +#include "util.h" + +typedef struct lldp_port lldp_port; + +typedef enum LLDPPortStatus { + LLDP_PORT_STATUS_NONE, + LLDP_PORT_STATUS_ENABLED, + LLDP_PORT_STATUS_DISABLED, + _LLDP_PORT_STATUS_MAX, + _LLDP_PORT_STATUS_INVALID = -1, +} LLDPPortStatus; + +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 new file mode 100644 index 0000000000..9170b50691 --- /dev/null +++ b/src/libsystemd-network/lldp-tlv.c @@ -0,0 +1,638 @@ +/*** + 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 <arpa/inet.h> +#include <net/ethernet.h> + +#include "alloc-util.h" +#include "lldp-tlv.h" +#include "macro.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); + m->n_ref = 1; + + *ret = m; + + return 0; +} + +tlv_packet *sd_lldp_packet_ref(tlv_packet *m) { + + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref++; + + return m; +} + +tlv_packet *sd_lldp_packet_unref(tlv_packet *m) { + tlv_section *s, *n; + + if (!m) + return NULL; + + assert(m->n_ref > 0); + m->n_ref--; + + if (m->n_ref > 0) + return m; + + LIST_FOREACH_SAFE(section, s, n, m->sections) + tlv_section_free(s); + + free(m); + return NULL; +} + +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->data + m->container->length - m->container->read_pos; + + m->container->read_pos += *data_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->data + m->container->length - m->container->read_pos; + + m->container->read_pos += *data_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 header */ + 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; + + if (section->type == LLDP_TYPE_PRIVATE && + section->length >= LLDP_OUI_LEN + 1) { + section->oui = p; + p += LLDP_OUI_LEN; + section->subtype = *p++; + + section->length -= LLDP_OUI_LEN + 1; + l += LLDP_OUI_LEN + 1; + } + + 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); + assert_return(type != LLDP_TYPE_PRIVATE, -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 = NULL; + return -1; + } + + return 0; +} + +int lldp_tlv_packet_enter_container_oui(tlv_packet *m, const uint8_t *oui, uint8_t subtype) { + tlv_section *s; + + assert_return(m, -EINVAL); + assert_return(oui, -EINVAL); + + LIST_FOREACH(section, s, m->sections) { + if (s->type == LLDP_TYPE_PRIVATE && + s->oui && + s->subtype == subtype && + !memcmp(s->oui, oui, LLDP_OUI_LEN)) + break; + } + + if (!s) + return -1; + + m->container = s; + + m->container->read_pos = s->data; + if (!m->container->read_pos) { + m->container = NULL; + return -1; + } + + return 0; +} + +int lldp_tlv_packet_exit_container(tlv_packet *m) { + assert_return(m, -EINVAL); + + m->container = 0; + + return 0; +} + +static int lldp_tlv_packet_read_u16_tlv(tlv_packet *tlv, uint16_t type, uint16_t *value) { + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container(tlv, type); + if (r < 0) + return r; + + r = tlv_packet_read_u16(tlv, value); + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +static int lldp_tlv_packet_read_string_tlv(tlv_packet *tlv, uint16_t type, char **data, uint16_t *length) { + char *s; + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container(tlv, type); + if (r < 0) + return r; + + r = tlv_packet_read_string(tlv, &s, length); + if (r < 0) + goto out; + + *data = (char *) s; + + out: + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_chassis_id(tlv_packet *tlv, + uint8_t *type, + uint8_t **data, + uint16_t *length) { + uint8_t subtype; + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_CHASSIS_ID); + if (r < 0) + return r; + + r = tlv_packet_read_u8(tlv, &subtype); + if (r < 0) + goto out; + + switch (subtype) { + case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + + r = tlv_packet_read_bytes(tlv, data, length); + if (r < 0) + goto out; + + break; + default: + r = -EOPNOTSUPP; + break; + } + + *type = subtype; + + out: + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_port_id(tlv_packet *tlv, + uint8_t *type, + uint8_t **data, + uint16_t *length) { + uint8_t subtype; + char *s; + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_ID); + if (r < 0) + return r; + + r = tlv_packet_read_u8(tlv, &subtype); + if (r < 0) + goto out; + + 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 out; + + *data = (uint8_t *) s; + + break; + case LLDP_PORT_SUBTYPE_MAC_ADDRESS: + + r = tlv_packet_read_bytes(tlv, data, length); + if (r < 0) + goto out; + + break; + default: + r = -EOPNOTSUPP; + break; + } + + *type = subtype; + + out: + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_ttl(tlv_packet *tlv, uint16_t *ttl) { + return lldp_tlv_packet_read_u16_tlv(tlv, LLDP_TYPE_TTL, ttl); +} + +int sd_lldp_packet_read_system_name(tlv_packet *tlv, + char **data, + uint16_t *length) { + return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_SYSTEM_NAME, data, length); +} + +int sd_lldp_packet_read_system_description(tlv_packet *tlv, + char **data, + uint16_t *length) { + return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_SYSTEM_DESCRIPTION, data, length); +} + +int sd_lldp_packet_read_port_description(tlv_packet *tlv, + char **data, + uint16_t *length) { + return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_PORT_DESCRIPTION, data, length); +} + +int sd_lldp_packet_read_system_capability(tlv_packet *tlv, uint16_t *data) { + return lldp_tlv_packet_read_u16_tlv(tlv, LLDP_TYPE_SYSTEM_CAPABILITIES, data); +} + +int sd_lldp_packet_read_port_vlan_id(tlv_packet *tlv, uint16_t *id) { + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_PORT_VLAN_ID); + if (r < 0) + return r; + + r = tlv_packet_read_u16(tlv, id); + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id) { + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_PORT_PROTOCOL_VLAN_ID); + if (r < 0) + return r; + + r = tlv_packet_read_u8(tlv, flags); + if (r >= 0) + r = tlv_packet_read_u16(tlv, id); + + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_vlan_name(tlv_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length) { + int r, r2; + uint8_t len = 0; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_VLAN_NAME); + if (r < 0) + return r; + + r = tlv_packet_read_u16(tlv, vlan_id); + if (r >= 0) + r = tlv_packet_read_u8(tlv, &len); + if (r >= 0) + r = tlv_packet_read_string(tlv, name, length); + + if (r >= 0 && len < *length) + *length = len; + + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_management_vid(tlv_packet *tlv, uint16_t *id) { + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_MANAGEMENT_VID); + if (r < 0) + return r; + + r = tlv_packet_read_u16(tlv, id); + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id) { + int r, r2; + + assert_return(tlv, -EINVAL); + + r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_LINK_AGGREGATION); + if (r < 0) + return r; + + r = tlv_packet_read_u8(tlv, status); + if (r >= 0) + r = tlv_packet_read_u32(tlv, id); + + r2 = lldp_tlv_packet_exit_container(tlv); + + return r < 0 ? r : r2; +} + +int sd_lldp_packet_get_destination_type(tlv_packet *tlv, int *dest) { + assert_return(tlv, -EINVAL); + assert_return(dest, -EINVAL); + + /* 802.1AB-2009, Table 7-1 */ + if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_BRIDGE, ETH_ALEN)) + *dest = SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE; + else if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE, ETH_ALEN)) + *dest = SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE; + else if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE, ETH_ALEN)) + *dest = SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE; + else + return -EINVAL; + + return 0; +} diff --git a/src/libsystemd-network/lldp-tlv.h b/src/libsystemd-network/lldp-tlv.h new file mode 100644 index 0000000000..4362b2ace6 --- /dev/null +++ b/src/libsystemd-network/lldp-tlv.h @@ -0,0 +1,94 @@ +/*** + 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 <systemd/sd-lldp.h> + +#include "list.h" +#include "lldp.h" +#include "util.h" + +typedef struct sd_lldp_packet tlv_packet; +typedef struct sd_lldp_section tlv_section; + +#define LLDP_OUI_LEN 3 + +struct sd_lldp_section { + uint16_t type; + uint16_t length; + uint8_t *oui; + uint8_t subtype; + + uint8_t *read_pos; + uint8_t *data; + + LIST_FIELDS(tlv_section, section); +}; + +#define LLDP_MAC_NEAREST_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e } +#define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 } +#define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 } + +int tlv_section_new(tlv_section **ret); +void tlv_section_free(tlv_section *ret); + +struct sd_lldp_packet { + unsigned n_ref; + + 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); + +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_enter_container_oui(tlv_packet *m, const uint8_t *oui, uint8_t subtype); +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 new file mode 100644 index 0000000000..d2c7164633 --- /dev/null +++ b/src/libsystemd-network/lldp.h @@ -0,0 +1,126 @@ +/*** + 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; + +#define LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 } +#define LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f } + +enum { + LLDP_OUI_SUBTYPE_802_1_PORT_VLAN_ID = 1, + LLDP_OUI_SUBTYPE_802_1_PORT_PROTOCOL_VLAN_ID = 2, + LLDP_OUI_SUBTYPE_802_1_VLAN_NAME = 3, + LLDP_OUI_SUBTYPE_802_1_PROTOCOL_IDENTITY = 4, + LLDP_OUI_SUBTYPE_802_1_VID_USAGE_DIGEST = 5, + LLDP_OUI_SUBTYPE_802_1_MANAGEMENT_VID = 6, + LLDP_OUI_SUBTYPE_802_1_LINK_AGGREGATION = 7, +}; diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c new file mode 100644 index 0000000000..35860cf11a --- /dev/null +++ b/src/libsystemd-network/network-internal.c @@ -0,0 +1,557 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 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 <arpa/inet.h> +#include <linux/if.h> +#include <netinet/ether.h> + +#include <systemd/sd-ndisc.h> + +#include "alloc-util.h" +#include "condition.h" +#include "conf-parser.h" +#include "dhcp-lease-internal.h" +#include "hexdecoct.h" +#include "log.h" +#include "network-internal.h" +#include "parse-util.h" +#include "siphash24.h" +#include "string-util.h" +#include "strv.h" +#include "utf8.h" +#include "util.h" + +const char *net_get_name(struct udev_device *device) { + const char *name, *field; + + assert(device); + + /* fetch some persistent data unique (on this machine) to this device */ + FOREACH_STRING(field, "ID_NET_NAME_ONBOARD", "ID_NET_NAME_SLOT", "ID_NET_NAME_PATH", "ID_NET_NAME_MAC") { + name = udev_device_get_property_value(device, field); + if (name) + return name; + } + + return NULL; +} + +#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, uint64_t *result) { + size_t l, sz = 0; + const char *name = NULL; + int r; + uint8_t *v; + + assert(device); + + name = net_get_name(device); + if (!name) + return -ENOENT; + + l = strlen(name); + sz = sizeof(sd_id128_t) + l; + v = alloca(sz); + + /* fetch some persistent data unique to this machine */ + r = sd_id128_get_machine((sd_id128_t*) v); + if (r < 0) + return r; + memcpy(v + sizeof(sd_id128_t), name, l); + + /* Let's hash the machine ID plus the device name. We + * use a fixed, but originally randomly created hash + * key here. */ + *result = htole64(siphash24(v, sz, HASH_KEY.bytes)); + + return 0; +} + +bool net_match_config(const struct ether_addr *match_mac, + char * const *match_paths, + char * const *match_drivers, + char * const *match_types, + char * const *match_names, + Condition *match_host, + Condition *match_virt, + Condition *match_kernel, + Condition *match_arch, + const struct ether_addr *dev_mac, + const char *dev_path, + const char *dev_parent_driver, + const char *dev_driver, + const char *dev_type, + const char *dev_name) { + + if (match_host && !condition_test(match_host)) + return false; + + if (match_virt && !condition_test(match_virt)) + return false; + + if (match_kernel && !condition_test(match_kernel)) + return false; + + if (match_arch && !condition_test(match_arch)) + return false; + + if (match_mac && (!dev_mac || memcmp(match_mac, dev_mac, ETH_ALEN))) + return false; + + if (!strv_isempty(match_paths) && + (!dev_path || !strv_fnmatch(match_paths, dev_path, 0))) + return false; + + if (!strv_isempty(match_drivers) && + (!dev_driver || !strv_fnmatch(match_drivers, dev_driver, 0))) + return false; + + if (!strv_isempty(match_types) && + (!dev_type || !strv_fnmatch_or_empty(match_types, dev_type, 0))) + return false; + + if (!strv_isempty(match_names) && + (!dev_name || !strv_fnmatch_or_empty(match_names, dev_name, 0))) + return false; + + return true; +} + +int config_parse_net_condition(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) { + + ConditionType cond = ltype; + Condition **ret = data; + bool negate; + Condition *c; + _cleanup_free_ char *s = NULL; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + negate = rvalue[0] == '!'; + if (negate) + rvalue++; + + s = strdup(rvalue); + if (!s) + return log_oom(); + + c = condition_new(cond, s, false, negate); + if (!c) + return log_oom(); + + if (*ret) + condition_free(*ret); + + *ret = c; + 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, 0, "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) { + + char ***sv = data; + const char *word, *state; + size_t l; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + FOREACH_WORD(word, l, rvalue, state) { + char *n; + + n = strndup(word, l); + if (!n) + return log_oom(); + + if (!ascii_is_valid(n) || strlen(n) >= IFNAMSIZ) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not ASCII clean or is too long, ignoring assignment: %s", rvalue); + free(n); + return 0; + } + + r = strv_consume(sv, n); + if (r < 0) + return log_oom(); + } + + return 0; +} + +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) { + + 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) >= IFALIASZ) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface alias 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_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) { + struct ether_addr **hwaddr = data; + struct ether_addr *n; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + n = new0(struct ether_addr, 1); + 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, 0, "Not a valid MAC address, ignoring assignment: %s", rvalue); + free(n); + return 0; + } + + free(*hwaddr); + *hwaddr = n; + + return 0; +} + +void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { + unsigned i; + + assert(f); + assert(addresses); + assert(size); + + for (i = 0; i < size; i++) + fprintf(f, "%s%s", inet_ntoa(addresses[i]), + (i < (size - 1)) ? " ": ""); +} + +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; + struct in_addr *new_addresses; + int r; + + 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])); + if (r <= 0) + continue; + + size ++; + } + + *ret = addresses; + addresses = NULL; + + 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++) + fprintf(f, SD_NDISC_ADDRESS_FORMAT_STR"%s", + SD_NDISC_ADDRESS_FORMAT_VAL(addresses[i]), + (i < (size - 1)) ? " ": ""); +} + +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; + struct in6_addr *new_addresses; + int r; + + 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])); + if (r <= 0) + continue; + + size++; + } + + *ret = addresses; + addresses = NULL; + + return size; +} + +void serialize_dhcp_routes(FILE *f, const char *key, sd_dhcp_route **routes, size_t size) { + unsigned i; + + assert(f); + assert(key); + assert(routes); + assert(size); + + fprintf(f, "%s=", key); + + for (i = 0; i < size; i++) { + 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); +} + +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; + char *tok, *tok_end; + unsigned n; + int r; + + if (!GREEDY_REALLOC(routes, allocated, size + 1)) + return -ENOMEM; + + entry = strndup(word, len); + if(!entry) + return -ENOMEM; + + tok = entry; + + /* get the subnet */ + tok_end = strchr(tok, '/'); + if (!tok_end) + continue; + *tok_end = '\0'; + + r = inet_aton(tok, &routes[size].dst_addr); + if (r == 0) + continue; + + tok = tok_end + 1; + + /* get the prefixlen */ + tok_end = strchr(tok, ','); + if (!tok_end) + continue; + + *tok_end = '\0'; + + r = safe_atou(tok, &n); + if (r < 0 || n > 32) + continue; + + routes[size].dst_prefixlen = (uint8_t) n; + tok = tok_end + 1; + + /* get the gateway */ + r = inet_aton(tok, &routes[size].gw_addr); + if (r == 0) + continue; + + size++; + } + + *ret_size = size; + *ret_allocated = allocated; + *ret = routes; + routes = NULL; + + 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 new file mode 100644 index 0000000000..2549d1420e --- /dev/null +++ b/src/libsystemd-network/network-internal.h @@ -0,0 +1,81 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 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 <stdbool.h> + +#include <systemd/sd-dhcp-lease.h> + +#include "condition.h" +#include "udev.h" + +bool net_match_config(const struct ether_addr *match_mac, + char * const *match_path, + char * const *match_driver, + char * const *match_type, + char * const *match_name, + Condition *match_host, + Condition *match_virt, + Condition *match_kernel, + Condition *match_arch, + const struct ether_addr *dev_mac, + const char *dev_path, + const char *dev_parent_driver, + const char *dev_driver, + const char *dev_type, + const char *dev_name); + +int config_parse_net_condition(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_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); + +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, 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, 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 new file mode 100644 index 0000000000..bd5ec76fc5 --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-client.c @@ -0,0 +1,1795 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 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 <net/ethernet.h> +#include <net/if_arp.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <linux/if_infiniband.h> + +#include <systemd/sd-dhcp-client.h> + +#include "alloc-util.h" +#include "async.h" +#include "dhcp-identifier.h" +#include "dhcp-internal.h" +#include "dhcp-lease-internal.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 { + unsigned n_ref; + + DHCPState state; + sd_event *event; + int event_priority; + sd_event_source *timeout_resend; + int index; + int fd; + union sockaddr_union link; + sd_event_source *receive_message; + bool request_broadcast; + uint8_t *req_opts; + size_t req_opts_allocated; + size_t req_opts_size; + be32_t last_addr; + uint8_t mac_addr[MAX_MAC_ADDR_LEN]; + size_t mac_addr_len; + uint16_t arp_type; + struct { + uint8_t type; + union { + struct { + /* 0: Generic (non-LL) (RFC 2132) */ + uint8_t data[MAX_CLIENT_ID_LEN]; + } _packed_ gen; + struct { + /* 1: Ethernet Link-Layer (RFC 2132) */ + uint8_t haddr[ETH_ALEN]; + } _packed_ eth; + struct { + /* 2 - 254: ARP/Link-Layer (RFC 2132) */ + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* 255: Node-specific (RFC 4361) */ + uint32_t iaid; + struct duid duid; + } _packed_ ns; + struct { + uint8_t data[MAX_CLIENT_ID_LEN]; + } _packed_ raw; + }; + } _packed_ client_id; + size_t client_id_len; + char *hostname; + char *vendor_class_identifier; + uint32_t mtu; + uint32_t xid; + usec_t start_time; + unsigned int attempt; + usec_t request_sent; + sd_event_source *timeout_t1; + sd_event_source *timeout_t2; + sd_event_source *timeout_expire; + sd_dhcp_client_cb_t cb; + void *userdata; + sd_dhcp_lease *lease; + usec_t start_delay; +}; + +static const uint8_t default_req_opts[] = { + 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 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) { + assert_return(client, -EINVAL); + + client->cb = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp_client_set_request_broadcast(sd_dhcp_client *client, int broadcast) { + assert_return(client, -EINVAL); + + client->request_broadcast = !!broadcast; + + return 0; +} + +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); + + switch(option) { + 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: + break; + } + + for (i = 0; i < client->req_opts_size; i++) + if (client->req_opts[i] == option) + return -EEXIST; + + if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated, + client->req_opts_size + 1)) + return -ENOMEM; + + client->req_opts[client->req_opts_size++] = option; + + return 0; +} + +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); + + if (last_addr) + client->last_addr = last_addr->s_addr; + else + client->last_addr = INADDR_ANY; + + 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); + + client->index = interface_index; + + return 0; +} + +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; + + 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); + + if (arp_type == ARPHRD_ETHER) + assert_return(addr_len == ETH_ALEN, -EINVAL); + else if (arp_type == ARPHRD_INFINIBAND) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + if (client->mac_addr_len == addr_len && + memcmp(&client->mac_addr, addr, addr_len) == 0) + 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"); + need_restart = true; + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + } + + memcpy(&client->mac_addr, addr, addr_len); + client->mac_addr_len = addr_len; + client->arp_type = arp_type; + + if (need_restart && client->state != DHCP_STATE_STOPPED) + sd_dhcp_client_start(client); + + return 0; +} + +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); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + *type = 0; + *data = NULL; + *data_len = 0; + if (client->client_id_len) { + *type = client->client_id.type; + *data = client->client_id.raw.data; + *data_len = client->client_id_len - sizeof(client->client_id.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) { + DHCP_CLIENT_DONT_DESTROY(client); + bool need_restart = false; + + assert_return(client, -EINVAL); + assert_return(data, -EINVAL); + 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; + } + + if (client->client_id_len == data_len + sizeof(client->client_id.type) && + client->client_id.type == type && + memcmp(&client->client_id.raw.data, data, data_len) == 0) + return 0; + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Changing client ID on running DHCP " + "client, restarting"); + need_restart = true; + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + } + + client->client_id.type = type; + memcpy(&client->client_id.raw.data, data, data_len); + client->client_id_len = data_len + sizeof (client->client_id.type); + + if (need_restart && client->state != DHCP_STATE_STOPPED) + 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; + + if (hostname) { + new_hostname = strdup(hostname); + if (!new_hostname) + return -ENOMEM; + } + + free(client->hostname); + client->hostname = new_hostname; + + return 0; +} + +int sd_dhcp_client_set_vendor_class_identifier(sd_dhcp_client *client, + const char *vci) { + char *new_vci = NULL; + + assert_return(client, -EINVAL); + + new_vci = strdup(vci); + if (!new_vci) + return -ENOMEM; + + free(client->vendor_class_identifier); + + client->vendor_class_identifier = new_vci; + + return 0; +} + +int sd_dhcp_client_set_mtu(sd_dhcp_client *client, uint32_t mtu) { + assert_return(client, -EINVAL); + assert_return(mtu >= DHCP_DEFAULT_MIN_SIZE, -ERANGE); + + client->mtu = mtu; + + return 0; +} + +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 = client->lease; + + return 0; +} + +static void client_notify(sd_dhcp_client *client, int event) { + if (client->cb) + client->cb(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->fd = asynchronous_close(client->fd); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + + client->timeout_t1 = sd_event_source_unref(client->timeout_t1); + client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + client->timeout_expire = sd_event_source_unref(client->timeout_expire); + + client->attempt = 1; + + client->state = DHCP_STATE_INIT; + client->xid = 0; + + client->lease = sd_dhcp_lease_unref(client->lease); + + return 0; +} + +static void client_stop(sd_dhcp_client *client, int error) { + assert(client); + + if (error < 0) + log_dhcp_client(client, "STOPPED: %s", strerror(-error)); + else if (error == SD_DHCP_CLIENT_EVENT_STOP) + log_dhcp_client(client, "STOPPED"); + else + log_dhcp_client(client, "STOPPED: Unknown event"); + + client_notify(client, 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; + size_t optlen, optoffset, size; + be16_t max_size; + usec_t time_now; + uint16_t secs; + int r; + + assert(client); + assert(client->start_time); + assert(ret); + assert(_optlen); + assert(_optoffset); + assert(type == DHCP_DISCOVER || type == DHCP_REQUEST); + + optlen = DHCP_MIN_OPTIONS_SIZE; + size = sizeof(DHCPPacket) + optlen; + + packet = malloc0(size); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREQUEST, client->xid, type, + client->arp_type, optlen, &optoffset); + if (r < 0) + return r; + + /* Although 'secs' field is a SHOULD in RFC 2131, certain DHCP servers + refuse to issue an DHCP lease if 'secs' is set to zero */ + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + assert(time_now >= client->start_time); + + /* seconds between sending first and last DISCOVER + * must always be strictly positive to deal with broken servers */ + secs = ((time_now - client->start_time) / USEC_PER_SEC) ? : 1; + packet->dhcp.secs = htobe16(secs); + + /* RFC2132 section 4.1 + A client that cannot receive unicast IP datagrams until its protocol + software has been configured with an IP address SHOULD set the + BROADCAST bit in the 'flags' field to 1 in any DHCPDISCOVER or + DHCPREQUEST messages that client sends. The BROADCAST bit will + provide a hint to the DHCP server and BOOTP relay agent to broadcast + any messages to the client on the client's subnet. + + Note: some interfaces needs this to be enabled, but some networks + needs this to be disabled as broadcasts are filteretd, so this + needs to be configurable */ + if (client->request_broadcast || client->arp_type != ARPHRD_ETHER) + packet->dhcp.flags = htobe16(0x8000); + + /* RFC2132 section 4.1.1: + The client MUST include its hardware address in the ’chaddr’ field, if + necessary for delivery of DHCP reply messages. Non-Ethernet + interfaces will leave 'chaddr' empty and use the client identifier + instead (eg, RFC 4390 section 2.1). + */ + if (client->arp_type == ARPHRD_ETHER) + memcpy(&packet->dhcp.chaddr, &client->mac_addr, ETH_ALEN); + + /* If no client identifier exists, construct an RFC 4361-compliant one */ + if (client->client_id_len == 0) { + size_t duid_len; + + client->client_id.type = 255; + + r = dhcp_identifier_set_iaid(client->index, client->mac_addr, client->mac_addr_len, &client->client_id.ns.iaid); + if (r < 0) + return r; + + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &duid_len); + if (r < 0) + return r; + + client->client_id_len = sizeof(client->client_id.type) + sizeof(client->client_id.ns.iaid) + duid_len; + } + + /* Some DHCP servers will refuse to issue an DHCP lease if the Client + Identifier option is not set */ + if (client->client_id_len) { + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_CLIENT_IDENTIFIER, + client->client_id_len, + &client->client_id); + if (r < 0) + return r; + } + + /* RFC2131 section 3.5: + in its initial DHCPDISCOVER or DHCPREQUEST message, a + client may provide the server with a list of specific + parameters the client is interested in. If the client + includes a list of parameters in a DHCPDISCOVER message, + it MUST include that list in any subsequent DHCPREQUEST + messages. + */ + r = dhcp_option_append(&packet->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST, + client->req_opts_size, client->req_opts); + if (r < 0) + return r; + + /* RFC2131 section 3.5: + The client SHOULD include the ’maximum DHCP message size’ option to + let the server know how large the server may make its DHCP messages. + + Note (from ConnMan): Some DHCP servers will send bigger DHCP packets + than the defined default size unless the Maximum Messge Size option + is explicitly set + + RFC3442 "Requirements to Avoid Sizing Constraints": + Because a full routing table can be quite large, the standard 576 + octet maximum size for a DHCP message may be too short to contain + some legitimate Classless Static Route options. Because of this, + clients implementing the Classless Static Route option SHOULD send a + Maximum DHCP Message Size [4] option if the DHCP client's TCP/IP + stack is capable of receiving larger IP datagrams. In this case, the + client SHOULD set the value of this option to at least the MTU of the + interface that the client is configuring. The client MAY set the + value of this option higher, up to the size of the largest UDP packet + it is prepared to accept. (Note that the value specified in the + Maximum DHCP Message Size option is the total maximum packet size, + including IP and UDP headers.) + */ + max_size = htobe16(size); + r = dhcp_option_append(&packet->dhcp, client->mtu, &optoffset, 0, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE, + 2, &max_size); + if (r < 0) + return r; + + *_optlen = optlen; + *_optoffset = optoffset; + *ret = packet; + packet = NULL; + + return 0; +} + +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); + + return dhcp_network_send_raw_socket(client->fd, &client->link, + packet, len); +} + +static int client_send_discover(sd_dhcp_client *client) { + _cleanup_free_ DHCPPacket *discover = NULL; + size_t optoffset, optlen; + int r; + + assert(client); + assert(client->state == DHCP_STATE_INIT || + client->state == DHCP_STATE_SELECTING); + + r = client_message_init(client, &discover, DHCP_DISCOVER, + &optlen, &optoffset); + if (r < 0) + return r; + + /* the client may suggest values for the network address + and lease time in the DHCPDISCOVER message. The client may include + the ’requested IP address’ option to suggest that a particular IP + address be assigned, and may include the ’IP address lease time’ + option to suggest the lease time it would like. + */ + if (client->last_addr != INADDR_ANY) { + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); + if (r < 0) + return r; + } + + if (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, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER, + strlen(client->vendor_class_identifier), + client->vendor_class_identifier); + if (r < 0) + return r; + } + + r = dhcp_option_append(&discover->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + /* We currently ignore: + The client SHOULD wait a random time between one and ten seconds to + desynchronize the use of DHCP at startup. + */ + r = dhcp_client_send_raw(client, discover, sizeof(DHCPPacket) + optoffset); + if (r < 0) + return r; + + log_dhcp_client(client, "DISCOVER"); + + return 0; +} + +static int client_send_request(sd_dhcp_client *client) { + _cleanup_free_ DHCPPacket *request = NULL; + size_t optoffset, optlen; + int r; + + r = client_message_init(client, &request, DHCP_REQUEST, + &optlen, &optoffset); + if (r < 0) + return r; + + switch (client->state) { + /* See RFC2131 section 4.3.2 (note that there is a typo in the RFC, + SELECTING should be REQUESTING) + */ + + case DHCP_STATE_REQUESTING: + /* Client inserts the address of the selected server in ’server + identifier’, ’ciaddr’ MUST be zero, ’requested IP address’ MUST be + filled in with the yiaddr value from the chosen DHCPOFFER. + */ + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &client->lease->server_address); + if (r < 0) + return r; + + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->lease->address); + if (r < 0) + return r; + + break; + + case DHCP_STATE_INIT_REBOOT: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST be filled in with client’s notion of its previously + assigned address. ’ciaddr’ MUST be zero. + */ + r = dhcp_option_append(&request->dhcp, optlen, &optoffset, 0, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS, + 4, &client->last_addr); + if (r < 0) + return r; + break; + + case DHCP_STATE_RENEWING: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST NOT be filled in, ’ciaddr’ MUST be filled in with + client’s IP address. + */ + + /* fall through */ + case DHCP_STATE_REBINDING: + /* ’server identifier’ MUST NOT be filled in, ’requested IP address’ + option MUST NOT be filled in, ’ciaddr’ MUST be filled in with + client’s IP address. + + This message MUST be broadcast to the 0xffffffff IP broadcast address. + */ + request->dhcp.ciaddr = client->lease->address; + + break; + + case DHCP_STATE_INIT: + case DHCP_STATE_SELECTING: + case DHCP_STATE_REBOOTING: + case DHCP_STATE_BOUND: + case DHCP_STATE_STOPPED: + return -EINVAL; + } + + if (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, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + if (client->state == DHCP_STATE_RENEWING) { + r = dhcp_network_send_udp_socket(client->fd, + client->lease->server_address, + DHCP_PORT_SERVER, + &request->dhcp, + sizeof(DHCPMessage) + optoffset); + } else { + r = dhcp_client_send_raw(client, request, sizeof(DHCPPacket) + optoffset); + } + if (r < 0) + 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; + } + + return 0; +} + +static int client_start(sd_dhcp_client *client); + +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; + uint64_t time_now; + uint32_t time_left; + int r; + + assert(s); + assert(client); + assert(client->event); + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto error; + + switch (client->state) { + case DHCP_STATE_RENEWING: + + time_left = (client->lease->t2 - client->lease->t1) / 2; + if (time_left < 60) + time_left = 60; + + next_timeout = time_now + time_left * USEC_PER_SEC; + + break; + + case DHCP_STATE_REBINDING: + + time_left = (client->lease->lifetime - client->lease->t2) / 2; + if (time_left < 60) + time_left = 60; + + next_timeout = time_now + time_left * USEC_PER_SEC; + break; + + case DHCP_STATE_REBOOTING: + /* start over as we did not receive a timely ack or nak */ + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start(client); + if (r < 0) + goto error; + else { + log_dhcp_client(client, "REBOOTED"); + return 0; + } + + case DHCP_STATE_INIT: + case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_SELECTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_BOUND: + + if (client->attempt < 64) + client->attempt *= 2; + + next_timeout = time_now + (client->attempt - 1) * USEC_PER_SEC; + + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + + next_timeout += (random_u32() & 0x1fffff); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + + r = sd_event_add_time(client->event, + &client->timeout_resend, + clock_boottime_or_monotonic(), + next_timeout, 10 * USEC_PER_MSEC, + client_timeout_resend, client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->timeout_resend, "dhcp4-resend-timer"); + if (r < 0) + goto error; + + switch (client->state) { + case DHCP_STATE_INIT: + r = client_send_discover(client); + if (r >= 0) { + client->state = DHCP_STATE_SELECTING; + client->attempt = 1; + } else { + if (client->attempt >= 64) + goto error; + } + + break; + + case DHCP_STATE_SELECTING: + r = client_send_discover(client); + if (r < 0 && client->attempt >= 64) + goto error; + + break; + + case DHCP_STATE_INIT_REBOOT: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + r = client_send_request(client); + if (r < 0 && client->attempt >= 64) + goto error; + + if (client->state == DHCP_STATE_INIT_REBOOT) + client->state = DHCP_STATE_REBOOTING; + + client->request_sent = time_now; + + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_BOUND: + + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + + return 0; + +error: + client_stop(client, r); + + /* Errors were dealt with when stopping the client, don't spill + errors into the event loop handler */ + return 0; +} + +static int client_initialize_io_events(sd_dhcp_client *client, + sd_event_io_handler_t io_callback) { + int r; + + assert(client); + assert(client->event); + + r = sd_event_add_io(client->event, &client->receive_message, + client->fd, EPOLLIN, io_callback, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->receive_message, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->receive_message, "dhcp4-receive-message"); + if (r < 0) + goto error; + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_initialize_time_events(sd_dhcp_client *client) { + uint64_t usec = 0; + int r; + + assert(client); + assert(client->event); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + + if (client->start_delay) { + sd_event_now(client->event, clock_boottime_or_monotonic(), &usec); + usec += client->start_delay; + } + + r = sd_event_add_time(client->event, + &client->timeout_resend, + clock_boottime_or_monotonic(), + usec, 0, + client_timeout_resend, client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->timeout_resend, "dhcp4-resend-timer"); + if (r < 0) + goto error; + +error: + if (r < 0) + client_stop(client, r); + + return 0; + +} + +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_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->fd < 0, -EBUSY); + assert_return(client->xid == 0, -EINVAL); + assert_return(client->state == DHCP_STATE_INIT || + client->state == DHCP_STATE_INIT_REBOOT, -EBUSY); + + client->xid = random_u32(); + + r = dhcp_network_bind_raw_socket(client->index, &client->link, + client->xid, client->mac_addr, + client->mac_addr_len, client->arp_type); + if (r < 0) { + client_stop(client, r); + return r; + } + client->fd = r; + + if (client->state == DHCP_STATE_INIT || client->state == DHCP_STATE_INIT_REBOOT) + client->start_time = now(clock_boottime_or_monotonic()); + + return client_initialize_events(client, client_receive_message_raw); +} + +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, SD_DHCP_CLIENT_EVENT_EXPIRED); + + /* lease was lost, start over if not freed or stopped in callback */ + if (client->state != DHCP_STATE_STOPPED) { + client_initialize(client); + client_start(client); + } + + return 0; +} + +static int client_timeout_t2(sd_event_source *s, uint64_t usec, void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + int r; + + 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, + client->xid, client->mac_addr, + client->mac_addr_len, client->arp_type); + if (r < 0) { + client_stop(client, r); + return 0; + } + client->fd = r; + + return client_initialize_events(client, client_receive_message_raw); +} + +static int client_timeout_t1(sd_event_source *s, uint64_t usec, + void *userdata) { + sd_dhcp_client *client = userdata; + DHCP_CLIENT_DONT_DESTROY(client); + + client->state = DHCP_STATE_RENEWING; + client->attempt = 1; + + return client_initialize_time_events(client); +} + +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); + if (r < 0) + return r; + + if (client->client_id_len) { + r = dhcp_lease_set_client_id(lease, + (uint8_t *) &client->client_id, + client->client_id_len); + if (r < 0) + return r; + } + + 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 == 0 || + lease->server_address == 0 || + lease->lifetime == 0) { + log_dhcp_client(client, "received lease lacks address, server address or lease lifetime, ignoring"); + return -ENOMSG; + } + + if (!lease->have_subnet_mask) { + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) { + log_dhcp_client(client, "received lease lacks subnet " + "mask, and a fallback one can not be " + "generated, ignoring"); + return -ENOMSG; + } + } + + sd_dhcp_lease_unref(client->lease); + client->lease = lease; + lease = NULL; + + log_dhcp_client(client, "OFFER"); + + return 0; +} + +static int client_handle_forcerenew(sd_dhcp_client *client, DHCPMessage *force, + size_t len) { + int r; + + r = dhcp_option_parse(force, len, NULL, NULL, NULL); + if (r != DHCP_FORCERENEW) + return -ENOMSG; + + log_dhcp_client(client, "FORCERENEW"); + + return 0; +} + +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); + if (r < 0) + return r; + + if (client->client_id_len) { + r = dhcp_lease_set_client_id(lease, + (uint8_t *) &client->client_id, + client->client_id_len); + if (r < 0) + return r; + } + + r = dhcp_option_parse(ack, len, dhcp_lease_parse_options, lease, &error_message); + if (r == DHCP_NAK) { + log_dhcp_client(client, "NAK: %s", strna(error_message)); + return -EADDRNOTAVAIL; + } + + if (r != DHCP_ACK) { + log_dhcp_client(client, "received message was not an ACK, ignoring"); + return -ENOMSG; + } + + lease->next_server = ack->siaddr; + + lease->address = ack->yiaddr; + + if (lease->address == INADDR_ANY || + lease->server_address == INADDR_ANY || + lease->lifetime == 0) { + log_dhcp_client(client, "received lease lacks address, server " + "address or lease lifetime, ignoring"); + return -ENOMSG; + } + + if (lease->subnet_mask == INADDR_ANY) { + r = dhcp_lease_set_default_subnet_mask(lease); + if (r < 0) { + log_dhcp_client(client, "received lease lacks subnet " + "mask, and a fallback one can not be " + "generated, ignoring"); + return -ENOMSG; + } + } + + 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 = SD_DHCP_CLIENT_EVENT_IP_CHANGE; + } else + r = SD_DHCP_CLIENT_EVENT_RENEW; + + client->lease = sd_dhcp_lease_unref(client->lease); + } + + client->lease = lease; + lease = NULL; + + log_dhcp_client(client, "ACK"); + + return r; +} + +static uint64_t client_compute_timeout(sd_dhcp_client *client, uint32_t lifetime, double factor) { + assert(client); + assert(client->request_sent); + assert(lifetime > 0); + + if (lifetime > 3) + lifetime -= 3; + else + lifetime = 0; + + return client->request_sent + (lifetime * USEC_PER_SEC * factor) + + + (random_u32() & 0x1fffff); +} + +static int client_set_lease_timeouts(sd_dhcp_client *client) { + usec_t time_now; + uint64_t lifetime_timeout; + uint64_t t2_timeout; + uint64_t t1_timeout; + char time_string[FORMAT_TIMESPAN_MAX]; + int r; + + assert(client); + assert(client->event); + assert(client->lease); + assert(client->lease->lifetime); + + client->timeout_t1 = sd_event_source_unref(client->timeout_t1); + client->timeout_t2 = sd_event_source_unref(client->timeout_t2); + client->timeout_expire = sd_event_source_unref(client->timeout_expire); + + /* don't set timers for infinite leases */ + if (client->lease->lifetime == 0xffffffff) + return 0; + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + assert(client->request_sent <= time_now); + + /* convert the various timeouts from relative (secs) to absolute (usecs) */ + lifetime_timeout = client_compute_timeout(client, client->lease->lifetime, 1); + 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) { + /* they are both valid */ + t2_timeout = client_compute_timeout(client, client->lease->t2, 1); + t1_timeout = client_compute_timeout(client, client->lease->t1, 1); + } else { + /* discard both */ + t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0); + client->lease->t2 = (client->lease->lifetime * 7) / 8; + t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5); + client->lease->t1 = client->lease->lifetime / 2; + } + } 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); + client->lease->t1 = client->lease->lifetime / 2; + if (t2_timeout <= t1_timeout) { + /* the computed T1 would be invalid, so discard T2 */ + 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 > 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); + client->lease->t2 = (client->lease->lifetime * 7) / 8; + if (t2_timeout <= t1_timeout) { + /* the computed T2 would be invalid, so discard T1 */ + t2_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5); + client->lease->t2 = client->lease->lifetime / 2; + } + } else { + /* fall back to the default timeouts */ + t1_timeout = client_compute_timeout(client, client->lease->lifetime, 0.5); + client->lease->t1 = client->lease->lifetime / 2; + t2_timeout = client_compute_timeout(client, client->lease->lifetime, 7.0 / 8.0); + client->lease->t2 = (client->lease->lifetime * 7) / 8; + } + + /* arm lifetime timeout */ + r = sd_event_add_time(client->event, &client->timeout_expire, + clock_boottime_or_monotonic(), + lifetime_timeout, 10 * USEC_PER_MSEC, + client_timeout_expire, client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->timeout_expire, + client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(client->timeout_expire, "dhcp4-lifetime"); + if (r < 0) + return r; + + log_dhcp_client(client, "lease expires in %s", + 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) + return 0; + + /* arm T2 timeout */ + r = sd_event_add_time(client->event, + &client->timeout_t2, + clock_boottime_or_monotonic(), + t2_timeout, + 10 * USEC_PER_MSEC, + client_timeout_t2, client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->timeout_t2, + client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(client->timeout_t2, "dhcp4-t2-timeout"); + if (r < 0) + return r; + + log_dhcp_client(client, "T2 expires in %s", + 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) + return 0; + + /* arm T1 timeout */ + r = sd_event_add_time(client->event, + &client->timeout_t1, + clock_boottime_or_monotonic(), + t1_timeout, 10 * USEC_PER_MSEC, + client_timeout_t1, client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->timeout_t1, + client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(client->timeout_t1, "dhcp4-t1-timer"); + if (r < 0) + return r; + + log_dhcp_client(client, "T1 expires in %s", + 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) { + DHCP_CLIENT_DONT_DESTROY(client); + char time_string[FORMAT_TIMESPAN_MAX]; + int r = 0, notify_event = 0; + + assert(client); + assert(client->event); + assert(message); + + switch (client->state) { + case DHCP_STATE_SELECTING: + + r = client_handle_offer(client, message, len); + if (r >= 0) { + + client->timeout_resend = + sd_event_source_unref(client->timeout_resend); + + client->state = DHCP_STATE_REQUESTING; + client->attempt = 1; + + r = sd_event_add_time(client->event, + &client->timeout_resend, + clock_boottime_or_monotonic(), + 0, 0, + client_timeout_resend, client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->timeout_resend, "dhcp4-resend-timer"); + if (r < 0) + goto error; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_REBOOTING: + case DHCP_STATE_REQUESTING: + case DHCP_STATE_RENEWING: + case DHCP_STATE_REBINDING: + + 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 = + sd_event_source_unref(client->receive_message); + client->fd = asynchronous_close(client->fd); + + if (IN_SET(client->state, DHCP_STATE_REQUESTING, + DHCP_STATE_REBOOTING)) + 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; + client->attempt = 1; + + client->last_addr = client->lease->address; + + r = client_set_lease_timeouts(client); + if (r < 0) { + log_dhcp_client(client, "could not set lease timeouts"); + goto error; + } + + r = dhcp_network_bind_udp_socket(client->lease->address, + DHCP_PORT_CLIENT); + if (r < 0) { + log_dhcp_client(client, "could not bind UDP socket"); + goto error; + } + + client->fd = r; + + client_initialize_io_events(client, client_receive_message_udp); + + if (notify_event) { + client_notify(client, notify_event); + if (client->state == DHCP_STATE_STOPPED) + return 0; + } + + } else if (r == -EADDRNOTAVAIL) { + /* got a NAK, let's restart the client */ + client->timeout_resend = + sd_event_source_unref(client->timeout_resend); + + r = client_initialize(client); + if (r < 0) + goto error; + + r = client_start_delayed(client); + if (r < 0) + goto error; + + 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) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_BOUND: + r = client_handle_forcerenew(client, message, len); + if (r >= 0) { + r = client_timeout_t1(NULL, 0, client); + if (r < 0) + goto error; + } else if (r == -ENOMSG) + /* invalid message, let's ignore it */ + return 0; + + break; + + case DHCP_STATE_INIT: + case DHCP_STATE_INIT_REBOOT: + + break; + + case DHCP_STATE_STOPPED: + r = -EINVAL; + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return r; +} + +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; + const struct ether_addr zero_mac = { { 0, 0, 0, 0, 0, 0 } }; + const struct ether_addr *expected_chaddr = NULL; + uint8_t expected_hlen = 0; + ssize_t len, buflen; + + assert(s); + assert(client); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return buflen; + + message = malloc0(buflen); + if (!message) + return -ENOMEM; + + len = read(fd, message, buflen); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_dhcp_client(client, "Could not receive message from UDP socket: %m"); + return -errno; + } else 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"); + return 0; + } + + if (message->op != BOOTREPLY) { + 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"); + return 0; + } + + if (client->arp_type == ARPHRD_ETHER) { + expected_hlen = ETH_ALEN; + expected_chaddr = (const struct ether_addr *) &client->mac_addr; + } else { + /* 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); + return 0; + } + + if (memcmp(&message->chaddr[0], expected_chaddr, ETH_ALEN)) { + log_dhcp_client(client, "Received chaddr does not match expected: ignoring"); + return 0; + } + + if (client->state != DHCP_STATE_BOUND && + 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", + be32toh(message->xid), client->xid); + return 0; + } + + return client_handle_message(client, message, len); +} + +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))]; + struct iovec iov = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + bool checksum = true; + ssize_t buflen, len; + int r; + + assert(s); + assert(client); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return buflen; + + packet = malloc0(buflen); + if (!packet) + return -ENOMEM; + + iov.iov_base = packet; + iov.iov_len = buflen; + + len = recvmsg(fd, &msg, 0); + if (len < 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; + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_PACKET && + cmsg->cmsg_type == PACKET_AUXDATA && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct tpacket_auxdata))) { + struct tpacket_auxdata *aux = (struct tpacket_auxdata*)CMSG_DATA(cmsg); + + checksum = !(aux->tp_status & TP_STATUS_CSUMNOTREADY); + break; + } + } + + r = dhcp_packet_verify_headers(packet, len, checksum); + if (r < 0) + return 0; + + len -= DHCP_IP_UDP_SIZE; + + return client_handle_message(client, &packet->dhcp, len); +} + +int sd_dhcp_client_start(sd_dhcp_client *client) { + int r; + + assert_return(client, -EINVAL); + + r = client_initialize(client); + if (r < 0) + return r; + + if (client->last_addr) + client->state = DHCP_STATE_INIT_REBOOT; + + r = client_start(client); + if (r >= 0) + log_dhcp_client(client, "STARTED on ifindex %i", client->index); + + return r; +} + +int sd_dhcp_client_stop(sd_dhcp_client *client) { + DHCP_CLIENT_DONT_DESTROY(client); + + assert_return(client, -EINVAL); + + 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 r; + + assert_return(client, -EINVAL); + assert_return(!client->event, -EBUSY); + + if (event) + client->event = sd_event_ref(event); + else { + r = sd_event_default(&client->event); + if (r < 0) + return 0; + } + + client->event_priority = priority; + + return 0; +} + +int sd_dhcp_client_detach_event(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + + client->event = sd_event_unref(client->event); + + return 0; +} + +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client) { + if (!client) + return NULL; + + return client->event; +} + +sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client) { + + 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) + return NULL; + + assert(client->n_ref >= 1); + client->n_ref--; + + if (client->n_ref > 0) + return NULL; + + log_dhcp_client(client, "FREE"); + + 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_(sd_dhcp_client_unrefp) sd_dhcp_client *client = NULL; + + assert_return(ret, -EINVAL); + + client = new0(sd_dhcp_client, 1); + if (!client) + return -ENOMEM; + + client->n_ref = 1; + client->state = DHCP_STATE_INIT; + client->index = -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; + + *ret = client; + client = NULL; + + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c new file mode 100644 index 0000000000..d8d6f446ee --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-lease.c @@ -0,0 +1,1178 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <arpa/inet.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include <systemd/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 "hexdecoct.h" +#include "hostname-util.h" +#include "in-addr-util.h" +#include "network-internal.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; +} + +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; +} + +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu) { + assert_return(lease, -EINVAL); + assert_return(mtu, -EINVAL); + + if (lease->mtu <= 0) + return -ENODATA; + + *mtu = lease->mtu; + return 0; +} + +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 <= 0) + return -ENODATA; + + *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 <= 0) + return -ENODATA; + + *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) + return -ENODATA; + + *domainname = lease->domainname; + return 0; +} + +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname) { + assert_return(lease, -EINVAL); + assert_return(hostname, -EINVAL); + + if (!lease->hostname) + return -ENODATA; + + *hostname = lease->hostname; + return 0; +} + +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) + return -ENODATA; + + *root_path = lease->root_path; + return 0; +} + +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 == 0) + return -ENODATA; + + addr->s_addr = lease->router; + return 0; +} + +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (!lease->have_subnet_mask) + return -ENODATA; + + addr->s_addr = lease->subnet_mask; + return 0; +} + +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->server_address == 0) + return -ENODATA; + + addr->s_addr = lease->server_address; + return 0; +} + +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + + if (lease->next_server == 0) + return -ENODATA; + + addr->s_addr = lease->next_server; + return 0; +} + +/* + * 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 <= 0) + return -ENODATA; + + 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 void **data, size_t *data_len) { + assert_return(lease, -EINVAL); + assert_return(data, -EINVAL); + assert_return(data_len, -EINVAL); + + 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) + 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) + return NULL; + + assert(lease->n_ref >= 1); + lease->n_ref--; + + if (lease->n_ref > 0) + return NULL; + + 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); + } + + 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 int lease_parse_u32(const uint8_t *option, size_t len, uint32_t *ret, uint32_t min) { + assert(option); + assert(ret); + + if (len != 4) + return -EINVAL; + + *ret = unaligned_read_be32((be32_t*) option); + if (*ret < min) + *ret = min; + + return 0; +} + +static int lease_parse_u16(const uint8_t *option, size_t len, uint16_t *ret, uint16_t min) { + assert(option); + assert(ret); + + if (len != 2) + return -EINVAL; + + *ret = unaligned_read_be16((be16_t*) option); + if (*ret < min) + *ret = min; + + return 0; +} + +static int lease_parse_be32(const uint8_t *option, size_t len, be32_t *ret) { + assert(option); + assert(ret); + + if (len != 4) + return -EINVAL; + + 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 <= 0) + *ret = mfree(*ret); + else { + char *string; + + /* + * 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 -ENOMEM; + + free(*ret); + *ret = string; + } + + return 0; +} + +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); + + 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; + + if (len % 4 != 0) + return -EINVAL; + + n_addresses = len / 4; + + addresses = newdup(struct in_addr, option, n_addresses); + if (!addresses) + return -ENOMEM; + + free(*ret); + *ret = addresses; + *n_ret = n_addresses; + } + + return 0; +} + +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 || len <= 0); + assert(routes); + assert(routes_size); + assert(routes_allocated); + + if (len <= 0) + return 0; + + if (len % 8 != 0) + return -EINVAL; + + if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + (len / 8))) + return -ENOMEM; + + while (len >= 8) { + struct sd_dhcp_route *route = *routes + *routes_size; + int r; + + r = in_addr_default_prefixlen((struct in_addr*) option, &route->dst_prefixlen); + if (r < 0) { + log_debug("Failed to determine destination prefix length from class based IP, ignoring"); + continue; + } + + assert_se(lease_parse_be32(option, 4, &addr.s_addr) >= 0); + route->dst_addr = inet_makeaddr(inet_netof(addr), 0); + option += 4; + + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); + option += 4; + + len -= 8; + (*routes_size)++; + } + + return 0; +} + +/* 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) { + + 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) { + uint8_t dst_octets; + struct sd_dhcp_route *route; + + if (!GREEDY_REALLOC(*routes, *routes_allocated, *routes_size + 1)) + return -ENOMEM; + + route = *routes + *routes_size; + + dst_octets = (*option == 0 ? 0 : ((*option - 1) / 8) + 1); + route->dst_prefixlen = *option; + option++; + len--; + + /* can't have more than 4 octets in IPv4 */ + if (dst_octets > 4 || len < dst_octets) + return -EINVAL; + + route->dst_addr.s_addr = 0; + memcpy(&route->dst_addr.s_addr, option, dst_octets); + option += dst_octets; + len -= dst_octets; + + if (len < 4) + return -EINVAL; + + assert_se(lease_parse_be32(option, 4, &route->gw_addr.s_addr) >= 0); + option += 4; + len -= 4; + + (*routes_size)++; + } + + return 0; +} + +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 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 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 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 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 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 SD_DHCP_OPTION_DOMAIN_NAME_SERVER: + r = lease_parse_in_addrs(option, len, &lease->dns, &lease->dns_size); + if (r < 0) + log_debug_errno(r, "Failed to parse DNS server, ignoring: %m"); + break; + + case SD_DHCP_OPTION_NTP_SERVER: + r = lease_parse_in_addrs(option, len, &lease->ntp, &lease->ntp_size); + if (r < 0) + log_debug_errno(r, "Failed to parse NTP server, ignoring: %m"); + break; + + 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) + log_debug_errno(r, "Failed to parse static routes, ignoring: %m"); + break; + + case SD_DHCP_OPTION_INTERFACE_MTU: + r = lease_parse_u16(option, len, &lease->mtu, 68); + if (r < 0) + log_debug_errno(r, "Failed to parse MTU, ignoring: %m"); + break; + + 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 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 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 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 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; + + 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) + log_debug_errno(r, "Failed to parse classless routes, ignoring: %m"); + break; + + case SD_DHCP_OPTION_NEW_TZDB_TIMEZONE: { + _cleanup_free_ char *tz = NULL; + + r = lease_parse_string(option, len, &tz); + if (r < 0) { + log_debug_errno(r, "Failed to parse timezone option, ignoring: %m"); + return 0; + } + + if (!timezone_is_valid(tz)) { + log_debug_errno(r, "Timezone is not valid, ignoring: %m"); + return 0; + } + + free(lease->timezone); + lease->timezone = tz; + tz = NULL; + + break; + } + + case SD_DHCP_OPTION_VENDOR_SPECIFIC: + + if (len <= 0) + lease->vendor_specific = mfree(lease->vendor_specific); + else { + void *p; + + p = memdup(option, len); + if (!p) + return -ENOMEM; + + free(lease->vendor_specific); + lease->vendor_specific = p; + } + + lease->vendor_specific_len = len; + break; + + 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; + + default: + log_debug("Ignoring option DHCP option %"PRIu8" while parsing.", code); + break; + } + + return 0; +} + +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; + + assert(lease); + + 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; + + 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; +} + +int dhcp_lease_new(sd_dhcp_lease **ret) { + sd_dhcp_lease *lease; + + lease = new0(sd_dhcp_lease, 1); + if (!lease) + return -ENOMEM; + + lease->router = INADDR_ANY; + lease->n_ref = 1; + + *ret = lease; + return 0; +} + +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 void *client_id, *data; + size_t client_id_len, data_len; + const char *string; + uint16_t mtu; + _cleanup_free_ sd_dhcp_route **routes = NULL; + uint32_t t1, t2, lifetime; + int r; + + assert(lease); + assert(lease_file); + + r = fopen_temporary(lease_file, &f, &temp_path); + if (r < 0) + goto fail; + + fchmod(fileno(f), 0644); + + fprintf(f, + "# This is private data. Do not parse.\n"); + + r = sd_dhcp_lease_get_address(lease, &address); + if (r >= 0) + fprintf(f, "ADDRESS=%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) + fprintf(f, "ROUTER=%s\n", inet_ntoa(address)); + + r = sd_dhcp_lease_get_server_identifier(lease, &address); + if (r >= 0) + 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); + + 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); + } + + r = sd_dhcp_lease_get_ntp(lease, &addresses); + if (r > 0) { + fputs("NTP=", f); + serialize_in_addrs(f, addresses, r); + fputs("\n", f); + } + + r = sd_dhcp_lease_get_domainname(lease, &string); + if (r >= 0) + fprintf(f, "DOMAINNAME=%s\n", string); + + r = sd_dhcp_lease_get_hostname(lease, &string); + if (r >= 0) + fprintf(f, "HOSTNAME=%s\n", string); + + r = sd_dhcp_lease_get_root_path(lease, &string); + if (r >= 0) + fprintf(f, "ROOT_PATH=%s\n", string); + + r = sd_dhcp_lease_get_routes(lease, &routes); + 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; + + client_id_hex = hexmem(client_id, client_id_len); + if (!client_id_hex) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "CLIENTID=%s\n", client_id_hex); + } + + r = sd_dhcp_lease_get_vendor_specific(lease, &data, &data_len); + if (r >= 0) { + _cleanup_free_ char *option_hex = NULL; + + option_hex = hexmem(data, data_len); + if (!option_hex) { + r = -ENOMEM; + goto fail; + } + fprintf(f, "VENDOR_SPECIFIC=%s\n", option_hex); + } + + LIST_FOREACH(options, option, lease->private_options) { + char key[strlen("OPTION_000")+1]; + + xsprintf(key, "OPTION_%" PRIu8, option->tag); + r = serialize_dhcp_option(f, key, option->data, option->length); + if (r < 0) + goto fail; + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, lease_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + +fail: + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save lease data %s: %m", lease_file); +} + +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); + + r = dhcp_lease_new(&lease); + if (r < 0) + return r; + + r = parse_env_file(lease_file, NEWLINE, + "ADDRESS", &address, + "ROUTER", &router, + "NETMASK", &netmask, + "SERVER_IDENTIFIER", &server_address, + "NEXT_SERVER", &next_server, + "BROADCAST", &broadcast, + "DNS", &dns, + "NTP", &ntp, + "MTU", &mtu, + "DOMAINNAME", &lease->domainname, + "HOSTNAME", &lease->hostname, + "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) + return r; + + 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, &lease->router); + if (r <= 0) + log_debug("Failed to parse router %s, ignoring.", router); + } + + 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, &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, &lease->next_server); + if (r <= 0) + log_debug("Failed to parse next server %s, ignoring.", next_server); + } + + 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) + 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) + log_debug_errno(r, "Failed to deserialize NTP servers %s, ignoring: %m", ntp); + else + lease->ntp_size = r; + } + + if (mtu) { + 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); + if (r < 0) + log_debug_errno(r, "Failed to parse DHCP routes %s, ignoring: %m", routes); + } + + if (lifetime) { + r = safe_atou32(lifetime, &lease->lifetime); + if (r < 0) + log_debug_errno(r, "Failed to parse lifetime %s, ignoring: %m", lifetime); + } + + if (t1) { + r = safe_atou32(t1, &lease->t1); + if (r < 0) + 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) { + 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 = dhcp_lease_insert_private_option(lease, SD_DHCP_OPTION_PRIVATE_BASE + i, data, len); + if (r < 0) + return r; + } + + *ret = lease; + lease = NULL; + + return 0; +} + +int dhcp_lease_set_default_subnet_mask(sd_dhcp_lease *lease) { + 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 */ + r = in_addr_default_subnet_mask(&address, &mask); + if (r < 0) + 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 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 void *client_id, size_t client_id_len) { + assert_return(lease, -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; + + p = memdup(client_id, client_id_len); + if (!p) + return -ENOMEM; + + 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 new file mode 100644 index 0000000000..e11462e6ac --- /dev/null +++ b/src/libsystemd-network/sd-dhcp-server.c @@ -0,0 +1,1156 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/ioctl.h> + +#include <systemd/sd-dhcp-server.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" + +#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; + + assert_return(server, -EINVAL); + assert_return(address, -EINVAL); + 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_offset = offset; + server->pool_size = size; + + server->address = address->s_addr; + 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; +} + +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) + return NULL; + + assert(server->n_ref >= 1); + server->n_ref++; + + return server; +} + +void client_id_hash_func(const void *p, struct siphash *state) { + const DHCPClientId *id = p; + + assert(id); + assert(id->length); + assert(id->data); + + 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) { + const DHCPClientId *a, *b; + + a = _a; + b = _b; + + assert(!a->length || a->data); + assert(!b->length || b->data); + + if (a->length != b->length) + return a->length < b->length ? -1 : 1; + + return memcmp(a->data, b->data, a->length); +} + +static const struct hash_ops client_id_hash_ops = { + .hash = client_id_hash_func, + .compare = client_id_compare_func +}; + +static void dhcp_lease_free(DHCPLease *lease) { + if (!lease) + return; + + free(lease->client_id.data); + free(lease); +} + +sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server) { + DHCPLease *lease; + + if (!server) + return NULL; + + assert(server->n_ref >= 1); + server->n_ref--; + + if (server->n_ref > 0) + return NULL; + + log_dhcp_server(server, "UNREF"); + + sd_dhcp_server_stop(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); + + free(server->bound_leases); + free(server); + + return NULL; +} + +int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex) { + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + + assert_return(ret, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + + server = new0(sd_dhcp_server, 1); + if (!server) + return -ENOMEM; + + server->n_ref = 1; + server->fd_raw = -1; + server->fd = -1; + server->address = htobe32(INADDR_ANY); + server->netmask = htobe32(INADDR_ANY); + 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; + + return 0; +} + +int sd_dhcp_server_attach_event(sd_dhcp_server *server, sd_event *event, + int priority) { + int r; + + assert_return(server, -EINVAL); + assert_return(!server->event, -EBUSY); + + if (event) + server->event = sd_event_ref(event); + else { + r = sd_event_default(&server->event); + if (r < 0) + return r; + } + + server->event_priority = priority; + + return 0; +} + +int sd_dhcp_server_detach_event(sd_dhcp_server *server) { + assert_return(server, -EINVAL); + + server->event = sd_event_unref(server->event); + + return 0; +} + +sd_event *sd_dhcp_server_get_event(sd_dhcp_server *server) { + assert_return(server, NULL); + + return server->event; +} + +int sd_dhcp_server_stop(sd_dhcp_server *server) { + assert_return(server, -EINVAL); + + server->receive_message = + sd_event_source_unref(server->receive_message); + + server->fd_raw = safe_close(server->fd_raw); + server->fd = safe_close(server->fd); + + log_dhcp_server(server, "STOPPED"); + + return 0; +} + +static int dhcp_server_send_unicast_raw(sd_dhcp_server *server, + DHCPPacket *packet, size_t len) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htons(ETH_P_IP), + .ll.sll_ifindex = server->ifindex, + .ll.sll_halen = ETH_ALEN, + }; + + assert(server); + assert(server->ifindex > 0); + assert(server->address); + assert(packet); + assert(len > sizeof(DHCPPacket)); + + memcpy(&link.ll.sll_addr, &packet->dhcp.chaddr, ETH_ALEN); + + dhcp_packet_append_ip_headers(packet, server->address, DHCP_PORT_SERVER, + packet->dhcp.yiaddr, + DHCP_PORT_CLIENT, len); + + 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, + DHCPMessage *message, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(DHCP_PORT_CLIENT), + .in.sin_addr.s_addr = destination, + }; + struct iovec iov = { + .iov_base = message, + .iov_len = len, + }; + uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))] = {}; + struct msghdr msg = { + .msg_name = &dest, + .msg_namelen = sizeof(dest.in), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + struct in_pktinfo *pktinfo; + int r; + + assert(server); + assert(server->fd > 0); + assert(message); + assert(len > sizeof(DHCPMessage)); + + cmsg = CMSG_FIRSTHDR(&msg); + assert(cmsg); + + cmsg->cmsg_level = IPPROTO_IP; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + + /* we attach source interface and address info to the message + rather than binding the socket. This will be mostly useful + when we gain support for arbitrary number of server addresses + */ + pktinfo = (struct in_pktinfo*) CMSG_DATA(cmsg); + assert(pktinfo); + + pktinfo->ipi_ifindex = server->ifindex; + pktinfo->ipi_spec_dst.s_addr = server->address; + + r = sendmsg(server->fd, &msg, 0); + if (r < 0) + return -errno; + + return 0; +} + +static bool requested_broadcast(DHCPRequest *req) { + assert(req); + + return req->message->flags & htobe16(0x8000); +} + +int dhcp_server_send_packet(sd_dhcp_server *server, + DHCPRequest *req, DHCPPacket *packet, + int type, size_t optoffset) { + be32_t destination = INADDR_ANY; + int r; + + assert(server); + assert(req); + assert(req->max_optlen); + assert(optoffset <= req->max_optlen); + assert(packet); + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + SD_DHCP_OPTION_SERVER_IDENTIFIER, + 4, &server->address); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &optoffset, 0, + SD_DHCP_OPTION_END, 0, NULL); + if (r < 0) + return r; + + /* RFC 2131 Section 4.1 + + If the ’giaddr’ field in a DHCP message from a client is non-zero, + the server sends any return messages to the ’DHCP server’ port on the + BOOTP relay agent whose address appears in ’giaddr’. If the ’giaddr’ + field is zero and the ’ciaddr’ field is nonzero, then the server + unicasts DHCPOFFER and DHCPACK messages to the address in ’ciaddr’. + If ’giaddr’ is zero and ’ciaddr’ is zero, and the broadcast bit is + set, then the server broadcasts DHCPOFFER and DHCPACK messages to + 0xffffffff. If the broadcast bit is not set and ’giaddr’ is zero and + ’ciaddr’ is zero, then the server unicasts DHCPOFFER and DHCPACK + messages to the client’s hardware address and ’yiaddr’ address. In + all cases, when ’giaddr’ is zero, the server broadcasts any DHCPNAK + messages to 0xffffffff. + + Section 4.3.2 + + If ’giaddr’ is set in the DHCPREQUEST message, the client is on a + different subnet. The server MUST set the broadcast bit in the + DHCPNAK, so that the relay agent will broadcast the DHCPNAK to the + client, because the client may not have a correct network address + or subnet mask, and the client may not be answering ARP requests. + */ + if (req->message->giaddr) { + destination = req->message->giaddr; + if (type == DHCP_NAK) + packet->dhcp.flags = htobe16(0x8000); + } else if (req->message->ciaddr && type != DHCP_NAK) + destination = req->message->ciaddr; + + if (destination != INADDR_ANY) + return dhcp_server_send_udp(server, destination, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else if (requested_broadcast(req) || type == DHCP_NAK) + return dhcp_server_send_udp(server, INADDR_BROADCAST, + &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else + /* we cannot send UDP packet to specific MAC address when the + address is not yet configured, so must fall back to raw + packets */ + return dhcp_server_send_unicast_raw(server, packet, + sizeof(DHCPPacket) + optoffset); +} + +static int server_message_init(sd_dhcp_server *server, DHCPPacket **ret, + uint8_t type, size_t *_optoffset, + DHCPRequest *req) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(ret); + assert(_optoffset); + assert(IN_SET(type, DHCP_OFFER, DHCP_ACK, DHCP_NAK)); + + packet = malloc0(sizeof(DHCPPacket) + req->max_optlen); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, + be32toh(req->message->xid), type, ARPHRD_ETHER, + req->max_optlen, &optoffset); + if (r < 0) + return r; + + packet->dhcp.flags = req->message->flags; + packet->dhcp.giaddr = req->message->giaddr; + memcpy(&packet->dhcp.chaddr, &req->message->chaddr, ETH_ALEN); + + *_optoffset = optoffset; + *ret = packet; + packet = NULL; + + return 0; +} + +static int server_send_offer(sd_dhcp_server *server, DHCPRequest *req, + be32_t address) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t offset; + be32_t lease_time; + int r; + + r = server_message_init(server, &packet, DHCP_OFFER, &offset, req); + if (r < 0) + return r; + + packet->dhcp.yiaddr = address; + + lease_time = htobe32(req->lifetime); + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + 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, + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + if (r < 0) + return r; + + 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) + return r; + + return 0; +} + +static int server_send_ack(sd_dhcp_server *server, DHCPRequest *req, + be32_t address) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t offset; + be32_t lease_time; + int r; + + r = server_message_init(server, &packet, DHCP_ACK, &offset, req); + if (r < 0) + return r; + + packet->dhcp.yiaddr = address; + + lease_time = htobe32(req->lifetime); + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + 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, + SD_DHCP_OPTION_SUBNET_MASK, 4, &server->netmask); + if (r < 0) + return r; + + 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) + return r; + + return 0; +} + +static int server_send_nak(sd_dhcp_server *server, DHCPRequest *req) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t offset; + int r; + + r = server_message_init(server, &packet, DHCP_NAK, &offset, req); + if (r < 0) + return r; + + return dhcp_server_send_packet(server, req, packet, DHCP_NAK, offset); +} + +static int server_send_forcerenew(sd_dhcp_server *server, be32_t address, + be32_t gateway, uint8_t chaddr[]) { + _cleanup_free_ DHCPPacket *packet = NULL; + size_t optoffset = 0; + int r; + + assert(server); + assert(address != INADDR_ANY); + assert(chaddr); + + packet = malloc0(sizeof(DHCPPacket) + DHCP_MIN_OPTIONS_SIZE); + if (!packet) + return -ENOMEM; + + r = dhcp_message_init(&packet->dhcp, BOOTREPLY, 0, + DHCP_FORCERENEW, ARPHRD_ETHER, + DHCP_MIN_OPTIONS_SIZE, &optoffset); + if (r < 0) + return r; + + r = dhcp_option_append(&packet->dhcp, DHCP_MIN_OPTIONS_SIZE, + &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, + sizeof(DHCPMessage) + optoffset); + if (r < 0) + return r; + + return 0; +} + +static int parse_request(uint8_t code, uint8_t len, const void *option, void *userdata) { + DHCPRequest *req = userdata; + + assert(req); + + switch(code) { + case SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME: + if (len == 4) + req->lifetime = be32toh(*(be32_t*)option); + + break; + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: + if (len == 4) + req->requested_ip = *(be32_t*)option; + + break; + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + if (len == 4) + req->server_id = *(be32_t*)option; + + break; + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: + if (len >= 2) { + uint8_t *data; + + data = memdup(option, len); + if (!data) + return -ENOMEM; + + free(req->client_id.data); + req->client_id.data = data; + req->client_id.length = len; + } + + break; + case SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE: + if (len == 2) + req->max_optlen = be16toh(*(be16_t*)option) - + - sizeof(DHCPPacket); + + break; + } + + return 0; +} + +static void dhcp_request_free(DHCPRequest *req) { + if (!req) + return; + + free(req->client_id.data); + free(req); +} + +DEFINE_TRIVIAL_CLEANUP_FUNC(DHCPRequest*, dhcp_request_free); +#define _cleanup_dhcp_request_free_ _cleanup_(dhcp_request_freep) + +static int ensure_sane_request(sd_dhcp_server *server, DHCPRequest *req, DHCPMessage *message) { + assert(req); + assert(message); + + req->message = message; + + /* set client id based on MAC address if client did not send an explicit + one */ + if (!req->client_id.data) { + void *data; + + 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; + } + + if (req->max_optlen < DHCP_MIN_OPTIONS_SIZE) + req->max_optlen = DHCP_MIN_OPTIONS_SIZE; + + 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; +} + +static int get_pool_offset(sd_dhcp_server *server, be32_t requested_ip) { + assert(server); + + if (!server->pool_size) + return -EINVAL; + + if (be32toh(requested_ip) < (be32toh(server->subnet) | server->pool_offset) || + be32toh(requested_ip) >= (be32toh(server->subnet) | (server->pool_offset + server->pool_size))) + return -ERANGE; + + 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; + + assert(server); + assert(message); + + if (message->op != BOOTREQUEST || + message->htype != ARPHRD_ETHER || + message->hlen != ETHER_ADDR_LEN) + return 0; + + req = new0(DHCPRequest, 1); + if (!req) + return -ENOMEM; + + type = dhcp_option_parse(message, length, parse_request, req, &error_message); + if (type < 0) + return 0; + + r = ensure_sane_request(server, req, message); + if (r < 0) + /* this only fails on critical errors */ + return r; + + existing_lease = hashmap_get(server->leases_by_client_id, + &req->client_id); + + switch(type) { + + case DHCP_DISCOVER: { + be32_t address = INADDR_ANY; + unsigned i; + + log_dhcp_server(server, "DISCOVER (0x%x)", + be32toh(req->message->xid)); + + if (!server->pool_size) + /* no pool allocated */ + return 0; + + /* for now pick a random free address from the pool */ + 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[next_offer]) { + address = server->subnet | htobe32(server->pool_offset + next_offer); + break; + } else + next_offer = (next_offer + 1) % server->pool_size; + } + } + + if (address == INADDR_ANY) + /* no free addresses left */ + return 0; + + r = server_send_offer(server, req, address); + if (r < 0) { + /* this only fails on critical errors */ + log_dhcp_server(server, "could not send offer: %s", + strerror(-r)); + return r; + } else { + log_dhcp_server(server, "OFFER (0x%x)", + be32toh(req->message->xid)); + return DHCP_OFFER; + } + + break; + } + case DHCP_DECLINE: + 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; + + case DHCP_REQUEST: { + be32_t address; + bool init_reboot = false; + int pool_offset; + + /* see RFC 2131, section 4.3.2 */ + + if (req->server_id) { + log_dhcp_server(server, "REQUEST (selecting) (0x%x)", + be32toh(req->message->xid)); + + /* SELECTING */ + if (req->server_id != server->address) + /* client did not pick us */ + return 0; + + if (req->message->ciaddr) + /* this MUST be zero */ + return 0; + + if (!req->requested_ip) + /* this must be filled in with the yiaddr + from the chosen OFFER */ + return 0; + + address = req->requested_ip; + } else if (req->requested_ip) { + log_dhcp_server(server, "REQUEST (init-reboot) (0x%x)", + be32toh(req->message->xid)); + + /* INIT-REBOOT */ + if (req->message->ciaddr) + /* this MUST be zero */ + return 0; + + /* TODO: check more carefully if IP is correct */ + address = req->requested_ip; + init_reboot = true; + } else { + log_dhcp_server(server, "REQUEST (rebinding/renewing) (0x%x)", + be32toh(req->message->xid)); + + /* REBINDING / RENEWING */ + if (!req->message->ciaddr) + /* this MUST be filled in with clients IP address */ + return 0; + + address = req->message->ciaddr; + } + + pool_offset = get_pool_offset(server, address); + + /* verify that the requested address is from the pool, and either + owned by the current client or free */ + if (pool_offset >= 0 && + server->bound_leases[pool_offset] == existing_lease) { + DHCPLease *lease; + usec_t time_now = 0; + + if (!existing_lease) { + lease = new0(DHCPLease, 1); + lease->address = req->requested_ip; + lease->client_id.data = memdup(req->client_id.data, + req->client_id.length); + if (!lease->client_id.data) { + free(lease); + return -ENOMEM; + } + lease->client_id.length = req->client_id.length; + memcpy(&lease->chaddr, &req->message->chaddr, + ETH_ALEN); + lease->gateway = req->message->giaddr; + } else + lease = existing_lease; + + r = sd_event_now(server->event, + clock_boottime_or_monotonic(), + &time_now); + 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); + if (r < 0) { + /* this only fails on critical errors */ + log_dhcp_server(server, "could not send ack: %s", + strerror(-r)); + + if (!existing_lease) + dhcp_lease_free(lease); + + return r; + } else { + log_dhcp_server(server, "ACK (0x%x)", + be32toh(req->message->xid)); + + server->bound_leases[pool_offset] = lease; + hashmap_put(server->leases_by_client_id, + &lease->client_id, lease); + + return DHCP_ACK; + } + } else if (init_reboot) { + r = server_send_nak(server, req); + if (r < 0) { + /* this only fails on critical errors */ + log_dhcp_server(server, "could not send nak: %s", + strerror(-r)); + return r; + } else { + log_dhcp_server(server, "NAK (0x%x)", + be32toh(req->message->xid)); + return DHCP_NAK; + } + } + + break; + } + + case DHCP_RELEASE: { + int pool_offset; + + log_dhcp_server(server, "RELEASE (0x%x)", + be32toh(req->message->xid)); + + if (!existing_lease) + return 0; + + if (existing_lease->address != req->message->ciaddr) + return 0; + + pool_offset = get_pool_offset(server, req->message->ciaddr); + if (pool_offset < 0) + return 0; + + if (server->bound_leases[pool_offset] == existing_lease) { + server->bound_leases[pool_offset] = NULL; + hashmap_remove(server->leases_by_client_id, existing_lease); + dhcp_lease_free(existing_lease); + + return 1; + } else + return 0; + } + } + + return 0; +} + +static int server_receive_message(sd_event_source *s, int fd, + uint32_t revents, void *userdata) { + _cleanup_free_ DHCPMessage *message = NULL; + uint8_t cmsgbuf[CMSG_LEN(sizeof(struct in_pktinfo))]; + sd_dhcp_server *server = userdata; + struct iovec iov = {}; + struct msghdr msg = { + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = cmsgbuf, + .msg_controllen = sizeof(cmsgbuf), + }; + struct cmsghdr *cmsg; + ssize_t buflen, len; + + assert(server); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return buflen; + + message = malloc(buflen); + if (!message) + return -ENOMEM; + + iov.iov_base = message; + iov.iov_len = buflen; + + len = recvmsg(fd, &msg, 0); + 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) { + if (cmsg->cmsg_level == IPPROTO_IP && + cmsg->cmsg_type == IP_PKTINFO && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct in_pktinfo))) { + struct in_pktinfo *info = (struct in_pktinfo*)CMSG_DATA(cmsg); + + /* TODO figure out if this can be done as a filter on + * the socket, like for IPv6 */ + if (server->ifindex != info->ipi_ifindex) + return 0; + + break; + } + } + + return dhcp_server_handle_message(server, message, (size_t)len); +} + +int sd_dhcp_server_start(sd_dhcp_server *server) { + int r; + + assert_return(server, -EINVAL); + assert_return(server->event, -EINVAL); + assert_return(!server->receive_message, -EBUSY); + assert_return(server->fd_raw == -1, -EBUSY); + assert_return(server->fd == -1, -EBUSY); + assert_return(server->address != htobe32(INADDR_ANY), -EUNATCH); + + r = socket(AF_PACKET, SOCK_DGRAM | SOCK_NONBLOCK, 0); + if (r < 0) { + r = -errno; + sd_dhcp_server_stop(server); + return r; + } + server->fd_raw = r; + + r = dhcp_network_bind_udp_socket(INADDR_ANY, DHCP_PORT_SERVER); + if (r < 0) { + sd_dhcp_server_stop(server); + return r; + } + server->fd = r; + + r = sd_event_add_io(server->event, &server->receive_message, + server->fd, EPOLLIN, + server_receive_message, server); + if (r < 0) { + sd_dhcp_server_stop(server); + return r; + } + + r = sd_event_source_set_priority(server->receive_message, + server->event_priority); + if (r < 0) { + sd_dhcp_server_stop(server); + return r; + } + + log_dhcp_server(server, "STARTED"); + + return 0; +} + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server) { + unsigned i; + int r = 0; + + assert_return(server, -EINVAL); + assert(server->bound_leases); + + for (i = 0; i < server->pool_size; i++) { + DHCPLease *lease = server->bound_leases[i]; + + if (!lease || lease == &server->invalid_lease) + continue; + + r = server_send_forcerenew(server, lease->address, + lease->gateway, + lease->chaddr); + if (r < 0) + return r; + else + log_dhcp_server(server, "FORCERENEW"); + } + + 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; +} diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c new file mode 100644 index 0000000000..494036cb54 --- /dev/null +++ b/src/libsystemd-network/sd-dhcp6-client.c @@ -0,0 +1,1304 @@ +/*** + This file is part of systemd. + + 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 + 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 <string.h> +#include <sys/ioctl.h> +#include <linux/if_infiniband.h> + +#include <systemd/sd-dhcp6-client.h> + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp6-internal.h" +#include "dhcp6-lease-internal.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 { + unsigned n_ref; + + enum DHCP6State state; + sd_event *event; + int event_priority; + int index; + struct in6_addr local_address; + uint8_t mac_addr[MAX_MAC_ADDR_LEN]; + size_t mac_addr_len; + uint16_t arp_type; + DHCP6IA ia_na; + be32_t transaction_id; + usec_t transaction_start; + struct sd_dhcp6_lease *lease; + int fd; + bool information_request; + be16_t *req_opts; + size_t req_opts_allocated; + size_t req_opts_len; + sd_event_source *receive_message; + usec_t retransmit_time; + uint8_t retransmit_count; + sd_event_source *timeout_resend; + sd_event_source *timeout_resend_expire; + sd_dhcp6_client_cb_t cb; + void *userdata; + struct duid duid; + size_t duid_len; +}; + +static const uint16_t default_req_opts[] = { + 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] = { + [DHCP6_SOLICIT] = "SOLICIT", + [DHCP6_ADVERTISE] = "ADVERTISE", + [DHCP6_REQUEST] = "REQUEST", + [DHCP6_CONFIRM] = "CONFIRM", + [DHCP6_RENEW] = "RENEW", + [DHCP6_REBIND] = "REBIND", + [DHCP6_REPLY] = "REPLY", + [DHCP6_RELEASE] = "RELEASE", + [DHCP6_DECLINE] = "DECLINE", + [DHCP6_RECONFIGURE] = "RECONFIGURE", + [DHCP6_INFORMATION_REQUEST] = "INFORMATION-REQUEST", + [DHCP6_RELAY_FORW] = "RELAY-FORW", + [DHCP6_RELAY_REPL] = "RELAY-REPL", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_type, int); + +const char * dhcp6_message_status_table[_DHCP6_STATUS_MAX] = { + [DHCP6_STATUS_SUCCESS] = "Success", + [DHCP6_STATUS_UNSPEC_FAIL] = "Unspecified failure", + [DHCP6_STATUS_NO_ADDRS_AVAIL] = "No addresses available", + [DHCP6_STATUS_NO_BINDING] = "Binding unavailable", + [DHCP6_STATUS_NOT_ON_LINK] = "Not on link", + [DHCP6_STATUS_USE_MULTICAST] = "Use multicast", +}; + +DEFINE_STRING_TABLE_LOOKUP(dhcp6_message_status, int); + +#define DHCP6_CLIENT_DONT_DESTROY(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) { + assert_return(client, -EINVAL); + + client->cb = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp6_client_set_index(sd_dhcp6_client *client, int interface_index) { + assert_return(client, -EINVAL); + assert_return(interface_index >= -1, -EINVAL); + + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->index = interface_index; + + 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) { + + 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) + assert_return(addr_len == INFINIBAND_ALEN, -EINVAL); + else + return -EINVAL; + + if (client->mac_addr_len == addr_len && + memcmp(&client->mac_addr, addr, addr_len) == 0) + return 0; + + memcpy(&client->mac_addr, addr, addr_len); + client->mac_addr_len = addr_len; + client->arp_type = arp_type; + + return 0; +} + +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) { + assert_return(client, -EINVAL); + assert_return(duid, -EINVAL); + assert_return(duid_len > 0 && duid_len <= MAX_DUID_LEN, -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; + } + + client->duid.type = htobe16(type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = duid_len + sizeof(client->duid.type); + + 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, int *enabled) { + assert_return(client, -EINVAL); + assert_return(enabled, -EINVAL); + + *enabled = client->information_request; + + return 0; +} + +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 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: + return -EINVAL; + } + + for (t = 0; t < client->req_opts_len; t++) + if (client->req_opts[t] == htobe16(option)) + return -EEXIST; + + if (!GREEDY_REALLOC(client->req_opts, client->req_opts_allocated, + client->req_opts_len + 1)) + return -ENOMEM; + + client->req_opts[client->req_opts_len++] = htobe16(option); + + return 0; +} + +int sd_dhcp6_client_get_lease(sd_dhcp6_client *client, sd_dhcp6_lease **ret) { + assert_return(client, -EINVAL); + + if (!client->lease) + return -ENOMSG; + + 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); +} + +static void client_set_lease(sd_dhcp6_client *client, sd_dhcp6_lease *lease) { + 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); + + client_set_lease(client, NULL); + + client->receive_message = + sd_event_source_unref(client->receive_message); + + client->fd = safe_close(client->fd); + + client->transaction_id = 0; + client->transaction_start = 0; + + client->ia_na.timeout_t1 = + sd_event_source_unref(client->ia_na.timeout_t1); + client->ia_na.timeout_t2 = + sd_event_source_unref(client->ia_na.timeout_t2); + + client->retransmit_time = 0; + client->retransmit_count = 0; + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->timeout_resend_expire = + sd_event_source_unref(client->timeout_resend_expire); + + client->state = DHCP6_STATE_STOPPED; + + return 0; +} + +static void client_stop(sd_dhcp6_client *client, int error) { + DHCP6_CLIENT_DONT_DESTROY(client); + + assert(client); + + client_notify(client, error); + + client_reset(client); +} + +static int client_send_message(sd_dhcp6_client *client, usec_t time_now) { + _cleanup_free_ DHCP6Message *message = NULL; + struct in6_addr all_servers = + IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; + size_t len, optlen = 512; + uint8_t *opt; + int r; + usec_t elapsed_usec; + be16_t elapsed_time; + + len = sizeof(DHCP6Message) + optlen; + + message = malloc0(len); + if (!message) + return -ENOMEM; + + opt = (uint8_t *)(message + 1); + + message->transaction_id = client->transaction_id; + + switch(client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + message->type = DHCP6_INFORMATION_REQUEST; + + break; + + case DHCP6_STATE_SOLICITATION: + message->type = DHCP6_SOLICIT; + + r = dhcp6_option_append(&opt, &optlen, + SD_DHCP6_OPTION_RAPID_COMMIT, 0, NULL); + if (r < 0) + return r; + + r = dhcp6_option_append_ia(&opt, &optlen, &client->ia_na); + if (r < 0) + return r; + + break; + + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + + if (client->state == DHCP6_STATE_REQUEST) + message->type = DHCP6_REQUEST; + else + message->type = DHCP6_RENEW; + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_SERVERID, + client->lease->serverid_len, + client->lease->serverid); + if (r < 0) + return r; + + r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia); + if (r < 0) + return r; + + break; + + case DHCP6_STATE_REBIND: + message->type = DHCP6_REBIND; + + r = dhcp6_option_append_ia(&opt, &optlen, &client->lease->ia); + if (r < 0) + return r; + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + return -EINVAL; + } + + 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, SD_DHCP6_OPTION_CLIENTID, + client->duid_len, &client->duid); + if (r < 0) + return r; + + elapsed_usec = time_now - client->transaction_start; + if (elapsed_usec < 0xffff * USEC_PER_MSEC * 10) + elapsed_time = htobe16(elapsed_usec / USEC_PER_MSEC / 10); + else + elapsed_time = 0xffff; + + r = dhcp6_option_append(&opt, &optlen, SD_DHCP6_OPTION_ELAPSED_TIME, + sizeof(elapsed_time), &elapsed_time); + if (r < 0) + return r; + + r = dhcp6_network_send_udp_socket(client->fd, &all_servers, message, + len - optlen); + if (r < 0) + return r; + + log_dhcp6_client(client, "Sent %s", + dhcp6_message_type_to_string(message->type)); + + return 0; +} + +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); + + client->lease->ia.timeout_t2 = + sd_event_source_unref(client->lease->ia.timeout_t2); + + log_dhcp6_client(client, "Timeout T2"); + + client_start(client, DHCP6_STATE_REBIND); + + return 0; +} + +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); + + client->lease->ia.timeout_t1 = + sd_event_source_unref(client->lease->ia.timeout_t1); + + log_dhcp6_client(client, "Timeout T1"); + + client_start(client, DHCP6_STATE_RENEW); + + return 0; +} + +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; + + assert(s); + assert(client); + assert(client->event); + + state = client->state; + + 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..." */ + if (state == DHCP6_STATE_REBIND) + client_start(client, DHCP6_STATE_SOLICITATION); + + return 0; +} + +static usec_t client_timeout_compute_random(usec_t val) { + return val - val / 10 + + (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) { + int r = 0; + sd_dhcp6_client *client = userdata; + usec_t time_now, init_retransmit_time = 0, max_retransmit_time = 0; + usec_t max_retransmit_duration = 0; + uint8_t max_retransmit_count = 0; + char time_string[FORMAT_TIMESPAN_MAX]; + uint32_t expire = 0; + + assert(s); + assert(client); + assert(client->event); + + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + init_retransmit_time = DHCP6_INF_TIMEOUT; + max_retransmit_time = DHCP6_INF_MAX_RT; + + break; + + case DHCP6_STATE_SOLICITATION: + + if (client->retransmit_count && client->lease) { + client_start(client, DHCP6_STATE_REQUEST); + return 0; + } + + init_retransmit_time = DHCP6_SOL_TIMEOUT; + max_retransmit_time = DHCP6_SOL_MAX_RT; + + break; + + case DHCP6_STATE_REQUEST: + init_retransmit_time = DHCP6_REQ_TIMEOUT; + max_retransmit_time = DHCP6_REQ_MAX_RT; + max_retransmit_count = DHCP6_REQ_MAX_RC; + + break; + + case DHCP6_STATE_RENEW: + init_retransmit_time = DHCP6_REN_TIMEOUT; + max_retransmit_time = DHCP6_REN_MAX_RT; + + /* RFC 3315, section 18.1.3. says max retransmit duration will + be the remaining time until T2. Instead of setting MRD, + wait for T2 to trigger with the same end result */ + + break; + + case DHCP6_STATE_REBIND: + init_retransmit_time = DHCP6_REB_TIMEOUT; + max_retransmit_time = DHCP6_REB_MAX_RT; + + if (!client->timeout_resend_expire) { + r = dhcp6_lease_ia_rebind_expire(&client->lease->ia, + &expire); + if (r < 0) { + client_stop(client, r); + return 0; + } + max_retransmit_duration = expire * USEC_PER_SEC; + } + + break; + + case DHCP6_STATE_STOPPED: + case DHCP6_STATE_BOUND: + return 0; + } + + if (max_retransmit_count && + client->retransmit_count >= max_retransmit_count) { + client_stop(client, SD_DHCP6_CLIENT_EVENT_RETRANS_MAX); + return 0; + } + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + goto error; + + r = client_send_message(client, time_now); + if (r >= 0) + client->retransmit_count++; + + if (!client->retransmit_time) { + client->retransmit_time = + client_timeout_compute_random(init_retransmit_time); + + if (client->state == DHCP6_STATE_SOLICITATION) + client->retransmit_time += init_retransmit_time / 10; + + } else { + if (max_retransmit_time && + client->retransmit_time > max_retransmit_time / 2) + client->retransmit_time = client_timeout_compute_random(max_retransmit_time); + else + client->retransmit_time += client_timeout_compute_random(client->retransmit_time); + } + + log_dhcp6_client(client, "Next retransmission in %s", + 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(), + time_now + client->retransmit_time, + 10 * USEC_PER_MSEC, client_timeout_resend, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timer"); + if (r < 0) + goto error; + + if (max_retransmit_duration && !client->timeout_resend_expire) { + + log_dhcp6_client(client, "Max retransmission duration %"PRIu64" secs", + max_retransmit_duration / USEC_PER_SEC); + + r = sd_event_add_time(client->event, + &client->timeout_resend_expire, + clock_boottime_or_monotonic(), + time_now + max_retransmit_duration, + USEC_PER_SEC, + client_timeout_resend_expire, client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->timeout_resend_expire, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->timeout_resend_expire, "dhcp6-resend-expire-timer"); + if (r < 0) + goto error; + } + +error: + if (r < 0) + client_stop(client, r); + + return 0; +} + +static int client_ensure_iaid(sd_dhcp6_client *client) { + int r; + + assert(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); + 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) { + int r; + uint8_t *optval, *option, *id = NULL; + uint16_t optcode, status; + size_t optlen, id_len; + bool clientid = false; + be32_t iaid_lease; + + option = (uint8_t *)message + sizeof(DHCP6Message); + len -= sizeof(DHCP6Message); + + while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, + &optval)) >= 0) { + switch (optcode) { + case SD_DHCP6_OPTION_CLIENTID: + if (clientid) { + log_dhcp6_client(client, "%s contains multiple clientids", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (optlen != client->duid_len || + memcmp(&client->duid, optval, optlen) != 0) { + log_dhcp6_client(client, "%s DUID does not match", + dhcp6_message_type_to_string(message->type)); + + return -EINVAL; + } + clientid = true; + + break; + + 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", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + r = dhcp6_lease_set_serverid(lease, optval, optlen); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_PREFERENCE: + if (optlen != 1) + return -EINVAL; + + r = dhcp6_lease_set_preference(lease, *optval); + if (r < 0) + return r; + + break; + + case SD_DHCP6_OPTION_STATUS_CODE: + if (optlen < 2) + return -EINVAL; + + status = optval[0] << 8 | optval[1]; + if (status) { + log_dhcp6_client(client, "%s Status %s", + dhcp6_message_type_to_string(message->type), + dhcp6_message_status_to_string(status)); + return -EINVAL; + } + + break; + + case SD_DHCP6_OPTION_IA_NA: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + log_dhcp6_client(client, "Information request ignoring IA NA option"); + + break; + } + + r = dhcp6_option_parse_ia(&optval, &optlen, optcode, + &lease->ia); + if (r < 0 && r != -ENOMSG) + return r; + + r = dhcp6_lease_get_iaid(lease, &iaid_lease); + if (r < 0) + return r; + + if (client->ia_na.id != iaid_lease) { + log_dhcp6_client(client, "%s has wrong IAID", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + break; + + 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) + r = 0; + + if (r < 0 || !clientid) { + log_dhcp6_client(client, "%s has incomplete options", + dhcp6_message_type_to_string(message->type)); + return -EINVAL; + } + + if (client->state != DHCP6_STATE_INFORMATION_REQUEST) { + r = dhcp6_lease_get_serverid(lease, &id, &id_len); + if (r < 0) + log_dhcp6_client(client, "%s has no server id", + dhcp6_message_type_to_string(message->type)); + } + + return r; +} + +static int client_receive_reply(sd_dhcp6_client *client, DHCP6Message *reply, size_t len) { + int r; + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + bool rapid_commit; + + if (reply->type != DHCP6_REPLY) + return 0; + + r = dhcp6_lease_new(&lease); + if (r < 0) + return -ENOMEM; + + r = client_parse_message(client, reply, len, lease); + if (r < 0) + return r; + + if (client->state == DHCP6_STATE_SOLICITATION) { + r = dhcp6_lease_get_rapid_commit(lease, &rapid_commit); + if (r < 0) + return r; + + if (!rapid_commit) + return 0; + } + + 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_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t pref_advertise = 0, pref_lease = 0; + + if (advertise->type != DHCP6_ADVERTISE) + return 0; + + r = dhcp6_lease_new(&lease); + if (r < 0) + return r; + + r = client_parse_message(client, advertise, len, lease); + if (r < 0) + return r; + + r = dhcp6_lease_get_preference(lease, &pref_advertise); + if (r < 0) + return r; + + r = dhcp6_lease_get_preference(client->lease, &pref_lease); + + if (r < 0 || pref_advertise > pref_lease) { + client_set_lease(client, lease); + lease = NULL; + r = 0; + } + + if (pref_advertise == 255 || client->retransmit_count > 1) + r = DHCP6_STATE_REQUEST; + + return r; +} + +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 = NULL; + ssize_t buflen, len; + int r = 0; + + assert(s); + assert(client); + assert(client->event); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return buflen; + + message = malloc(buflen); + if (!message) + return -ENOMEM; + + len = read(fd, message, buflen); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_dhcp6_client(client, "Could not receive message from UDP socket: %m"); + + return -errno; + } else if ((size_t)len < sizeof(DHCP6Message)) + return 0; + + switch(message->type) { + case DHCP6_SOLICIT: + case DHCP6_REQUEST: + case DHCP6_CONFIRM: + case DHCP6_RENEW: + case DHCP6_REBIND: + case DHCP6_RELEASE: + case DHCP6_DECLINE: + case DHCP6_INFORMATION_REQUEST: + case DHCP6_RELAY_FORW: + case DHCP6_RELAY_REPL: + return 0; + + case DHCP6_ADVERTISE: + case DHCP6_REPLY: + case DHCP6_RECONFIGURE: + break; + + default: + log_dhcp6_client(client, "unknown message type %d", + message->type); + return 0; + } + + if (client->transaction_id != (message->transaction_id & + htobe32(0x00ffffff))) + return 0; + + switch (client->state) { + case DHCP6_STATE_INFORMATION_REQUEST: + r = client_receive_reply(client, message, len); + if (r < 0) + return 0; + + client_notify(client, SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST); + + client_start(client, DHCP6_STATE_STOPPED); + + break; + + case DHCP6_STATE_SOLICITATION: + r = client_receive_advertise(client, message, len); + + if (r == DHCP6_STATE_REQUEST) { + client_start(client, r); + + break; + } + + /* fall through for Soliciation Rapid Commit option check */ + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + case DHCP6_STATE_REBIND: + + r = client_receive_reply(client, message, len); + if (r < 0) + return 0; + + if (r == DHCP6_STATE_BOUND) { + + r = client_start(client, DHCP6_STATE_BOUND); + if (r < 0) { + client_stop(client, r); + return 0; + } + + client_notify(client, SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); + } + + break; + + case DHCP6_STATE_BOUND: + + break; + + case DHCP6_STATE_STOPPED: + return 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) { + 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->state != state, -EINVAL); + + client->timeout_resend_expire = + sd_event_source_unref(client->timeout_resend_expire); + client->timeout_resend = sd_event_source_unref(client->timeout_resend); + client->retransmit_time = 0; + client->retransmit_count = 0; + + r = sd_event_now(client->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + + switch (state) { + case DHCP6_STATE_STOPPED: + if (client->state == DHCP6_STATE_INFORMATION_REQUEST) { + client->state = DHCP6_STATE_STOPPED; + + return 0; + } + + /* fall through */ + case DHCP6_STATE_SOLICITATION: + client->state = DHCP6_STATE_SOLICITATION; + + break; + + case DHCP6_STATE_INFORMATION_REQUEST: + case DHCP6_STATE_REQUEST: + case DHCP6_STATE_RENEW: + case DHCP6_STATE_REBIND: + + client->state = state; + + break; + + case DHCP6_STATE_BOUND: + + 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", + be32toh(client->lease->ia.lifetime_t1), + be32toh(client->lease->ia.lifetime_t2)); + + return 0; + } + + 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, USEC_PER_SEC)); + + r = sd_event_add_time(client->event, + &client->lease->ia.timeout_t1, + clock_boottime_or_monotonic(), time_now + timeout, + 10 * USEC_PER_SEC, client_timeout_t1, + client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->lease->ia.timeout_t1, + client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(client->lease->ia.timeout_t1, "dhcp6-t1-timeout"); + if (r < 0) + return r; + + 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, USEC_PER_SEC)); + + r = sd_event_add_time(client->event, + &client->lease->ia.timeout_t2, + clock_boottime_or_monotonic(), time_now + timeout, + 10 * USEC_PER_SEC, client_timeout_t2, + client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->lease->ia.timeout_t2, + client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(client->lease->ia.timeout_t2, "dhcp6-t2-timeout"); + if (r < 0) + return r; + + client->state = state; + + return 0; + } + + client->transaction_id = random_u32() & htobe32(0x00ffffff); + client->transaction_start = time_now; + + r = sd_event_add_time(client->event, &client->timeout_resend, + clock_boottime_or_monotonic(), 0, 0, client_timeout_resend, + client); + if (r < 0) + return r; + + r = sd_event_source_set_priority(client->timeout_resend, + client->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(client->timeout_resend, "dhcp6-resend-timeout"); + if (r < 0) + return r; + + return 0; +} + +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_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) { + int r = 0; + enum DHCP6State state = DHCP6_STATE_SOLICITATION; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->index > 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) + return r; + + r = client_ensure_iaid(client); + if (r < 0) + return r; + + r = client_ensure_duid(client); + if (r < 0) + return r; + + r = dhcp6_network_bind_udp_socket(client->index, &client->local_address); + if (r < 0) + return r; + + client->fd = r; + + r = sd_event_add_io(client->event, &client->receive_message, + client->fd, EPOLLIN, client_receive_message, + client); + if (r < 0) + goto error; + + r = sd_event_source_set_priority(client->receive_message, + client->event_priority); + if (r < 0) + goto error; + + r = sd_event_source_set_description(client->receive_message, + "dhcp6-receive-message"); + if (r < 0) + goto error; + + if (client->information_request) + state = DHCP6_STATE_INFORMATION_REQUEST; + + log_dhcp6_client(client, "Started in %s mode", + client->information_request? "Information request": + "Managed"); + + return client_start(client, state); + +error: + client_reset(client); + return r; +} + +int sd_dhcp6_client_attach_event(sd_dhcp6_client *client, sd_event *event, int priority) { + int r; + + assert_return(client, -EINVAL); + assert_return(!client->event, -EBUSY); + + if (event) + client->event = sd_event_ref(event); + else { + r = sd_event_default(&client->event); + if (r < 0) + return 0; + } + + client->event_priority = priority; + + return 0; +} + +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client) { + assert_return(client, -EINVAL); + + client->event = sd_event_unref(client->event); + + return 0; +} + +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client) { + if (!client) + return NULL; + + return client->event; +} + +sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client) { + + 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) + return NULL; + + assert(client->n_ref >= 1); + client->n_ref--; + + if (client->n_ref > 0) + return NULL; + + 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_(sd_dhcp6_client_unrefp) sd_dhcp6_client *client = NULL; + size_t t; + + assert_return(ret, -EINVAL); + + client = new0(sd_dhcp6_client, 1); + if (!client) + return -ENOMEM; + + client->n_ref = 1; + + client->ia_na.type = SD_DHCP6_OPTION_IA_NA; + + client->index = -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; + + for (t = 0; t < client->req_opts_len; t++) + client->req_opts[t] = htobe16(default_req_opts[t]); + + *ret = client; + client = NULL; + + return 0; +} diff --git a/src/libsystemd-network/sd-dhcp6-lease.c b/src/libsystemd-network/sd-dhcp6-lease.c new file mode 100644 index 0000000000..5c10a6326a --- /dev/null +++ b/src/libsystemd-network/sd-dhcp6-lease.c @@ -0,0 +1,410 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + 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 + 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 "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); + + ia->timeout_t1 = sd_event_source_unref(ia->timeout_t1); + ia->timeout_t2 = sd_event_source_unref(ia->timeout_t2); + + return 0; +} + +int dhcp6_lease_ia_rebind_expire(const DHCP6IA *ia, uint32_t *expire) { + DHCP6Address *addr; + uint32_t valid = 0, t; + + assert_return(ia, -EINVAL); + assert_return(expire, -EINVAL); + + LIST_FOREACH(addresses, addr, ia->addresses) { + t = be32toh(addr->iaaddr.lifetime_valid); + if (valid < t) + valid = t; + } + + t = be32toh(ia->lifetime_t2); + if (t > valid) + return -EINVAL; + + *expire = valid - t; + + return 0; +} + +DHCP6IA *dhcp6_lease_free_ia(DHCP6IA *ia) { + DHCP6Address *address; + + if (!ia) + return NULL; + + dhcp6_lease_clear_timers(ia); + + while (ia->addresses) { + address = ia->addresses; + + LIST_REMOVE(addresses, ia->addresses, address); + + free(address); + } + + return NULL; +} + +int dhcp6_lease_set_serverid(sd_dhcp6_lease *lease, const uint8_t *id, + size_t len) { + assert_return(lease, -EINVAL); + assert_return(id, -EINVAL); + + free(lease->serverid); + + lease->serverid = memdup(id, len); + if (!lease->serverid) + return -EINVAL; + + lease->serverid_len = len; + + return 0; +} + +int dhcp6_lease_get_serverid(sd_dhcp6_lease *lease, uint8_t **id, size_t *len) { + assert_return(lease, -EINVAL); + assert_return(id, -EINVAL); + assert_return(len, -EINVAL); + + *id = lease->serverid; + *len = lease->serverid_len; + + return 0; +} + +int dhcp6_lease_set_preference(sd_dhcp6_lease *lease, uint8_t preference) { + assert_return(lease, -EINVAL); + + lease->preference = preference; + + return 0; +} + +int dhcp6_lease_get_preference(sd_dhcp6_lease *lease, uint8_t *preference) { + assert_return(preference, -EINVAL); + + if (!lease) + return -EINVAL; + + *preference = lease->preference; + + return 0; +} + +int dhcp6_lease_set_rapid_commit(sd_dhcp6_lease *lease) { + assert_return(lease, -EINVAL); + + lease->rapid_commit = true; + + return 0; +} + +int dhcp6_lease_get_rapid_commit(sd_dhcp6_lease *lease, bool *rapid_commit) { + assert_return(lease, -EINVAL); + assert_return(rapid_commit, -EINVAL); + + *rapid_commit = lease->rapid_commit; + + return 0; +} + +int dhcp6_lease_get_iaid(sd_dhcp6_lease *lease, be32_t *iaid) { + assert_return(lease, -EINVAL); + assert_return(iaid, -EINVAL); + + *iaid = lease->ia.id; + + return 0; +} + +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid) { + assert_return(lease, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(lifetime_preferred, -EINVAL); + assert_return(lifetime_valid, -EINVAL); + + if (!lease->addr_iter) + return -ENOMSG; + + memcpy(addr, &lease->addr_iter->iaaddr.address, + sizeof(struct in6_addr)); + *lifetime_preferred = + be32toh(lease->addr_iter->iaaddr.lifetime_preferred); + *lifetime_valid = be32toh(lease->addr_iter->iaaddr.lifetime_valid); + + lease->addr_iter = lease->addr_iter->addresses_next; + + return 0; +} + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease) { + if (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) + 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) + 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; +} + +int dhcp6_lease_new(sd_dhcp6_lease **ret) { + sd_dhcp6_lease *lease; + + lease = new0(sd_dhcp6_lease, 1); + if (!lease) + return -ENOMEM; + + lease->n_ref = 1; + + LIST_HEAD_INIT(lease->ia.addresses); + + *ret = lease; + return 0; +} diff --git a/src/libsystemd-network/sd-ipv4acd.c b/src/libsystemd-network/sd-ipv4acd.c new file mode 100644 index 0000000000..f97fc0bf47 --- /dev/null +++ b/src/libsystemd-network/sd-ipv4acd.c @@ -0,0 +1,527 @@ +/*** + 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 <systemd/sd-ipv4acd.h> + +#include "alloc-util.h" +#include "arp-util.h" +#include "fd-util.h" +#include "in-addr-util.h" +#include "list.h" +#include "random-util.h" +#include "refcnt.h" +#include "siphash24.h" +#include "util.h" + +/* 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 IPV4ACD_NETWORK 0xA9FE0000L +#define IPV4ACD_NETMASK 0xFFFF0000L + +#define log_ipv4acd_full(ll, level, error, fmt, ...) log_internal(level, error, __FILE__, __LINE__, __func__, "ACD: " fmt, ##__VA_ARGS__) + +#define log_ipv4acd_debug(ll, ...) log_ipv4acd_full(ll, LOG_DEBUG, 0, ##__VA_ARGS__) +#define log_ipv4acd_info(ll, ...) log_ipv4acd_full(ll, LOG_INFO, 0, ##__VA_ARGS__) +#define log_ipv4acd_notice(ll, ...) log_ipv4acd_full(ll, LOG_NOTICE, 0, ##__VA_ARGS__) +#define log_ipv4acd_warning(ll, ...) log_ipv4acd_full(ll, LOG_WARNING, 0, ##__VA_ARGS__) +#define log_ipv4acd_error(ll, ...) log_ipv4acd_full(ll, LOG_ERR, 0, ##__VA_ARGS__) + +#define log_ipv4acd_debug_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_DEBUG, error, ##__VA_ARGS__) +#define log_ipv4acd_info_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_INFO, error, ##__VA_ARGS__) +#define log_ipv4acd_notice_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_NOTICE, error, ##__VA_ARGS__) +#define log_ipv4acd_warning_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_WARNING, error, ##__VA_ARGS__) +#define log_ipv4acd_error_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_ERR, error, ##__VA_ARGS__) + +typedef enum IPv4ACDState { + IPV4ACD_STATE_INIT, + 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 { + RefCount n_ref; + + IPv4ACDState state; + int index; + int fd; + int iteration; + int conflict; + sd_event_source *receive_message; + sd_event_source *timer; + usec_t defend_window; + be32_t address; + /* External */ + struct ether_addr mac_addr; + sd_event *event; + int event_priority; + sd_ipv4acd_cb_t cb; + void* userdata; +}; + +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll) { + if (ll) + assert_se(REFCNT_INC(ll->n_ref) >= 2); + + return ll; +} + +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll) { + if (!ll || REFCNT_DEC(ll->n_ref) > 0) + return NULL; + + 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_ipv4acd_detach_event(ll); + + free(ll); + + return NULL; +} + +int sd_ipv4acd_new(sd_ipv4acd **ret) { + _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *ll = NULL; + + assert_return(ret, -EINVAL); + + ll = new0(sd_ipv4acd, 1); + if (!ll) + return -ENOMEM; + + ll->n_ref = REFCNT_INIT; + ll->state = IPV4ACD_STATE_INIT; + ll->index = -1; + ll->fd = -1; + + *ret = ll; + ll = NULL; + + return 0; +} + +static void ipv4acd_set_state(sd_ipv4acd *ll, IPv4ACDState st, bool reset_counter) { + + assert(ll); + assert(st < _IPV4ACD_STATE_MAX); + + if (st == ll->state && !reset_counter) + ll->iteration++; + else { + ll->state = st; + ll->iteration = 0; + } +} + +static void ipv4acd_client_notify(sd_ipv4acd *ll, int event) { + assert(ll); + + if (ll->cb) + ll->cb(ll, event, ll->userdata); +} + +static void ipv4acd_stop(sd_ipv4acd *ll) { + 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); + + log_ipv4acd_debug(ll, "STOPPED"); + + ipv4acd_set_state (ll, IPV4ACD_STATE_INIT, true); +} + +int sd_ipv4acd_stop(sd_ipv4acd *ll) { + assert_return(ll, -EINVAL); + + ipv4acd_stop(ll); + + ipv4acd_client_notify(ll, 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 *ll, int sec, int random_sec) { + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + usec_t next_timeout; + usec_t time_now; + int r; + + 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); + + assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now) >= 0); + + r = sd_event_add_time(ll->event, &timer, clock_boottime_or_monotonic(), + time_now + next_timeout, 0, ipv4acd_on_timeout, ll); + if (r < 0) + return r; + + r = sd_event_source_set_priority(timer, ll->event_priority); + if (r < 0) + return r; + + r = sd_event_source_set_description(timer, "ipv4acd-timer"); + if (r < 0) + return r; + + ll->timer = sd_event_source_unref(ll->timer); + ll->timer = timer; + timer = NULL; + + return 0; +} + +static bool ipv4acd_arp_conflict(sd_ipv4acd *ll, struct ether_arp *arp) { + assert(ll); + assert(arp); + + /* see the BPF */ + if (memcmp(arp->arp_spa, &ll->address, sizeof(ll->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 *ll = userdata; + int r = 0; + + assert(ll); + + switch (ll->state) { + case IPV4ACD_STATE_INIT: + + ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_PROBE, true); + + if (ll->conflict >= MAX_CONFLICTS) { + log_ipv4acd_notice(ll, "Max conflicts reached, delaying by %us", RATE_LIMIT_INTERVAL); + r = ipv4acd_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT); + if (r < 0) + goto out; + + ll->conflict = 0; + } else { + r = ipv4acd_set_next_wakeup(ll, 0, PROBE_WAIT); + if (r < 0) + goto out; + } + + break; + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + /* Send a probe */ + r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr); + if (r < 0) { + log_ipv4acd_error_errno(ll, r, "Failed to send ARP probe: %m"); + goto out; + } else { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = ll->address }; + + r = in_addr_to_string(AF_INET, &addr, &address); + if (r >= 0) + log_ipv4acd_debug(ll, "Probing %s", address); + } + + if (ll->iteration < PROBE_NUM - 2) { + ipv4acd_set_state(ll, IPV4ACD_STATE_PROBING, false); + + r = ipv4acd_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN)); + if (r < 0) + goto out; + } else { + ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_ANNOUNCE, true); + + r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_WAIT, 0); + if (r < 0) + goto out; + } + + break; + + case IPV4ACD_STATE_ANNOUNCING: + if (ll->iteration >= ANNOUNCE_NUM - 1) { + ipv4acd_set_state(ll, IPV4ACD_STATE_RUNNING, false); + + break; + } + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* Send announcement packet */ + r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); + if (r < 0) { + log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m"); + goto out; + } else + log_ipv4acd_debug(ll, "ANNOUNCE"); + + ipv4acd_set_state(ll, IPV4ACD_STATE_ANNOUNCING, false); + + r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0); + if (r < 0) + goto out; + + if (ll->iteration == 0) { + ll->conflict = 0; + ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_BIND); + } + + break; + default: + assert_not_reached("Invalid state."); + } + +out: + if (r < 0) + sd_ipv4acd_stop(ll); + + return 1; +} + +static void ipv4acd_on_conflict(sd_ipv4acd *ll) { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = ll->address }; + int r; + + assert(ll); + + ll->conflict++; + + r = in_addr_to_string(AF_INET, &addr, &address); + if (r >= 0) + log_ipv4acd_debug(ll, "Conflict on %s (%u)", address, ll->conflict); + + ipv4acd_stop(ll); + + ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_CONFLICT); +} + +static int ipv4acd_on_packet(sd_event_source *s, int fd, + uint32_t revents, void *userdata) { + sd_ipv4acd *ll = userdata; + struct ether_arp packet; + int r; + + assert(ll); + assert(fd >= 0); + + r = read(fd, &packet, sizeof(struct ether_arp)); + if (r < (int) sizeof(struct ether_arp)) + goto out; + + switch (ll->state) { + case IPV4ACD_STATE_ANNOUNCING: + case IPV4ACD_STATE_RUNNING: + if (ipv4acd_arp_conflict(ll, &packet)) { + usec_t ts; + + assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* Defend address */ + if (ts > ll->defend_window) { + ll->defend_window = ts + DEFEND_INTERVAL * USEC_PER_SEC; + r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr); + if (r < 0) { + log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m"); + goto out; + } else + log_ipv4acd_debug(ll, "DEFEND"); + + } else + ipv4acd_on_conflict(ll); + } + + 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(ll); + + break; + default: + assert_not_reached("Invalid state."); + } + +out: + if (r < 0) + sd_ipv4acd_stop(ll); + + return 1; +} + +int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index) { + assert_return(ll, -EINVAL); + assert_return(interface_index > 0, -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + ll->index = interface_index; + + return 0; +} + +int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr) { + assert_return(ll, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + memcpy(&ll->mac_addr, addr, ETH_ALEN); + + return 0; +} + +int sd_ipv4acd_detach_event(sd_ipv4acd *ll) { + assert_return(ll, -EINVAL); + + ll->event = sd_event_unref(ll->event); + + return 0; +} + +int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int priority) { + int r; + + 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) + return r; + } + + ll->event_priority = priority; + + return 0; +} + +int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->cb = cb; + ll->userdata = userdata; + + return 0; +} + +int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address){ + assert_return(ll, -EINVAL); + assert_return(address, -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + ll->address = address->s_addr; + + return 0; +} + +int sd_ipv4acd_is_running(sd_ipv4acd *ll) { + assert_return(ll, false); + + return ll->state != IPV4ACD_STATE_INIT; +} + +static bool ether_addr_is_nul(const struct ether_addr *addr) { + const struct ether_addr nul_addr = {}; + + assert(addr); + + return memcmp(addr, &nul_addr, sizeof(struct ether_addr)) == 0; +} + +#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + +int sd_ipv4acd_start(sd_ipv4acd *ll) { + int r; + + assert_return(ll, -EINVAL); + assert_return(ll->event, -EINVAL); + assert_return(ll->index > 0, -EINVAL); + assert_return(ll->address != 0, -EINVAL); + assert_return(!ether_addr_is_nul(&ll->mac_addr), -EINVAL); + assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY); + + ll->defend_window = 0; + + r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr); + if (r < 0) + goto out; + + ll->fd = safe_close(ll->fd); + ll->fd = r; + + r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd, + EPOLLIN, ipv4acd_on_packet, ll); + if (r < 0) + goto out; + + r = sd_event_source_set_priority(ll->receive_message, ll->event_priority); + if (r < 0) + goto out; + + r = sd_event_source_set_description(ll->receive_message, "ipv4acd-receive-message"); + if (r < 0) + goto out; + + r = ipv4acd_set_next_wakeup(ll, 0, 0); + if (r < 0) + goto out; +out: + if (r < 0) { + ipv4acd_stop(ll); + return r; + } + + return 0; +} diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c new file mode 100644 index 0000000000..7725b67548 --- /dev/null +++ b/src/libsystemd-network/sd-ipv4ll.c @@ -0,0 +1,364 @@ +/*** + 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 <systemd/sd-ipv4acd.h> +#include <systemd/sd-ipv4ll.h> + +#include "alloc-util.h" +#include "in-addr-util.h" +#include "list.h" +#include "random-util.h" +#include "refcnt.h" +#include "siphash24.h" +#include "sparse-endian.h" +#include "util.h" + +#define IPV4LL_NETWORK 0xA9FE0000L +#define IPV4LL_NETMASK 0xFFFF0000L + +#define IPV4LL_DONT_DESTROY(ll) \ + _cleanup_(sd_ipv4ll_unrefp) _unused_ sd_ipv4ll *_dont_destroy_##ll = sd_ipv4ll_ref(ll) + +struct sd_ipv4ll { + unsigned n_ref; + + sd_ipv4acd *acd; + be32_t address; /* the address pushed to ACD */ + struct random_data *random_data; + char *random_data_state; + + /* External */ + be32_t claimed_address; + sd_ipv4ll_cb_t cb; + void* userdata; +}; + +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll) { + if (!ll) + return NULL; + + assert(ll->n_ref >= 1); + ll->n_ref++; + + return ll; +} + +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll) { + if (!ll) + return NULL; + + assert(ll->n_ref >= 1); + ll->n_ref--; + + if (ll->n_ref > 0) + return NULL; + + sd_ipv4acd_unref(ll->acd); + + free(ll->random_data); + free(ll->random_data_state); + free(ll); + + return NULL; +} + +static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, void *userdata); + +int sd_ipv4ll_new(sd_ipv4ll **ret) { + _cleanup_(sd_ipv4ll_unrefp) sd_ipv4ll *ll = NULL; + int r; + + assert_return(ret, -EINVAL); + + ll = new0(sd_ipv4ll, 1); + if (!ll) + return -ENOMEM; + + ll->n_ref = 1; + + r = sd_ipv4acd_new(&ll->acd); + if (r < 0) + return r; + + r = sd_ipv4acd_set_callback(ll->acd, ipv4ll_on_acd, ll); + if (r < 0) + return r; + + *ret = ll; + ll = NULL; + + return 0; +} + +int sd_ipv4ll_stop(sd_ipv4ll *ll) { + int r; + + assert_return(ll, -EINVAL); + + r = sd_ipv4acd_stop(ll->acd); + if (r < 0) + return r; + + return 0; +} + +int sd_ipv4ll_set_index(sd_ipv4ll *ll, int interface_index) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_set_index(ll->acd, interface_index); +} + +#define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { + int r; + + assert_return(ll, -EINVAL); + + if (!ll->random_data) { + uint64_t seed; + + /* If no random data is set, generate some from the MAC */ + seed = siphash24(&addr->ether_addr_octet, ETH_ALEN, HASH_KEY.bytes); + + assert_cc(sizeof(unsigned) <= 8); + + r = sd_ipv4ll_set_address_seed(ll, (unsigned) htole64(seed)); + if (r < 0) + return r; + } + + return sd_ipv4acd_set_mac(ll->acd, addr); +} + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_detach_event(ll->acd); +} + +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int priority) { + int r; + + assert_return(ll, -EINVAL); + + r = sd_ipv4acd_attach_event(ll->acd, event, priority); + if (r < 0) + return r; + + return 0; +} + +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_cb_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->cb = cb; + ll->userdata = userdata; + + return 0; +} + +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) + return -ENOENT; + + address->s_addr = ll->claimed_address; + + return 0; +} + +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, unsigned seed) { + _cleanup_free_ struct random_data *random_data = NULL; + _cleanup_free_ char *random_data_state = NULL; + int r; + + assert_return(ll, -EINVAL); + + random_data = new0(struct random_data, 1); + if (!random_data) + return -ENOMEM; + + random_data_state = new0(char, 128); + if (!random_data_state) + return -ENOMEM; + + r = initstate_r(seed, random_data_state, 128, random_data); + if (r < 0) + return r; + + free(ll->random_data); + ll->random_data = random_data; + random_data = NULL; + + free(ll->random_data_state); + ll->random_data_state = random_data_state; + random_data_state = NULL; + + return 0; +} + +int sd_ipv4ll_is_running(sd_ipv4ll *ll) { + assert_return(ll, false); + + return sd_ipv4acd_is_running(ll->acd); +} + +static bool ipv4ll_address_is_valid(const struct in_addr *address) { + uint32_t addr; + + assert(address); + + if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address)) + return false; + + addr = be32toh(address->s_addr); + + if ((addr & 0x0000FF00) == 0x0000 || + (addr & 0x0000FF00) == 0xFF00) + return false; + + return true; +} + +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); + + r = sd_ipv4acd_set_address(ll->acd, address); + if (r < 0) + return r; + + ll->address = address->s_addr; + + return 0; +} + +static int ipv4ll_pick_address(sd_ipv4ll *ll) { + struct in_addr in_addr; + be32_t addr; + int r; + int32_t random; + + assert(ll); + assert(ll->random_data); + + 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) & 0x0000FF00) == 0x0000 || + (ntohl(addr) & 0x0000FF00) == 0xFF00); + + in_addr.s_addr = addr; + + r = sd_ipv4ll_set_address(ll, &in_addr); + if (r < 0) + return r; + + return 0; +} + +int sd_ipv4ll_start(sd_ipv4ll *ll) { + int r; + + assert_return(ll, -EINVAL); + assert_return(ll->random_data, -EINVAL); + + if (ll->address == 0) { + r = ipv4ll_pick_address(ll); + if (r < 0) + return r; + } + + r = sd_ipv4acd_start(ll->acd); + if (r < 0) + return r; + + return 0; +} + +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { + assert(ll); + + if (ll->cb) + ll->cb(ll, event, ll->userdata); +} + +void ipv4ll_on_acd(sd_ipv4acd *acd, int event, void *userdata) { + sd_ipv4ll *ll = userdata; + IPV4LL_DONT_DESTROY(ll); + int r; + + assert(acd); + assert(ll); + + switch (event) { + case SD_IPV4ACD_EVENT_STOP: + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_STOP); + + ll->claimed_address = 0; + + break; + case SD_IPV4ACD_EVENT_BIND: + ll->claimed_address = ll->address; + ipv4ll_client_notify(ll, SD_IPV4LL_EVENT_BIND); + + break; + 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); + + ll->claimed_address = 0; + } else { + r = ipv4ll_pick_address(ll); + if (r < 0) + goto error; + + r = sd_ipv4acd_start(ll->acd); + if (r < 0) + goto error; + } + + break; + default: + assert_not_reached("Invalid IPv4ACD event."); + } + + return; + +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 new file mode 100644 index 0000000000..6bb2c7797b --- /dev/null +++ b/src/libsystemd-network/sd-lldp.c @@ -0,0 +1,756 @@ +/*** + 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 <arpa/inet.h> + +#include <systemd/sd-lldp.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "hashmap.h" +#include "lldp-internal.h" +#include "lldp-port.h" +#include "lldp-tlv.h" +#include "prioq.h" +#include "siphash24.h" +#include "string-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 void chassis_id_hash_func(const void *p, struct siphash *state) { + const lldp_chassis_id *id = p; + + assert(id); + assert(id->data); + + siphash24_compress(&id->length, sizeof(id->length), state); + siphash24_compress(id->data, id->length, state); +} + +static int chassis_id_compare_func(const void *_a, const void *_b) { + const lldp_chassis_id *a, *b; + + a = _a; + b = _b; + + assert(!a->length || a->data); + assert(!b->length || b->data); + + if (a->type != b->type) + return -1; + + if (a->length != b->length) + return a->length < b->length ? -1 : 1; + + return memcmp(a->data, b->data, a->length); +} + +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; + + assert(lldp); + assert(tlv); + + /* Remove expired packets */ + if (prioq_size(lldp->by_expiry) > 0) { + + lldp_set_state(lldp, LLDP_AGENT_RX_DELETE_INFO); + + 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); + + return 0; +} + +/* 10.3.2 LLDPDU validation: rxProcessFrame() */ +int lldp_handle_packet(tlv_packet *tlv, uint16_t length) { + bool system_description = false, system_name = false, chassis_id = false; + bool malformed = false, port_id = false, ttl = false, end = false; + uint16_t type, len, i, l, t; + lldp_port *port; + uint8_t *p, *q; + sd_lldp *lldp; + int r; + + assert(tlv); + assert(length > 0); + + port = (lldp_port *) tlv->userdata; + lldp = (sd_lldp *) port->userdata; + + if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) { + log_lldp("Port: %s is disabled. Dropping.", lldp->port->ifname); + goto out; + } + + lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME); + + p = tlv->pdu; + p += sizeof(struct ether_header); + + for (i = 1, l = 0; l <= length; i++) { + + memcpy(&t, p, sizeof(uint16_t)); + + type = ntohs(t) >> 9; + len = ntohs(t) & 0x01ff; + + if (type == LLDP_TYPE_END) { + if (len != 0) { + log_lldp("TLV type end must be length 0 (not %d). Dropping.", len); + + malformed = true; + goto out; + } + + end = true; + + break; + } else if (type >=_LLDP_TYPE_MAX) { + log_lldp("TLV type: %d not recognized. Dropping.", type); + + malformed = true; + goto out; + } + + /* skip type and length encoding */ + p += 2; + q = p; + + p += len; + l += (len + 2); + + if (i <= 3) { + if (i != type) { + log_lldp("TLV missing or out of order. Dropping."); + + malformed = true; + goto out; + } + } + + switch(type) { + case LLDP_TYPE_CHASSIS_ID: + + if (len < 2) { + log_lldp("Received malformed Chassis ID TLV length: %d. Dropping.", len); + + malformed = true; + goto out; + } + + if (chassis_id) { + log_lldp("Duplicate Chassis ID TLV found. Dropping."); + + malformed = true; + goto out; + } + + /* 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); + + malformed = true; + goto out; + + } + + chassis_id = true; + + break; + case LLDP_TYPE_PORT_ID: + + if (len < 2) { + log_lldp("Received malformed Port ID TLV length: %d. Dropping.", len); + + malformed = true; + goto out; + } + + if (port_id) { + log_lldp("Duplicate Port ID TLV found. Dropping."); + + malformed = true; + goto out; + } + + /* 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); + + malformed = true; + goto out; + + } + + port_id = true; + + break; + case LLDP_TYPE_TTL: + + if(len != 2) { + log_lldp("Received invalid TTL TLV lenth: %d. Dropping.", len); + + malformed = true; + goto out; + } + + if (ttl) { + log_lldp("Duplicate TTL TLV found. Dropping."); + + malformed = true; + goto out; + } + + ttl = true; + + break; + case LLDP_TYPE_SYSTEM_NAME: + + /* According to RFC 1035 the length of a FQDN is limited to 255 characters */ + if (len > 255) { + log_lldp("Received invalid system name length: %d. Dropping.", len); + malformed = true; + goto out; + } + + if (system_name) { + log_lldp("Duplicate system name found. Dropping."); + malformed = true; + goto out; + } + + system_name = true; + + break; + case LLDP_TYPE_SYSTEM_DESCRIPTION: + + /* 0 <= n <= 255 octets */ + if (len > 255) { + log_lldp("Received invalid system description length: %d. Dropping.", len); + malformed = true; + goto out; + } + + if (system_description) { + log_lldp("Duplicate system description found. Dropping."); + malformed = true; + goto out; + } + + system_description = true; + break; + default: + + if (len == 0) { + log_lldp("TLV type: %d length 0 received. Dropping.", type); + + malformed = true; + goto out; + } + break; + } + } + + if(!chassis_id || !port_id || !ttl || !end) { + log_lldp("One or more mandatory TLV missing. Dropping."); + + malformed = true; + goto out; + + } + + r = tlv_packet_parse_pdu(tlv, length); + if (r < 0) { + log_lldp("Failed to parse the TLV. Dropping."); + + malformed = true; + goto out; + } + + return lldp_receive_frame(lldp, tlv); + + out: + lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME); + + if (malformed) { + lldp->statistics.stats_frames_discarded_total ++; + lldp->statistics.stats_frames_in_errors_total ++; + } + + sd_lldp_packet_unref(tlv); + + return 0; +} + +static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) { + const lldp_neighbour_port *p = a, *q = b; + + if (p->until < q->until) + return -1; + + if (p->until > q->until) + return 1; + + return 0; +} + +static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) { + + assert(lldp); + assert(state < _LLDP_AGENT_RX_STATE_MAX); + + lldp->rx_state = state; + + lldp_run_state_machine(lldp); +} + +static void lldp_run_state_machine(sd_lldp *lldp) { + if (!lldp->cb) + return; + + switch (lldp->rx_state) { + case LLDP_AGENT_RX_UPDATE_INFO: + lldp->cb(lldp, SD_LLDP_EVENT_UPDATE_INFO, lldp->userdata); + break; + default: + break; + } +} + +/* 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; + + /* Remove all entries that are past their TTL */ + for (;;) { + + if (prioq_size(lldp->by_expiry) <= 0) + break; + + p = prioq_peek(lldp->by_expiry); + if (!p) + break; + + if (t <= 0) + t = now(clock_boottime_or_monotonic()); + + if (p->until > t) + break; + + lldp_neighbour_port_remove_and_free(p); + + lldp->statistics.stats_ageouts_total ++; + } +} + +static void lldp_mib_objects_flush(sd_lldp *lldp) { + lldp_neighbour_port *p, *q; + lldp_chassis *c; + + assert(lldp); + assert(lldp->neighbour_mib); + assert(lldp->by_expiry); + + /* Drop all packets */ + while ((c = hashmap_steal_first(lldp->neighbour_mib))) { + + LIST_FOREACH_SAFE(port, p, q, c->ports) { + lldp_neighbour_port_remove_and_free(p); + } + } + + assert(hashmap_size(lldp->neighbour_mib) == 0); + assert(prioq_size(lldp->by_expiry) == 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; + + assert(lldp); + assert(lldp_file); + + r = fopen_temporary(lldp_file, &f, &temp_path); + if (r < 0) + goto fail; + + fchmod(fileno(f), 0644); + + HASHMAP_FOREACH(c, lldp->neighbour_mib, i) { + LIST_FOREACH(port, p, c->ports) { + _cleanup_free_ char *s = NULL; + char *k, *t; + + r = sd_lldp_packet_read_chassis_id(p->packet, &type, &mac, &length); + if (r < 0) + continue; + + sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type); + + s = strdup(buf); + if (!s) { + r = -ENOMEM; + goto fail; + } + + r = sd_lldp_packet_read_port_id(p->packet, &type, &port_id, &length); + if (r < 0) + continue; + + if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) { + k = strndup((char *) port_id, length -1); + if (!k) { + r = -ENOMEM; + goto fail; + } + + 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); + } + + k = strappend(s, buf); + if (!k) { + r = -ENOMEM; + goto fail; + } + + free(s); + s = k; + + time = now(clock_boottime_or_monotonic()); + + /* Don't write expired packets */ + if (time - p->until <= 0) + continue; + + sprintf(buf, "'_TTL="USEC_FMT"' ", p->until); + + k = strappend(s, buf); + if (!k) { + r = -ENOMEM; + goto fail; + } + + free(s); + s = k; + + r = sd_lldp_packet_read_system_name(p->packet, &k, &length); + if (r < 0) + k = strappend(s, "'_NAME=N/A' "); + else { + t = strndup(k, length); + if (!t) { + r = -ENOMEM; + goto fail; + } + + k = strjoin(s, "'_NAME=", t, "' ", NULL); + free(t); + } + + if (!k) { + r = -ENOMEM; + goto fail; + } + + free(s); + s = k; + + (void) sd_lldp_packet_read_system_capability(p->packet, &data); + + sprintf(buf, "'_CAP=%x'", data); + + k = strappend(s, buf); + if (!k) { + r = -ENOMEM; + goto fail; + } + + free(s); + s = k; + + fprintf(f, "%s\n", s); + } + } + + r = fflush_and_check(f); + if (r < 0) + goto fail; + + if (rename(temp_path, lldp_file) < 0) { + r = -errno; + goto fail; + } + + return 0; + + fail: + if (temp_path) + (void) unlink(temp_path); + + return log_error_errno(r, "Failed to save lldp data %s: %m", lldp_file); +} + +int sd_lldp_start(sd_lldp *lldp) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->port, -EINVAL); + + lldp->port->status = LLDP_PORT_STATUS_ENABLED; + + lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE); + + r = lldp_port_start(lldp->port); + if (r < 0) { + log_lldp("Failed to start Port : %s , %s", + lldp->port->ifname, + strerror(-r)); + + lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL); + + return r; + } + + lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME); + + return 0; +} + +int sd_lldp_stop(sd_lldp *lldp) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->port, -EINVAL); + + lldp->port->status = LLDP_PORT_STATUS_DISABLED; + + r = lldp_port_stop(lldp->port); + if (r < 0) + return r; + + lldp_mib_objects_flush(lldp); + + return 0; +} + +int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(!lldp->port->event, -EBUSY); + + if (event) + lldp->port->event = sd_event_ref(event); + else { + r = sd_event_default(&lldp->port->event); + if (r < 0) + return r; + } + + lldp->port->event_priority = priority; + + return 0; +} + +int sd_lldp_detach_event(sd_lldp *lldp) { + + assert_return(lldp, -EINVAL); + + lldp->port->event = sd_event_unref(lldp->port->event); + + return 0; +} + +int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) { + assert_return(lldp, -EINVAL); + + lldp->cb = cb; + lldp->userdata = userdata; + + return 0; +} + +sd_lldp* sd_lldp_unref(sd_lldp *lldp) { + + if (!lldp) + return NULL; + + /* Drop all packets */ + lldp_mib_objects_flush(lldp); + + lldp_port_free(lldp->port); + + hashmap_free(lldp->neighbour_mib); + prioq_free(lldp->by_expiry); + + free(lldp); + return NULL; +} + +int sd_lldp_new(int ifindex, + const char *ifname, + const struct ether_addr *mac, + sd_lldp **ret) { + _cleanup_(sd_lldp_unrefp) 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; + + lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL; + + *ret = lldp; + lldp = NULL; + + return 0; +} + +int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs) { + lldp_neighbour_port *p; + lldp_chassis *c; + Iterator iter; + unsigned count = 0, i; + + assert_return(lldp, -EINVAL); + assert_return(tlvs, -EINVAL); + + HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) { + LIST_FOREACH(port, p, c->ports) + count++; + } + + if (!count) { + *tlvs = NULL; + return 0; + } + + *tlvs = new(sd_lldp_packet *, count); + if (!*tlvs) + return -ENOMEM; + + i = 0; + HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) { + LIST_FOREACH(port, p, c->ports) + (*tlvs)[i++] = sd_lldp_packet_ref(p->packet); + } + + return count; +} diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c new file mode 100644 index 0000000000..cd07fc1b3f --- /dev/null +++ b/src/libsystemd-network/sd-ndisc.c @@ -0,0 +1,713 @@ +/*** + 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 <netinet/ip6.h> +#include <stdbool.h> +#include <string.h> +#include <sys/ioctl.h> + +#include <systemd/sd-ndisc.h> + +#include "alloc-util.h" +#include "async.h" +#include "icmp6-util.h" +#include "in-addr-util.h" +#include "list.h" +#include "socket-util.h" +#include "string-util.h" + +#define NDISC_ROUTER_SOLICITATION_INTERVAL 4 * USEC_PER_SEC +#define NDISC_MAX_ROUTER_SOLICITATIONS 3 + +enum NDiscState { + NDISC_STATE_IDLE, + NDISC_STATE_SOLICITATION_SENT, + NDISC_STATE_ADVERTISMENT_LISTEN, + _NDISC_STATE_MAX, + _NDISC_STATE_INVALID = -1, +}; + +#define IP6_MIN_MTU (unsigned)1280 +#define ICMP6_RECV_SIZE (IP6_MIN_MTU - sizeof(struct ip6_hdr)) +#define NDISC_OPT_LEN_UNITS 8 + +#define ND_RA_FLAG_PREF 0x18 +#define ND_RA_FLAG_PREF_LOW 0x03 +#define ND_RA_FLAG_PREF_MEDIUM 0x0 +#define ND_RA_FLAG_PREF_HIGH 0x1 +#define ND_RA_FLAG_PREF_INVALID 0x2 + +typedef struct NDiscPrefix NDiscPrefix; + +struct NDiscPrefix { + unsigned n_ref; + + sd_ndisc *nd; + + LIST_FIELDS(NDiscPrefix, prefixes); + + uint8_t len; + usec_t valid_until; + struct in6_addr addr; +}; + +struct sd_ndisc { + unsigned n_ref; + + enum NDiscState state; + sd_event *event; + int event_priority; + int index; + struct ether_addr mac_addr; + uint32_t mtu; + LIST_HEAD(NDiscPrefix, prefixes); + int fd; + sd_event_source *recv; + sd_event_source *timeout; + int nd_sent; + sd_ndisc_router_callback_t router_callback; + sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback; + sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback; + sd_ndisc_callback_t callback; + void *userdata; +}; + +#define log_ndisc(p, fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "NDisc CLIENT: " fmt, ##__VA_ARGS__) + +static NDiscPrefix *ndisc_prefix_unref(NDiscPrefix *prefix) { + + if (!prefix) + return NULL; + + assert(prefix->n_ref > 0); + prefix->n_ref--; + + if (prefix->n_ref > 0) + return NULL; + + if (prefix->nd) + LIST_REMOVE(prefixes, prefix->nd->prefixes, prefix); + + free(prefix); + + return NULL; +} + +static int ndisc_prefix_new(sd_ndisc *nd, NDiscPrefix **ret) { + NDiscPrefix *prefix; + + assert(ret); + + prefix = new0(NDiscPrefix, 1); + if (!prefix) + return -ENOMEM; + + prefix->n_ref = 1; + LIST_INIT(prefixes, prefix); + prefix->nd = nd; + + *ret = prefix; + return 0; +} + +int sd_ndisc_set_callback(sd_ndisc *nd, + sd_ndisc_router_callback_t router_callback, + sd_ndisc_prefix_onlink_callback_t prefix_onlink_callback, + sd_ndisc_prefix_autonomous_callback_t prefix_autonomous_callback, + sd_ndisc_callback_t callback, + void *userdata) { + assert(nd); + + nd->router_callback = router_callback; + nd->prefix_onlink_callback = prefix_onlink_callback; + nd->prefix_autonomous_callback = prefix_autonomous_callback; + nd->callback = callback; + nd->userdata = userdata; + + return 0; +} + +int sd_ndisc_set_index(sd_ndisc *nd, int interface_index) { + assert(nd); + assert(interface_index >= -1); + + nd->index = interface_index; + + return 0; +} + +int sd_ndisc_set_mac(sd_ndisc *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_ndisc_attach_event(sd_ndisc *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_ndisc_detach_event(sd_ndisc *nd) { + assert_return(nd, -EINVAL); + + nd->event = sd_event_unref(nd->event); + + return 0; +} + +sd_event *sd_ndisc_get_event(sd_ndisc *nd) { + assert(nd); + + return nd->event; +} + +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_init(sd_ndisc *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_ndisc *sd_ndisc_unref(sd_ndisc *nd) { + NDiscPrefix *prefix, *p; + + if (!nd) + return NULL; + + assert(nd->n_ref > 0); + nd->n_ref--; + + if (nd->n_ref > 0) + return NULL; + + ndisc_init(nd); + sd_ndisc_detach_event(nd); + + LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) + prefix = ndisc_prefix_unref(prefix); + + free(nd); + + return NULL; +} + +int sd_ndisc_new(sd_ndisc **ret) { + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + + assert(ret); + + nd = new0(sd_ndisc, 1); + if (!nd) + return -ENOMEM; + + nd->n_ref = 1; + + nd->index = -1; + nd->fd = -1; + + LIST_HEAD_INIT(nd->prefixes); + + *ret = nd; + nd = NULL; + + return 0; +} + +int sd_ndisc_get_mtu(sd_ndisc *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 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 ndisc_prefix_match(sd_ndisc *nd, const struct in6_addr *addr, + uint8_t addr_len, NDiscPrefix **result) { + NDiscPrefix *prefix, *p; + usec_t time_now; + int r; + + assert(nd); + + r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + + LIST_FOREACH_SAFE(prefixes, prefix, p, nd->prefixes) { + if (prefix->valid_until < time_now) { + prefix = ndisc_prefix_unref(prefix); + continue; + } + + if (prefix_match(&prefix->addr, prefix->len, addr, addr_len) >= 0) { + *result = prefix; + return 0; + } + } + + return -EADDRNOTAVAIL; +} + +static int ndisc_prefix_update(sd_ndisc *nd, ssize_t len, + const struct nd_opt_prefix_info *prefix_opt) { + NDiscPrefix *prefix; + uint32_t lifetime_valid, lifetime_preferred; + usec_t time_now; + char time_string[FORMAT_TIMESPAN_MAX]; + int r; + + assert(nd); + assert(prefix_opt); + + if (len < prefix_opt->nd_opt_pi_len) + return -ENOMSG; + + if (!(prefix_opt->nd_opt_pi_flags_reserved & (ND_OPT_PI_FLAG_ONLINK | ND_OPT_PI_FLAG_AUTO))) + return 0; + + if (in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &prefix_opt->nd_opt_pi_prefix) > 0) + return 0; + + lifetime_valid = be32toh(prefix_opt->nd_opt_pi_valid_time); + lifetime_preferred = be32toh(prefix_opt->nd_opt_pi_preferred_time); + + if (lifetime_valid < lifetime_preferred) + return 0; + + r = ndisc_prefix_match(nd, &prefix_opt->nd_opt_pi_prefix, + prefix_opt->nd_opt_pi_prefix_len, &prefix); + if (r < 0) { + if (r != -EADDRNOTAVAIL) + return r; + + /* if router advertisment prefix valid timeout is zero, the timeout + callback will be called immediately to clean up the prefix */ + + r = ndisc_prefix_new(nd, &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_ndisc(nd, "New prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", + SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr), + prefix->len, lifetime_valid, + format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC)); + + 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_ndisc(nd, "Prefix length mismatch %d/%d using %d", + prefix->len, + prefix_opt->nd_opt_pi_prefix_len, + prefixlen); + + prefix->len = prefixlen; + } + + log_ndisc(nd, "Update prefix "SD_NDISC_ADDRESS_FORMAT_STR"/%d lifetime %d expires in %s", + SD_NDISC_ADDRESS_FORMAT_VAL(prefix->addr), + prefix->len, lifetime_valid, + format_timespan(time_string, FORMAT_TIMESPAN_MAX, lifetime_valid * USEC_PER_SEC, USEC_PER_SEC)); + } + + r = sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now); + if (r < 0) + return r; + + prefix->valid_until = time_now + lifetime_valid * USEC_PER_SEC; + + if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && nd->prefix_onlink_callback) + nd->prefix_onlink_callback(nd, &prefix->addr, prefix->len, prefix->valid_until, nd->userdata); + + if ((prefix_opt->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && nd->prefix_autonomous_callback) + nd->prefix_autonomous_callback(nd, &prefix->addr, prefix->len, lifetime_preferred, lifetime_valid, + nd->userdata); + + return 0; +} + +static int ndisc_ra_parse(sd_ndisc *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 < NDISC_OPT_LEN_UNITS) { + log_ndisc(nd, "Router Advertisement below minimum length"); + + return -ENOMSG; + } + + opt = ra + 1; + opt_hdr = opt; + + while (len != 0 && len >= opt_hdr->nd_opt_len * NDISC_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_ndisc(nd, "Router Advertisement link MTU %d using %d", + mtu, nd->mtu); + } + + break; + + case ND_OPT_PREFIX_INFORMATION: + opt_prefix = opt; + + ndisc_prefix_update(nd, len, opt_prefix); + + break; + } + + len -= opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS; + opt = (void *)((char *)opt + + opt_hdr->nd_opt_len * NDISC_OPT_LEN_UNITS); + opt_hdr = opt; + } + + if (len > 0) + log_ndisc(nd, "Router Advertisement contains %zd bytes of trailing garbage", len); + + return 0; +} + +static int ndisc_router_advertisment_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_free_ struct nd_router_advert *ra = NULL; + sd_ndisc *nd = userdata; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_LEN(sizeof(int))]; + } 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; + struct in6_addr *gw; + unsigned lifetime; + ssize_t len, buflen; + int r, pref, stateful; + + assert(s); + assert(nd); + assert(nd->event); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return buflen; + + iov.iov_len = buflen; + + ra = malloc(iov.iov_len); + if (!ra) + return -ENOMEM; + + iov.iov_base = ra; + + len = recvmsg(fd, &msg, 0); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_ndisc(nd, "Could not receive message from ICMPv6 socket: %m"); + return -errno; + } else if ((size_t)len < sizeof(struct nd_router_advert)) { + return 0; + } else if (msg.msg_namelen == 0) + gw = NULL; /* only happens when running the test-suite over a socketpair */ + else if (msg.msg_namelen != sizeof(sa.in6)) { + log_ndisc(nd, "Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t)msg.msg_namelen); + return 0; + } else + gw = &sa.in6.sin6_addr; + + 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(nd, "Received RA with invalid hop limit %d. Ignoring.", hops); + return 0; + } + + break; + } + } + + if (gw && !in_addr_is_link_local(AF_INET6, (const union in_addr_union*) gw)) { + _cleanup_free_ char *addr = NULL; + + (void)in_addr_to_string(AF_INET6, (const union in_addr_union*) gw, &addr); + + log_ndisc(nd, "Received RA from non-link-local address %s. Ignoring.", strna(addr)); + 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 = NDISC_STATE_ADVERTISMENT_LISTEN; + + stateful = ra->nd_ra_flags_reserved & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER); + pref = (ra->nd_ra_flags_reserved & ND_RA_FLAG_PREF) >> 3; + + switch (pref) { + case ND_RA_FLAG_PREF_LOW: + case ND_RA_FLAG_PREF_HIGH: + break; + default: + pref = ND_RA_FLAG_PREF_MEDIUM; + break; + } + + lifetime = be16toh(ra->nd_ra_router_lifetime); + + log_ndisc(nd, "Received Router Advertisement: flags %s preference %s lifetime %u sec", + stateful & ND_RA_FLAG_MANAGED ? "MANAGED" : stateful & ND_RA_FLAG_OTHER ? "OTHER" : "none", + pref == ND_RA_FLAG_PREF_HIGH ? "high" : pref == ND_RA_FLAG_PREF_LOW ? "low" : "medium", + lifetime); + + r = ndisc_ra_parse(nd, ra, len); + if (r < 0) { + log_ndisc(nd, "Could not parse Router Advertisement: %s", strerror(-r)); + return 0; + } + + if (nd->router_callback) + nd->router_callback(nd, stateful, gw, lifetime, pref, nd->userdata); + + return 0; +} + +static int ndisc_router_solicitation_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ndisc *nd = userdata; + uint64_t time_now, next_timeout; + int r; + + assert(s); + assert(nd); + assert(nd->event); + + nd->timeout = sd_event_source_unref(nd->timeout); + + if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) { + if (nd->callback) + nd->callback(nd, SD_NDISC_EVENT_TIMEOUT, nd->userdata); + nd->state = NDISC_STATE_ADVERTISMENT_LISTEN; + } else { + r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); + if (r < 0) + log_ndisc(nd, "Error sending Router Solicitation"); + else { + nd->state = NDISC_STATE_SOLICITATION_SENT; + log_ndisc(nd, "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_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), + next_timeout, 0, + ndisc_router_solicitation_timeout, nd); + if (r < 0) { + /* we cannot continue if we are unable to rearm the timer */ + sd_ndisc_stop(nd); + return 0; + } + + r = sd_event_source_set_priority(nd->timeout, nd->event_priority); + if (r < 0) + return 0; + + r = sd_event_source_set_description(nd->timeout, "ndisc-timeout"); + if (r < 0) + return 0; + } + + return 0; +} + +int sd_ndisc_stop(sd_ndisc *nd) { + assert_return(nd, -EINVAL); + assert_return(nd->event, -EINVAL); + + log_ndisc(client, "Stop NDisc"); + + ndisc_init(nd); + + nd->state = NDISC_STATE_IDLE; + + if (nd->callback) + nd->callback(nd, SD_NDISC_EVENT_STOP, nd->userdata); + + return 0; +} + +int sd_ndisc_router_discovery_start(sd_ndisc *nd) { + int r; + + assert(nd); + assert(nd->event); + + if (nd->state != NDISC_STATE_IDLE) + return -EBUSY; + + if (nd->index < 1) + return -EINVAL; + + r = 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, + ndisc_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, "ndisc-receive-message"); + if (r < 0) + goto error; + + r = sd_event_add_time(nd->event, &nd->timeout, clock_boottime_or_monotonic(), + 0, 0, ndisc_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, "ndisc-timeout"); +error: + if (r < 0) + ndisc_init(nd); + else + log_ndisc(client, "Start Router Solicitation"); + + return r; +} diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c new file mode 100644 index 0000000000..53ddfc3b62 --- /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 <systemd/sd-event.h> +#include <systemd/sd-ipv4acd.h> +#include <systemd/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_index(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 new file mode 100644 index 0000000000..478b370c4c --- /dev/null +++ b/src/libsystemd-network/test-dhcp-client.c @@ -0,0 +1,513 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 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 <stdio.h> +#include <sys/socket.h> +#include <unistd.h> + +#include <systemd/sd-dhcp-client.h> +#include <systemd/sd-event.h> + +#include "alloc-util.h" +#include "dhcp-identifier.h" +#include "dhcp-internal.h" +#include "dhcp-protocol.h" +#include "fd-util.h" +#include "util.h" + +static uint8_t mac_addr[] = {'A', 'B', 'C', '1', '2', '3'}; + +typedef int (*test_callback_recv_t)(size_t size, DHCPMessage *dhcp); + +static bool verbose = true; +static int test_fd[2]; +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) { + assert_not_reached("Test case should have completed in 2 seconds"); + + return 0; +} + +static void test_request_basic(sd_event *e) { + int r; + + sd_dhcp_client *client; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + r = sd_dhcp_client_new(&client); + + assert_se(r >= 0); + assert_se(client); + + r = sd_dhcp_client_attach_event(client, e, 0); + assert_se(r >= 0); + + assert_se(sd_dhcp_client_set_request_option(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_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_request_option(client, + SD_DHCP_OPTION_SUBNET_MASK) == -EEXIST); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_ROUTER) == -EEXIST); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_HOST_NAME) == -EEXIST); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_DOMAIN_NAME) == -EEXIST); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER) == -EEXIST); + + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_PAD) == -EINVAL); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_END) == -EINVAL); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_MESSAGE_TYPE) == -EINVAL); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_OVERLOAD) == -EINVAL); + assert_se(sd_dhcp_client_set_request_option(client, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST) + == -EINVAL); + + assert_se(sd_dhcp_client_set_request_option(client, 33) == 0); + assert_se(sd_dhcp_client_set_request_option(client, 33) == -EEXIST); + assert_se(sd_dhcp_client_set_request_option(client, 44) == 0); + assert_se(sd_dhcp_client_set_request_option(client, 33) == -EEXIST); + + sd_dhcp_client_unref(client); +} + +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, + 0xff, 0xff, 0xff, 0xff + }; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + assert_se(dhcp_packet_checksum((uint8_t*)&buf, 20) == be16toh(0x78ae)); +} + +static int check_options(uint8_t code, uint8_t len, const void *option, void *userdata) { + switch(code) { + case SD_DHCP_OPTION_CLIENT_IDENTIFIER: + { + uint32_t iaid; + struct duid duid; + size_t duid_len; + + assert_se(dhcp_identifier_set_duid_en(&duid, &duid_len) >= 0); + assert_se(dhcp_identifier_set_iaid(42, mac_addr, ETH_ALEN, &iaid) >= 0); + + assert_se(len == sizeof(uint8_t) + sizeof(uint32_t) + duid_len); + assert_se(len == 19); + assert_se(((uint8_t*) option)[0] == 0xff); + + assert_se(memcmp((uint8_t*) option + 1, &iaid, sizeof(iaid)) == 0); + assert_se(memcmp((uint8_t*) option + 5, &duid, duid_len) == 0); + break; + } + + default: + break; + } + + return 0; +} + +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; + + assert_se(s >= 0); + assert_se(packet); + + size = sizeof(DHCPPacket); + assert_se(len > size); + + discover = memdup(packet, len); + + assert_se(discover->ip.ttl == IPDEFTTL); + assert_se(discover->ip.protocol == IPPROTO_UDP); + assert_se(discover->ip.saddr == INADDR_ANY); + assert_se(discover->ip.daddr == INADDR_BROADCAST); + assert_se(discover->udp.source == be16toh(DHCP_PORT_CLIENT)); + assert_se(discover->udp.dest == be16toh(DHCP_PORT_SERVER)); + + ip_check = discover->ip.check; + + discover->ip.ttl = 0; + discover->ip.check = discover->udp.len; + + udp_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip.ttl, len - 8); + assert_se(udp_check == 0xffff); + + discover->ip.ttl = IPDEFTTL; + discover->ip.check = ip_check; + + ip_check = ~dhcp_packet_checksum((uint8_t*)&discover->ip, sizeof(discover->ip)); + assert_se(ip_check == 0xffff); + + assert_se(discover->dhcp.xid); + assert_se(memcmp(discover->dhcp.chaddr, &mac_addr, ETH_ALEN) == 0); + + size = len - sizeof(struct iphdr) - sizeof(struct udphdr); + + assert_se(callback_recv); + callback_recv(size, &discover->dhcp); + + 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) { + + 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 fd; + + fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, 0); + if (fd < 0) + return -errno; + + return fd; +} + +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) { + int res; + + res = dhcp_option_parse(dhcp, size, check_options, NULL, NULL); + assert_se(res == DHCP_DISCOVER); + + if (verbose) + printf(" recv DHCP Discover 0x%08x\n", be32toh(dhcp->xid)); + + return 0; +} + +static void test_discover_message(sd_event *e) { + sd_dhcp_client *client; + int res, r; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + r = sd_dhcp_client_new(&client); + assert_se(r >= 0); + assert_se(client); + + r = sd_dhcp_client_attach_event(client, e, 0); + assert_se(r >= 0); + + assert_se(sd_dhcp_client_set_index(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); + + callback_recv = test_discover_message_verify; + + res = sd_dhcp_client_start(client); + + assert_se(res == 0 || res == -EINPROGRESS); + + sd_event_run(e, (uint64_t) -1); + + sd_dhcp_client_stop(client); + sd_dhcp_client_unref(client); + + test_fd[1] = safe_close(test_fd[1]); + + callback_recv = NULL; +} + +static uint8_t test_addr_acq_offer[] = { + 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01, + 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44, + 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, + 0x6f, 0x95, 0x2f, 0x30, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf, + 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x02, 0x36, + 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00, + 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff, + 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f, + 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static uint8_t test_addr_acq_ack[] = { + 0x45, 0x10, 0x01, 0x48, 0x00, 0x00, 0x00, 0x00, + 0x80, 0x11, 0xb3, 0x84, 0xc0, 0xa8, 0x02, 0x01, + 0xc0, 0xa8, 0x02, 0xbf, 0x00, 0x43, 0x00, 0x44, + 0x01, 0x34, 0x00, 0x00, 0x02, 0x01, 0x06, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xc0, 0xa8, 0x02, 0xbf, + 0xc0, 0xa8, 0x02, 0x01, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x63, 0x82, 0x53, 0x63, 0x35, 0x01, 0x05, 0x36, + 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x33, 0x04, 0x00, + 0x00, 0x02, 0x58, 0x01, 0x04, 0xff, 0xff, 0xff, + 0x00, 0x2a, 0x04, 0xc0, 0xa8, 0x02, 0x01, 0x0f, + 0x09, 0x6c, 0x61, 0x62, 0x2e, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x03, 0x04, 0xc0, 0xa8, 0x02, 0x01, + 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +}; + +static void test_addr_acq_acquired(sd_dhcp_client *client, int event, + void *userdata) { + sd_event *e = userdata; + sd_dhcp_lease *lease; + struct in_addr addr; + + assert_se(client); + assert_se(event == SD_DHCP_CLIENT_EVENT_IP_ACQUIRE); + + assert_se(sd_dhcp_client_get_lease(client, &lease) >= 0); + assert_se(lease); + + assert_se(sd_dhcp_lease_get_address(lease, &addr) >= 0); + assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[44], + sizeof(addr.s_addr)) == 0); + + assert_se(sd_dhcp_lease_get_netmask(lease, &addr) >= 0); + assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[285], + sizeof(addr.s_addr)) == 0); + + assert_se(sd_dhcp_lease_get_router(lease, &addr) >= 0); + assert_se(memcmp(&addr.s_addr, &test_addr_acq_ack[308], + sizeof(addr.s_addr)) == 0); + + if (verbose) + printf(" DHCP address acquired\n"); + + sd_event_exit(e, 0); +} + +static int test_addr_acq_recv_request(size_t size, DHCPMessage *request) { + uint16_t udp_check = 0; + uint8_t *msg_bytes = (uint8_t *)request; + int res; + + 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] == SD_DHCP_OPTION_END); + + if (verbose) + printf(" recv DHCP Request 0x%08x\n", be32toh(xid)); + + memcpy(&test_addr_acq_ack[26], &udp_check, sizeof(udp_check)); + memcpy(&test_addr_acq_ack[32], &xid, sizeof(xid)); + memcpy(&test_addr_acq_ack[56], &mac_addr, ETHER_ADDR_LEN); + + callback_recv = NULL; + + res = write(test_fd[1], test_addr_acq_ack, + sizeof(test_addr_acq_ack)); + assert_se(res == sizeof(test_addr_acq_ack)); + + if (verbose) + printf(" send DHCP Ack\n"); + + return 0; +}; + +static int test_addr_acq_recv_discover(size_t size, DHCPMessage *discover) { + uint16_t udp_check = 0; + uint8_t *msg_bytes = (uint8_t *)discover; + int res; + + res = dhcp_option_parse(discover, size, check_options, NULL, NULL); + assert_se(res == DHCP_DISCOVER); + + assert_se(msg_bytes[size - 1] == SD_DHCP_OPTION_END); + + xid = discover->xid; + + if (verbose) + printf(" recv DHCP Discover 0x%08x\n", be32toh(xid)); + + memcpy(&test_addr_acq_offer[26], &udp_check, sizeof(udp_check)); + memcpy(&test_addr_acq_offer[32], &xid, sizeof(xid)); + memcpy(&test_addr_acq_offer[56], &mac_addr, ETHER_ADDR_LEN); + + callback_recv = test_addr_acq_recv_request; + + res = write(test_fd[1], test_addr_acq_offer, + sizeof(test_addr_acq_offer)); + assert_se(res == sizeof(test_addr_acq_offer)); + + if (verbose) + printf(" sent DHCP Offer\n"); + + return 0; +} + +static void test_addr_acq(sd_event *e) { + usec_t time_now = now(clock_boottime_or_monotonic()); + sd_dhcp_client *client; + int res, r; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + r = sd_dhcp_client_new(&client); + assert_se(r >= 0); + assert_se(client); + + r = sd_dhcp_client_attach_event(client, e, 0); + assert_se(r >= 0); + + assert_se(sd_dhcp_client_set_index(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); + + callback_recv = test_addr_acq_recv_discover; + + assert_se(sd_event_add_time(e, &test_hangcheck, + clock_boottime_or_monotonic(), + time_now + 2 * USEC_PER_SEC, 0, + test_dhcp_hangcheck, NULL) >= 0); + + res = sd_dhcp_client_start(client); + assert_se(res == 0 || res == -EINPROGRESS); + + assert_se(sd_event_loop(e) >= 0); + + test_hangcheck = sd_event_source_unref(test_hangcheck); + + assert_se(sd_dhcp_client_set_callback(client, NULL, NULL) >= 0); + assert_se(sd_dhcp_client_stop(client) >= 0); + sd_dhcp_client_unref(client); + + test_fd[1] = safe_close(test_fd[1]); + + callback_recv = NULL; + xid = 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e; + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + assert_se(sd_event_new(&e) >= 0); + + test_request_basic(e); + test_checksum(); + + test_discover_message(e); + test_addr_acq(e); + +#ifdef VALGRIND + /* Make sure the async_close thread has finished. + * valgrind would report some of the phread_* structures + * as not cleaned up properly. */ + sleep(1); +#endif + + return 0; +} diff --git a/src/libsystemd-network/test-dhcp-option.c b/src/libsystemd-network/test-dhcp-option.c new file mode 100644 index 0000000000..7d8a957227 --- /dev/null +++ b/src/libsystemd-network/test-dhcp-option.c @@ -0,0 +1,372 @@ +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.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]; + int snamelen; + uint8_t file[128]; + int filelen; + uint8_t options[128]; + int len; + bool success; + int filepos; + int snamepos; + int pos; +}; + +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, + 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, + 0x04, 0xff, 0xff, 0xff, 0x00, 0x03, 0x04, 0xc0, + 0xa8, 0x00, 0x01, 0x06, 0x04, 0xc0, 0xa8, 0x00, + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, }, + 40, true, }, + { {}, 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, 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, SD_DHCP_OPTION_MESSAGE_TYPE, 1, DHCP_ACK }, 9, + { 222, 3, 1, 2, 3 }, 5, + { SD_DHCP_OPTION_OVERLOAD, 1, + DHCP_OVERLOAD_FILE|DHCP_OVERLOAD_SNAME }, 3, true, }, +}; + +static const char *dhcp_type(int type) { + switch(type) { + case DHCP_DISCOVER: + return "DHCPDISCOVER"; + case DHCP_OFFER: + return "DHCPOFFER"; + case DHCP_REQUEST: + return "DHCPREQUEST"; + case DHCP_DECLINE: + return "DHCPDECLINE"; + case DHCP_ACK: + return "DHCPACK"; + case DHCP_NAK: + return "DHCPNAK"; + case DHCP_RELEASE: + return "DHCPRELEASE"; + default: + return "unknown"; + } +} + +static void test_invalid_buffer_length(void) { + DHCPMessage message; + + 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) { + _cleanup_free_ DHCPMessage *message = NULL; + size_t optlen = 4, optoffset; + size_t len = sizeof(DHCPMessage) + optlen; + uint8_t *magic; + + message = malloc0(len); + + assert_se(dhcp_message_init(message, BOOTREQUEST, 0x12345678, + DHCP_DISCOVER, ARPHRD_ETHER, optlen, &optoffset) >= 0); + + assert_se(message->xid == htobe32(0x12345678)); + assert_se(message->op == BOOTREQUEST); + + magic = (uint8_t*)&message->magic; + + assert_se(magic[0] == 99); + assert_se(magic[1] == 130); + assert_se(magic[2] == 83); + assert_se(magic[3] == 99); + + assert_se(dhcp_option_parse(message, len, NULL, NULL, NULL) >= 0); +} + +static DHCPMessage *create_message(uint8_t *options, uint16_t optlen, + uint8_t *file, uint8_t filelen, + uint8_t *sname, uint8_t snamelen) { + DHCPMessage *message; + size_t len = sizeof(DHCPMessage) + 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); + + return message; +} + +static void test_ignore_opts(uint8_t *descoption, int *descpos, int *desclen) { + assert(*descpos >= 0); + + while (*descpos < *desclen) { + switch(descoption[*descpos]) { + case SD_DHCP_OPTION_PAD: + *descpos += 1; + break; + + case SD_DHCP_OPTION_MESSAGE_TYPE: + case SD_DHCP_OPTION_OVERLOAD: + *descpos += 3; + break; + + default: + return; + } + } +} + +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; + uint8_t optlen = 0; + uint8_t i; + + assert_se((!desc && !code && !len) || desc); + + if (!desc) + return -EINVAL; + + 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) { + + if (desc->pos >= 0) { + descoption = &desc->options[0]; + desclen = &desc->len; + descpos = &desc->pos; + } else if (desc->filepos >= 0) { + descoption = &desc->file[0]; + desclen = &desc->filelen; + descpos = &desc->filepos; + } else if (desc->snamepos >= 0) { + descoption = &desc->sname[0]; + desclen = &desc->snamelen; + descpos = &desc->snamepos; + } + + assert_se(descoption && desclen && descpos); + + if (*desclen) + test_ignore_opts(descoption, descpos, desclen); + + if (*descpos < *desclen) + break; + + if (*descpos == *desclen) + *descpos = -1; + } + + assert_se(descpos); + assert_se(*descpos != -1); + + optcode = descoption[*descpos]; + optlen = descoption[*descpos + 1]; + + if (verbose) + printf("DHCP code %2d(%2d) len %2d(%2d) ", code, optcode, + len, optlen); + + assert_se(code == optcode); + assert_se(len == optlen); + + for (i = 0; i < len; i++) { + + if (verbose) + printf("0x%02x(0x%02x) ", ((uint8_t*) option)[i], + descoption[*descpos + 2 + i]); + + assert_se(((uint8_t*) option)[i] == descoption[*descpos + 2 + i]); + } + + if (verbose) + printf("\n"); + + *descpos += optlen + 2; + + test_ignore_opts(descoption, descpos, desclen); + + if (desc->pos != -1 && desc->pos == desc->len) + desc->pos = -1; + + if (desc->filepos != -1 && desc->filepos == desc->filelen) + desc->filepos = -1; + + if (desc->snamepos != -1 && desc->snamepos == desc->snamelen) + desc->snamepos = -1; + + return 0; +} + +static void test_options(struct option_desc *desc) { + uint8_t *options = NULL; + uint8_t *file = NULL; + uint8_t *sname = NULL; + int optlen = 0; + int filelen = 0; + int snamelen = 0; + int buflen = 0; + _cleanup_free_ DHCPMessage *message = NULL; + int res; + + if (desc) { + file = &desc->file[0]; + filelen = desc->filelen; + if (!filelen) + desc->filepos = -1; + + sname = &desc->sname[0]; + snamelen = desc->snamelen; + if (!snamelen) + desc->snamepos = -1; + + options = &desc->options[0]; + optlen = desc->len; + desc->pos = 0; + } + message = create_message(options, optlen, file, filelen, + sname, snamelen); + + buflen = sizeof(DHCPMessage) + optlen; + + if (!desc) { + 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, 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, NULL)) < 0); + + if (verbose) + printf("DHCP type %s\n", dhcp_type(res)); +} + +static uint8_t options[64] = { + 'A', 'B', 'C', 'D', + 160, 2, 0x11, 0x12, + 0, + 31, 8, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, + 0, + 55, 3, 0x51, 0x52, 0x53, + 17, 7, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, + 255 +}; + +static void test_option_set(void) { + _cleanup_free_ DHCPMessage *result = NULL; + size_t offset = 0, len, pos; + unsigned i; + + result = malloc0(sizeof(DHCPMessage) + 11); + assert_se(result); + + result->options[0] = 'A'; + result->options[1] = 'B'; + result->options[2] = 'C'; + result->options[3] = 'D'; + + 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, SD_DHCP_OPTION_PAD, + 0, NULL) == -ENOBUFS); + assert_se(offset == 4); + 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] != 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] == SD_DHCP_OPTION_PAD) + pos++; + else + pos += 2 + options[pos + 1]; + + if (pos < len) + assert_se(offset == pos); + } + + for (i = 0; i < 9; i++) { + if (verbose) + printf("%2u: 0x%02x(0x%02x) (options)\n", i, result->options[i], + options[i]); + assert_se(result->options[i] == options[i]); + } + + if (verbose) + printf("%2d: 0x%02x(0x%02x) (options)\n", 9, result->options[9], + SD_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], + SD_DHCP_OPTION_PAD); + + assert_se(result->options[10] == SD_DHCP_OPTION_PAD); + + for (i = 0; i < pos - 8; i++) { + if (verbose) + printf("%2u: 0x%02x(0x%02x) (sname)\n", i, result->sname[i], + options[i + 9]); + assert_se(result->sname[i] == options[i + 9]); + } + + if (verbose) + printf ("\n"); +} + +int main(int argc, char *argv[]) { + unsigned i; + + test_invalid_buffer_length(); + test_message_init(); + + test_options(NULL); + + for (i = 0; i < ELEMENTSOF(option_tests); i++) + test_options(&option_tests[i]); + + test_option_set(); + + return 0; +} diff --git a/src/libsystemd-network/test-dhcp-server.c b/src/libsystemd-network/test-dhcp-server.c new file mode 100644 index 0000000000..7dc315c07f --- /dev/null +++ b/src/libsystemd-network/test-dhcp-server.c @@ -0,0 +1,261 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 Intel Corporation. All rights reserved. + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <errno.h> + +#include <systemd/sd-dhcp-server.h> +#include <systemd/sd-event.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_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + struct in_addr address_lo = { + .s_addr = htonl(INADDR_LOOPBACK), + }; + struct in_addr address_any = { + .s_addr = htonl(INADDR_ANY), + }; + int r; + + /* attach to loopback interface */ + assert_se(sd_dhcp_server_new(&server, 1) >= 0); + assert_se(server); + + assert_se(sd_dhcp_server_attach_event(server, event, 0) >= 0); + assert_se(sd_dhcp_server_attach_event(server, event, 0) == -EBUSY); + assert_se(sd_dhcp_server_get_event(server) == event); + assert_se(sd_dhcp_server_detach_event(server) >= 0); + assert_se(!sd_dhcp_server_get_event(server)); + assert_se(sd_dhcp_server_attach_event(server, NULL, 0) >= 0); + assert_se(sd_dhcp_server_attach_event(server, NULL, 0) == -EBUSY); + + assert_se(sd_dhcp_server_ref(server) == server); + assert_se(!sd_dhcp_server_unref(server)); + + assert_se(sd_dhcp_server_start(server) == -EUNATCH); + + 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); + + if (r == -EPERM) + return EXIT_TEST_SKIP; + assert_se(r >= 0); + + assert_se(sd_dhcp_server_start(server) == -EBUSY); + assert_se(sd_dhcp_server_stop(server) >= 0); + assert_se(sd_dhcp_server_stop(server) >= 0); + assert_se(sd_dhcp_server_start(server) >= 0); + + return 0; +} + +static void test_message_handler(void) { + _cleanup_(sd_dhcp_server_unrefp) sd_dhcp_server *server = NULL; + struct { + DHCPMessage message; + struct { + uint8_t code; + uint8_t length; + uint8_t type; + } _packed_ option_type; + struct { + uint8_t code; + uint8_t length; + be32_t address; + } _packed_ option_requested_ip; + struct { + uint8_t code; + uint8_t length; + be32_t address; + } _packed_ option_server_id; + struct { + uint8_t code; + uint8_t length; + uint8_t id[7]; + } _packed_ option_client_id; + uint8_t end; + } _packed_ test = { + .message.op = BOOTREQUEST, + .message.htype = ARPHRD_ETHER, + .message.hlen = ETHER_ADDR_LEN, + .message.xid = htobe32(0x12345678), + .message.chaddr = { 'A', 'B', 'C', 'D', 'E', 'F' }, + .option_type.code = SD_DHCP_OPTION_MESSAGE_TYPE, + .option_type.length = 1, + .option_type.type = DHCP_DISCOVER, + .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_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)) == 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 = 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 = 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); + + test.message.op = 0; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.message.op = BOOTREQUEST; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); + + test.message.htype = 0; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.message.htype = ARPHRD_ETHER; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); + + test.message.hlen = 0; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.message.hlen = ETHER_ADDR_LEN; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_OFFER); + + test.option_type.type = DHCP_REQUEST; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.option_requested_ip.code = 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 = 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); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK); + + test.option_server_id.address = htobe32(0x12345678); + test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.option_server_id.address = htobe32(INADDR_LOOPBACK); + test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 4); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); + test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 3); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK); + + test.option_client_id.code = 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'; + test.option_client_id.id[2] = 'B'; + test.option_client_id.id[3] = 'C'; + test.option_client_id.id[4] = 'D'; + test.option_client_id.id[5] = 'E'; + test.option_client_id.id[6] = 'F'; + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == DHCP_ACK); + + test.option_requested_ip.address = htobe32(INADDR_LOOPBACK + 30); + assert_se(dhcp_server_handle_message(server, (DHCPMessage*)&test, sizeof(test)) == 0); +} + +static 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, + }, b = { + .length = 4, + }; + uint8_t hash_key[HASH_KEY_SIZE] = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', + }; + + a.data = (uint8_t*)strdup("abcd"); + b.data = (uint8_t*)strdup("abcd"); + + assert_se(client_id_compare_func(&a, &b) == 0); + assert_se(client_id_hash_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_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_helper(&a, hash_key) == client_id_hash_helper(&b, hash_key)); + + free(b.data); + b.data = (uint8_t*)strdup("abce"); + assert_se(client_id_compare_func(&a, &b) != 0); + + free(a.data); + free(b.data); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e; + int r; + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + assert_se(sd_event_new(&e) >= 0); + + r = test_basic(e); + if (r != 0) + return r; + + test_message_handler(); + test_client_id_hash(); + + return 0; +} diff --git a/src/libsystemd-network/test-dhcp6-client.c b/src/libsystemd-network/test-dhcp6-client.c new file mode 100644 index 0000000000..0548e8381e --- /dev/null +++ b/src/libsystemd-network/test-dhcp6-client.c @@ -0,0 +1,763 @@ +/*** + 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 <net/ethernet.h> +#include <stdbool.h> +#include <stdio.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <systemd/sd-dhcp6-client.h> +#include <systemd/sd-event.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'} +}; + +static bool verbose = true; + +static sd_event_source *hangcheck; +static int test_dhcp_fd[2]; +static int test_index = 42; +static int test_client_message_num; +static be32_t test_iaid = 0; +static uint8_t test_duid[14] = { }; + +static int test_client_basic(sd_event *e) { + sd_dhcp6_client *client; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + assert_se(sd_dhcp6_client_new(&client) >= 0); + assert_se(client); + + 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_mac(client, (const uint8_t *) &mac_addr, + sizeof (mac_addr), + ARPHRD_ETHER) >= 0); + + 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); + + assert_se(sd_dhcp6_client_detach_event(client) >= 0); + assert_se(!sd_dhcp6_client_unref(client)); + + return 0; +} + +static int test_option(sd_event *e) { + uint8_t packet[] = { + 'F', 'O', 'O', + 0x00, SD_DHCP6_OPTION_ORO, 0x00, 0x07, + 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 0x00, SD_DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09, + '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'B', 'A', 'R', + }; + uint8_t result[] = { + 'F', 'O', 'O', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 'B', 'A', 'R', + }; + uint16_t optcode; + size_t optlen; + uint8_t *optval, *buf, *out; + size_t zero = 0, pos = 3; + size_t buflen = sizeof(packet), outlen = sizeof(result); + + if (verbose) + printf("* %s\n", __FUNCTION__); + + assert_se(buflen == outlen); + + assert_se(dhcp6_option_parse(&buf, &zero, &optcode, &optlen, + &optval) == -ENOMSG); + + buflen -= 3; + buf = &packet[3]; + outlen -= 3; + out = &result[3]; + + assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen, + &optval) >= 0); + pos += 4 + optlen; + assert_se(buf == &packet[pos]); + assert_se(optcode == SD_DHCP6_OPTION_ORO); + assert_se(optlen == 7); + assert_se(buflen + pos == sizeof(packet)); + + assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen, + optval) >= 0); + assert_se(out == &result[pos]); + assert_se(*out == 0x00); + + assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen, + &optval) >= 0); + pos += 4 + optlen; + assert_se(buf == &packet[pos]); + assert_se(optcode == SD_DHCP6_OPTION_VENDOR_CLASS); + assert_se(optlen == 9); + assert_se(buflen + pos == sizeof(packet)); + + assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen, + optval) >= 0); + assert_se(out == &result[pos]); + assert_se(*out == 'B'); + + assert_se(memcmp(packet, result, sizeof(packet)) == 0); + + return 0; +} + +static uint8_t msg_advertise[198] = { + 0x02, 0x0f, 0xb4, 0xe5, 0x00, 0x01, 0x00, 0x0e, + 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, 0xf3, 0x30, + 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x03, + 0x00, 0x5e, 0x0e, 0xcf, 0xa3, 0x7d, 0x00, 0x00, + 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, 0x00, 0x05, + 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, + 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, 0x09, 0x3c, + 0x55, 0xad, 0x00, 0x00, 0x00, 0x96, 0x00, 0x00, + 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x32, 0x00, 0x00, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x28, + 0x65, 0x73, 0x29, 0x20, 0x72, 0x65, 0x6e, 0x65, + 0x77, 0x65, 0x64, 0x2e, 0x20, 0x47, 0x72, 0x65, + 0x65, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x66, + 0x72, 0x6f, 0x6d, 0x20, 0x70, 0x6c, 0x61, 0x6e, + 0x65, 0x74, 0x20, 0x45, 0x61, 0x72, 0x74, 0x68, + 0x00, 0x17, 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8, + 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b, + 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, + 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, + 0x02, 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x19, + 0x40, 0x5c, 0x53, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, + 0x53, 0x00, 0x07, 0x00, 0x01, 0x00 +}; + +static uint8_t msg_reply[173] = { + 0x07, 0xf7, 0x4e, 0x57, 0x00, 0x02, 0x00, 0x0e, + 0x00, 0x01, 0x00, 0x01, 0x19, 0x40, 0x5c, 0x53, + 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, 0x00, 0x01, + 0x00, 0x0e, 0x00, 0x01, 0x00, 0x01, 0x1a, 0x6b, + 0xf3, 0x30, 0x3c, 0x97, 0x0e, 0xcf, 0xa3, 0x7d, + 0x00, 0x03, 0x00, 0x4a, 0x0e, 0xcf, 0xa3, 0x7d, + 0x00, 0x00, 0x00, 0x50, 0x00, 0x00, 0x00, 0x78, + 0x00, 0x05, 0x00, 0x18, 0x20, 0x01, 0x0d, 0xb8, + 0xde, 0xad, 0xbe, 0xef, 0x78, 0xee, 0x1c, 0xf3, + 0x09, 0x3c, 0x55, 0xad, 0x00, 0x00, 0x00, 0x96, + 0x00, 0x00, 0x00, 0xb4, 0x00, 0x0d, 0x00, 0x1e, + 0x00, 0x00, 0x41, 0x6c, 0x6c, 0x20, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x20, + 0x77, 0x65, 0x72, 0x65, 0x20, 0x61, 0x73, 0x73, + 0x69, 0x67, 0x6e, 0x65, 0x64, 0x2e, 0x00, 0x17, + 0x00, 0x10, 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, + 0xbe, 0xef, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x18, 0x00, 0x0b, 0x03, 0x6c, + 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, 0x72, 0x61, + 0x00, 0x00, 0x1f, 0x00, 0x10, 0x20, 0x01, 0x0d, + 0xb8, 0xde, 0xad, 0xbe, 0xef, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x01 +}; + +static int test_advertise_option(sd_event *e) { + _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; + size_t optlen, len = sizeof(msg_advertise) - sizeof(DHCP6Message); + be32_t val; + uint8_t preference = 255; + struct in6_addr addr; + uint32_t lt_pref, lt_valid; + int r; + bool opt_clientid = false; + struct in6_addr *addrs; + char **domains; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + assert_se(dhcp6_lease_new(&lease) >= 0); + + assert_se(advertise->type == DHCP6_ADVERTISE); + assert_se((be32toh(advertise->transaction_id) & 0x00ffffff) == + 0x0fb4e5); + + while ((r = dhcp6_option_parse(&opt, &len, &optcode, &optlen, + &optval)) >= 0) { + + switch(optcode) { + case SD_DHCP6_OPTION_CLIENTID: + assert_se(optlen == 14); + + opt_clientid = true; + break; + + case SD_DHCP6_OPTION_IA_NA: + assert_se(optlen == 94); + assert_se(!memcmp(optval, &msg_advertise[26], optlen)); + + val = htobe32(0x0ecfa37d); + assert_se(!memcmp(optval, &val, sizeof(val))); + + val = htobe32(80); + assert_se(!memcmp(optval + 4, &val, sizeof(val))); + + val = htobe32(120); + assert_se(!memcmp(optval + 8, &val, sizeof(val))); + + assert_se(dhcp6_option_parse_ia(&optval, &optlen, + optcode, + &lease->ia) >= 0); + + break; + + case SD_DHCP6_OPTION_SERVERID: + assert_se(optlen == 14); + assert_se(!memcmp(optval, &msg_advertise[179], optlen)); + + assert_se(dhcp6_lease_set_serverid(lease, optval, + optlen) >= 0); + break; + + case SD_DHCP6_OPTION_PREFERENCE: + assert_se(optlen == 1); + assert_se(!*optval); + + assert_se(dhcp6_lease_set_preference(lease, + *optval) >= 0); + break; + + 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; + } + } + + + assert_se(r == -ENOMSG); + + assert_se(opt_clientid); + + sd_dhcp6_lease_reset_address_iter(lease); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) >= 0); + assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) == -ENOMSG); + + sd_dhcp6_lease_reset_address_iter(lease); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) >= 0); + assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) == -ENOMSG); + sd_dhcp6_lease_reset_address_iter(lease); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) >= 0); + assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) == -ENOMSG); + + assert_se(dhcp6_lease_get_serverid(lease, &opt, &len) >= 0); + assert_se(len == 14); + assert_se(!memcmp(opt, &msg_advertise[179], len)); + + 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; +} + +static int test_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_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 == SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE); + + 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)); + + assert_se(sd_dhcp6_client_set_request_option(client, SD_DHCP6_OPTION_DNS_SERVERS) == -EBUSY); + + if (verbose) + printf(" got DHCPv6 event %d\n", event); + + sd_event_exit(e, 0); +} + +static int test_client_send_reply(DHCP6Message *request) { + DHCP6Message reply; + + reply.transaction_id = request->transaction_id; + reply.type = DHCP6_REPLY; + + memcpy(msg_reply, &reply.transaction_id, 4); + + memcpy(&msg_reply[26], test_duid, sizeof(test_duid)); + + memcpy(&msg_reply[44], &test_iaid, sizeof(test_iaid)); + + assert_se(write(test_dhcp_fd[1], msg_reply, sizeof(msg_reply)) + == sizeof(msg_reply)); + + return 0; +} + +static int test_client_verify_request(DHCP6Message *request, uint8_t *option, + size_t len) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t *optval; + uint16_t optcode; + size_t optlen; + bool found_clientid = false, found_iana = false, found_serverid = false, + found_elapsed_time = false; + int r; + struct in6_addr addr; + be32_t val; + uint32_t lt_pref, lt_valid; + + assert_se(request->type == DHCP6_REQUEST); + + assert_se(dhcp6_lease_new(&lease) >= 0); + + while ((r = dhcp6_option_parse(&option, &len, + &optcode, &optlen, &optval)) >= 0) { + switch(optcode) { + case SD_DHCP6_OPTION_CLIENTID: + assert_se(!found_clientid); + found_clientid = true; + + assert_se(!memcmp(optval, &test_duid, + sizeof(test_duid))); + + break; + + case SD_DHCP6_OPTION_IA_NA: + assert_se(!found_iana); + found_iana = true; + + + assert_se(optlen == 40); + assert_se(!memcmp(optval, &test_iaid, sizeof(test_iaid))); + + val = htobe32(80); + assert_se(!memcmp(optval + 4, &val, sizeof(val))); + + val = htobe32(120); + assert_se(!memcmp(optval + 8, &val, sizeof(val))); + + assert_se(!dhcp6_option_parse_ia(&optval, &optlen, + optcode, &lease->ia)); + + break; + + case SD_DHCP6_OPTION_SERVERID: + assert_se(!found_serverid); + found_serverid = true; + + assert_se(optlen == 14); + assert_se(!memcmp(&msg_advertise[179], optval, optlen)); + + break; + + case SD_DHCP6_OPTION_ELAPSED_TIME: + assert_se(!found_elapsed_time); + found_elapsed_time = true; + + assert_se(optlen == 2); + + break; + } + } + + assert_se(r == -ENOMSG); + assert_se(found_clientid && found_iana && found_serverid && + found_elapsed_time); + + sd_dhcp6_lease_reset_address_iter(lease); + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) >= 0); + assert_se(!memcmp(&addr, &msg_advertise[42], sizeof(addr))); + assert_se(lt_pref == 150); + assert_se(lt_valid == 180); + + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) == -ENOMSG); + + return 0; +} + +static int test_client_send_advertise(DHCP6Message *solicit) { + DHCP6Message advertise; + + advertise.transaction_id = solicit->transaction_id; + advertise.type = DHCP6_ADVERTISE; + + memcpy(msg_advertise, &advertise.transaction_id, 4); + + memcpy(&msg_advertise[8], test_duid, sizeof(test_duid)); + + memcpy(&msg_advertise[26], &test_iaid, sizeof(test_iaid)); + + assert_se(write(test_dhcp_fd[1], msg_advertise, sizeof(msg_advertise)) + == sizeof(msg_advertise)); + + return 0; +} + +static int test_client_verify_solicit(DHCP6Message *solicit, uint8_t *option, + size_t len) { + uint8_t *optval; + uint16_t optcode; + size_t optlen; + bool found_clientid = false, found_iana = false, + found_elapsed_time = false; + int r; + + assert_se(solicit->type == DHCP6_SOLICIT); + + while ((r = dhcp6_option_parse(&option, &len, + &optcode, &optlen, &optval)) >= 0) { + switch(optcode) { + case SD_DHCP6_OPTION_CLIENTID: + assert_se(!found_clientid); + found_clientid = true; + + assert_se(optlen == sizeof(test_duid)); + memcpy(&test_duid, optval, sizeof(test_duid)); + + break; + + case SD_DHCP6_OPTION_IA_NA: + assert_se(!found_iana); + found_iana = true; + + assert_se(optlen == 12); + + memcpy(&test_iaid, optval, sizeof(test_iaid)); + + break; + + case SD_DHCP6_OPTION_ELAPSED_TIME: + assert_se(!found_elapsed_time); + found_elapsed_time = true; + + assert_se(optlen == 2); + + break; + } + } + + assert_se(r == -ENOMSG); + assert_se(found_clientid && found_iana && found_elapsed_time); + + return 0; +} + +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 == 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_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t *optval; + uint16_t optcode; + size_t optlen; + bool found_clientid = false, found_elapsed_time = false; + int r; + struct in6_addr addr; + uint32_t lt_pref, lt_valid; + + assert_se(information_request->type == DHCP6_INFORMATION_REQUEST); + + assert_se(dhcp6_lease_new(&lease) >= 0); + + while ((r = dhcp6_option_parse(&option, &len, + &optcode, &optlen, &optval)) >= 0) { + switch(optcode) { + case SD_DHCP6_OPTION_CLIENTID: + assert_se(!found_clientid); + found_clientid = true; + + assert_se(optlen == sizeof(test_duid)); + memcpy(&test_duid, optval, sizeof(test_duid)); + + break; + + case SD_DHCP6_OPTION_IA_NA: + assert_not_reached("IA TA option must not be present"); + + break; + + case SD_DHCP6_OPTION_SERVERID: + assert_not_reached("Server ID option must not be present"); + + break; + + case SD_DHCP6_OPTION_ELAPSED_TIME: + assert_se(!found_elapsed_time); + found_elapsed_time = true; + + assert_se(optlen == 2); + + break; + } + } + + assert_se(r == -ENOMSG); + assert_se(found_clientid && found_elapsed_time); + + sd_dhcp6_lease_reset_address_iter(lease); + + assert_se(sd_dhcp6_lease_get_address(lease, &addr, <_pref, + <_valid) == -ENOMSG); + + return 0; +} + +int dhcp6_network_send_udp_socket(int s, struct in6_addr *server_address, + const void *packet, size_t len) { + struct in6_addr mcast = + IN6ADDR_ALL_DHCP6_RELAY_AGENTS_AND_SERVERS_INIT; + DHCP6Message *message; + uint8_t *option; + + assert_se(s == test_dhcp_fd[0]); + assert_se(server_address); + assert_se(packet); + assert_se(len > sizeof(DHCP6Message) + 4); + + assert_se(IN6_ARE_ADDR_EQUAL(server_address, &mcast)); + + message = (DHCP6Message *)packet; + option = (uint8_t *)(message + 1); + len -= sizeof(DHCP6Message); + + assert_se(message->transaction_id & 0x00ffffff); + + if (test_client_message_num == 0) { + test_client_verify_information_request(message, option, len); + test_client_send_reply(message); + test_client_message_num++; + } else if (test_client_message_num == 1) { + test_client_verify_solicit(message, option, len); + test_client_send_advertise(message); + test_client_message_num++; + } else if (test_client_message_num == 2) { + test_client_verify_request(message, option, len); + test_client_send_reply(message); + test_client_message_num++; + } + + return len; +} + +int dhcp6_network_bind_udp_socket(int index, struct in6_addr *local_address) { + assert_se(index == test_index); + + if (socketpair(AF_UNIX, SOCK_STREAM, 0, test_dhcp_fd) < 0) + return -errno; + + return test_dhcp_fd[0]; +} + +static int test_client_solicit(sd_event *e) { + sd_dhcp6_client *client; + usec_t time_now = now(clock_boottime_or_monotonic()); + 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__); + + assert_se(sd_dhcp6_client_new(&client) >= 0); + assert_se(client); + + 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_mac(client, (const uint8_t *) &mac_addr, + sizeof (mac_addr), + ARPHRD_ETHER) >= 0); + + assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0); + assert_se(val == false); + assert_se(sd_dhcp6_client_set_information_request(client, true) >= 0); + assert_se(sd_dhcp6_client_get_information_request(client, &val) >= 0); + assert_se(val == true); + + assert_se(sd_dhcp6_client_set_callback(client, + test_client_information_cb, e) >= 0); + + assert_se(sd_event_add_time(e, &hangcheck, clock_boottime_or_monotonic(), + 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); + + hangcheck = sd_event_source_unref(hangcheck); + + assert_se(!sd_dhcp6_client_unref(client)); + + test_dhcp_fd[1] = safe_close(test_dhcp_fd[1]); + + return 0; +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e; + + assert_se(sd_event_new(&e) >= 0); + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + test_client_basic(e); + test_option(e); + test_advertise_option(e); + test_client_solicit(e); + + 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..caa315b210 --- /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 <systemd/sd-event.h> +#include <systemd/sd-ipv4ll.h> +#include <systemd/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_index(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 new file mode 100644 index 0000000000..b7278834f2 --- /dev/null +++ b/src/libsystemd-network/test-ipv4ll.c @@ -0,0 +1,220 @@ +/*** + 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 <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/types.h> +#include <unistd.h> + +#include <systemd/sd-ipv4ll.h> + +#include "arp-util.h" +#include "fd-util.h" +#include "socket-util.h" +#include "util.h" + +static bool verbose = false; +static bool extended = false; +static int test_fd[2]; + +static int basic_request_handler_bind = 0; +static int basic_request_handler_stop = 0; +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_userdata); + + switch(event) { + case SD_IPV4LL_EVENT_STOP: + basic_request_handler_stop = 1; + break; + case SD_IPV4LL_EVENT_BIND: + basic_request_handler_bind = 1; + break; + default: + assert_se(0); + break; + } +} + +static int arp_network_send_raw_socket(int fd, int ifindex, + const struct ether_arp *arp) { + assert_se(arp); + assert_se(ifindex > 0); + assert_se(fd >= 0); + + if (send(fd, arp, sizeof(struct ether_arp), 0) < 0) + return -errno; + + return 0; +} + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + struct ether_arp ea = {}; + + assert(fd >= 0); + assert(ifindex > 0); + assert(pa != 0); + assert(ha); + + return arp_network_send_raw_socket(fd, ifindex, &ea); +} + +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + struct ether_arp ea = {}; + + assert(fd >= 0); + assert(ifindex > 0); + assert(pa != 0); + assert(ha); + + return arp_network_send_raw_socket(fd, ifindex, &ea); +} + +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; + + return test_fd[0]; +} + +static void test_public_api_setters(sd_event *e) { + struct in_addr address = {}; + unsigned seed = 0; + sd_ipv4ll *ll; + struct ether_addr mac_addr = { + .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + assert_se(sd_ipv4ll_new(&ll) == 0); + assert_se(ll); + + assert_se(sd_ipv4ll_attach_event(NULL, NULL, 0) == -EINVAL); + assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0); + assert_se(sd_ipv4ll_attach_event(ll, e, 0) == -EBUSY); + + 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(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_ref(ll) == ll); + assert_se(sd_ipv4ll_unref(ll) == NULL); + + /* Cleanup */ + assert_se(sd_ipv4ll_unref(ll) == NULL); +} + +static void test_basic_request(sd_event *e) { + + sd_ipv4ll *ll; + struct ether_arp arp; + struct ether_addr mac_addr = { + .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}}; + + if (verbose) + printf("* %s\n", __FUNCTION__); + + assert_se(sd_ipv4ll_new(&ll) == 0); + assert_se(sd_ipv4ll_start(ll) == -EINVAL); + + assert_se(sd_ipv4ll_attach_event(ll, e, 0) == 0); + assert_se(sd_ipv4ll_start(ll) == -EINVAL); + + assert_se(sd_ipv4ll_set_mac(ll, &mac_addr) == 0); + assert_se(sd_ipv4ll_start(ll) == -EINVAL); + + assert_se(sd_ipv4ll_set_callback(ll, basic_request_handler, + 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_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)); + + 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)); + + /* PROBE */ + sd_event_run(e, (uint64_t) -1); + assert_se(read(test_fd[1], &arp, sizeof(struct ether_arp)) == sizeof(struct ether_arp)); + + sd_event_run(e, (uint64_t) -1); + assert_se(basic_request_handler_bind == 1); + } + + sd_ipv4ll_stop(ll); + assert_se(basic_request_handler_stop == 1); + + /* Cleanup */ + assert_se(sd_ipv4ll_unref(ll) == NULL); + safe_close(test_fd[1]); +} + +int main(int argc, char *argv[]) { + _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_basic_request(e); + + return 0; +} diff --git a/src/libsystemd-network/test-lldp.c b/src/libsystemd-network/test-lldp.c new file mode 100644 index 0000000000..0243bc132b --- /dev/null +++ b/src/libsystemd-network/test-lldp.c @@ -0,0 +1,468 @@ +/*** + 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 <arpa/inet.h> +#include <net/ethernet.h> +#include <stdio.h> +#include <string.h> + +#include <systemd/sd-event.h> +#include <systemd/sd-lldp.h> + +#include "alloc-util.h" +#include "fd-util.h" +#include "lldp-network.h" +#include "lldp-tlv.h" +#include "lldp.h" +#include "macro.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 int test_fd[2]; + +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_(sd_lldp_packet_unrefp) 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); + + /* system descrition */ + assert_se(lldp_tlv_packet_open_container(m, LLDP_TYPE_SYSTEM_DESCRIPTION) >= 0); + + 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; +} + +static int lldp_parse_chassis_tlv(tlv_packet *m, uint8_t *type) { + uint8_t *p, subtype; + uint16_t length; + + assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_CHASSIS_ID) >= 0); + assert_se(tlv_packet_read_u8(m, &subtype) >= 0); + + switch (subtype) { + case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + + *type = LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS; + assert_se(tlv_packet_read_bytes(m, &p, &length) >= 0); + + assert_se(memcmp(p, &mac_addr.ether_addr_octet, ETHER_ADDR_LEN) == 0); + + break; + default: + assert_not_reached("Unhandled option"); + } + + assert_se(lldp_tlv_packet_exit_container(m) >= 0); + + 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); + + switch (subtype) { + case LLDP_PORT_SUBTYPE_INTERFACE_NAME: + assert_se(tlv_packet_read_string(m, &str, &length) >= 0); + + p = strndup(str, length-1); + assert_se(p); + + assert_se(streq(p, TEST_LLDP_PORT) == 1); + break; + default: + assert_not_reached("Unhandled option"); + } + + assert_se(lldp_tlv_packet_exit_container(m) >= 0); + + return 0; +} + +static int lldp_parse_system_name_tlv(tlv_packet *m) { + _cleanup_free_ char *p = NULL; + char *str = NULL; + uint16_t length; + + assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_SYSTEM_NAME) >= 0); + assert_se(tlv_packet_read_string(m, &str, &length) >= 0); + + p = strndup(str, length); + assert_se(p); + + assert_se(streq(p, TEST_LLDP_TYPE_SYSTEM_NAME) == 1); + + assert_se(lldp_tlv_packet_exit_container(m) >= 0); + + return 1; +} + +static int lldp_parse_system_desc_tlv(tlv_packet *m) { + _cleanup_free_ char *p = NULL; + char *str = NULL; + uint16_t length; + + assert_se(lldp_tlv_packet_enter_container(m, LLDP_TYPE_SYSTEM_DESCRIPTION) >= 0); + assert_se(tlv_packet_read_string(m, &str, &length) >= 0); + + p = strndup(str, length); + assert_se(p); + + assert_se(streq(p, TEST_LLDP_TYPE_SYSTEM_DESC) == 1); + + assert_se(lldp_tlv_packet_exit_container(m) >= 0); + + return 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); + + assert_se(ttl == 170); + + assert_se(lldp_tlv_packet_exit_container(m) >= 0); + + return 0; +} + +static int lldp_get_destination_type(tlv_packet *m) { + int dest; + + assert_se(sd_lldp_packet_get_destination_type(m, &dest) >= 0); + assert_se(dest == SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE); + + return 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); + + assert_se(lldp_get_destination_type(m) >= 0); + + return 0; +} + +static void test_parser(void) { + _cleanup_(sd_lldp_packet_unrefp) tlv_packet *tlv = NULL; + + /* form a packet */ + lldp_build_tlv_packet(&tlv); + /* parse the packet */ + tlv_packet_parse_pdu(tlv, tlv->length); + /* verify */ + lldp_parse_tlv_packet(tlv, tlv->length); +} + +int lldp_network_bind_raw_socket(int ifindex) { + if (socketpair(AF_UNIX, SOCK_DGRAM | SOCK_NONBLOCK, 0, test_fd) < 0) + return -errno; + + return test_fd[0]; +} + +static int lldp_handler_calls; +static void lldp_handler (sd_lldp *lldp, int event, void *userdata) { + lldp_handler_calls++; +} + +static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_cb_t cb, void *cb_data) { + int r; + + r = sd_lldp_new(42, "dummy", &mac_addr, lldp); + if (r) + return r; + + r = sd_lldp_attach_event(*lldp, e, 0); + if (r) + return r; + + r = sd_lldp_set_callback(*lldp, cb, cb_data); + if (r) + return r; + + r = sd_lldp_start(*lldp); + if (r) + return r; + + return 0; +} + +static int stop_lldp(sd_lldp *lldp) { + int r; + + r = sd_lldp_stop(lldp); + if (r) + return r; + + r = sd_lldp_detach_event(lldp); + if (r) + return r; + + sd_lldp_unref(lldp); + safe_close(test_fd[1]); + + return 0; +} + +static void test_receive_basic_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_packet **packets; + uint8_t type, *data; + uint16_t length, ttl; + int dest_type; + char *str; + 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 */ + }; + + 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_packets(lldp, &packets) == 1); + + assert_se(sd_lldp_packet_read_chassis_id(packets[0], &type, &data, &length) == 0); + assert_se(type == LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS); + assert_se(length == ETH_ALEN); + assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN)); + + assert_se(sd_lldp_packet_read_port_id(packets[0], &type, &data, &length) == 0); + assert_se(type == LLDP_PORT_SUBTYPE_INTERFACE_NAME); + assert_se(length == 3); + assert_se(strneq((char *) data, "1/3", 3)); + + assert_se(sd_lldp_packet_read_port_description(packets[0], &str, &length) == 0); + assert_se(length == 4); + assert_se(strneq(str, "Port", 4)); + + assert_se(sd_lldp_packet_read_system_name(packets[0], &str, &length) == 0); + assert_se(length == 3); + assert_se(strneq(str, "SYS", 3)); + + assert_se(sd_lldp_packet_read_system_description(packets[0], &str, &length) == 0); + assert_se(length == 4); /* This is the real length in the TLV packet */ + assert_se(strneq(str, "foo", 3)); + + assert_se(sd_lldp_packet_read_ttl(packets[0], &ttl) == 0); + assert_se(ttl == 120); + + assert_se(sd_lldp_packet_get_destination_type(packets[0], &dest_type) == 0); + assert_se(dest_type == SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE); + + sd_lldp_packet_unref(packets[0]); + free(packets); + + assert_se(stop_lldp(lldp) == 0); +} + +static void test_receive_incomplete_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_packet **packets; + 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 */ + }; + + 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 == 0); + assert_se(sd_lldp_get_packets(lldp, &packets) == 0); + + assert_se(stop_lldp(lldp) == 0); +} + +static void test_receive_oui_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_packet **packets; + uint32_t id32; + uint16_t id16, len; + uint8_t flags; + char *str; + 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 */ + }; + + 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_packets(lldp, &packets) == 1); + + assert_se(sd_lldp_packet_read_port_vlan_id(packets[0], &id16) == 0); + assert_se(id16 == 0x1234); + + assert_se(sd_lldp_packet_read_port_protocol_vlan_id(packets[0], &flags, &id16) == 0); + assert_se(flags == 1); + assert_se(id16 == 0x7788); + + assert_se(sd_lldp_packet_read_vlan_name(packets[0], &id16, &str, &len) == 0); + assert_se(id16 == 0x1234); + assert_se(len == 6); + assert_se(strneq(str, "Vlan51", 6)); + + assert_se(sd_lldp_packet_read_management_vid(packets[0], &id16) == 0); + assert_se(id16 == 0x0102); + + assert_se(sd_lldp_packet_read_link_aggregation(packets[0], &flags, &id32) == 0); + assert_se(flags == 1); + assert_se(id32 == 0x00140012); + + sd_lldp_packet_unref(packets[0]); + free(packets); + + assert_se(stop_lldp(lldp) == 0); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + + test_parser(); + + /* 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..863a76637c --- /dev/null +++ b/src/libsystemd-network/test-ndisc-rs.c @@ -0,0 +1,168 @@ +/*** + 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 <systemd/sd-ndisc.h> + +#include "icmp6-util.h" +#include "socket-util.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 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_rs_done(sd_ndisc *nd, uint8_t flags, const struct in6_addr *gateway, unsigned lifetime, int pref, void *userdata) { + sd_event *e = userdata; + static unsigned idx = 0; + uint8_t flags_array[] = { + 0, + 0, + 0, + ND_RA_FLAG_OTHER, + ND_RA_FLAG_MANAGED + }; + uint32_t mtu; + + assert_se(nd); + + assert_se(flags == flags_array[idx]); + idx++; + + if (verbose) + printf(" got event 0x%02x\n", flags); + + if (idx < ELEMENTSOF(flags_array)) { + send_ra(flags_array[idx]); + return; + } + + assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENOMSG); + + 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_index(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_rs_done, NULL, NULL, NULL, 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_router_discovery_start(nd) >= 0); + assert_se(sd_ndisc_stop(nd) >= 0); + + assert_se(sd_ndisc_router_discovery_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; +} |