From eb4b7726f3d9603da6a03029e67024e275c067c6 Mon Sep 17 00:00:00 2001 From: Luke Shumaker Date: Sat, 20 May 2017 17:21:57 -0400 Subject: ./tools/notsd-move --- src/libsystemd-network/GNUmakefile | 1 + src/libsystemd-network/Makefile | 30 +- src/libsystemd-network/arp-util.c | 154 -- src/libsystemd-network/arp-util.h | 32 - src/libsystemd-network/dhcp-identifier.c | 129 -- src/libsystemd-network/dhcp-identifier.h | 74 - src/libsystemd-network/dhcp-internal.h | 69 - src/libsystemd-network/dhcp-lease-internal.h | 102 -- src/libsystemd-network/dhcp-network.c | 235 --- src/libsystemd-network/dhcp-option.c | 263 --- src/libsystemd-network/dhcp-packet.c | 191 -- src/libsystemd-network/dhcp-protocol.h | 113 -- src/libsystemd-network/dhcp-server-internal.h | 96 - src/libsystemd-network/dhcp6-internal.h | 81 - src/libsystemd-network/dhcp6-lease-internal.h | 74 - src/libsystemd-network/dhcp6-network.c | 91 - src/libsystemd-network/dhcp6-option.c | 413 ----- src/libsystemd-network/dhcp6-protocol.h | 106 -- src/libsystemd-network/icmp6-util.c | 141 -- src/libsystemd-network/icmp6-util.h | 25 - .../include/systemd-network/_sd-common.h | 1 + .../include/systemd-network/arp-util.h | 32 + .../include/systemd-network/dhcp-identifier.h | 74 + .../include/systemd-network/dhcp-internal.h | 70 + .../include/systemd-network/dhcp-lease-internal.h | 103 ++ .../include/systemd-network/dhcp-protocol.h | 113 ++ .../include/systemd-network/dhcp-server-internal.h | 97 + .../include/systemd-network/dhcp6-internal.h | 81 + .../include/systemd-network/dhcp6-lease-internal.h | 73 + .../include/systemd-network/dhcp6-protocol.h | 106 ++ .../include/systemd-network/icmp6-util.h | 25 + .../include/systemd-network/lldp-internal.h | 56 + .../include/systemd-network/lldp-neighbor.h | 108 ++ .../include/systemd-network/lldp-network.h | 25 + .../include/systemd-network/ndisc-internal.h | 49 + .../include/systemd-network/ndisc-router.h | 62 + .../include/systemd-network/network-internal.h | 81 + .../include/systemd-network/sd-dhcp-client.h | 157 ++ .../include/systemd-network/sd-dhcp-lease.h | 67 + .../include/systemd-network/sd-dhcp-server.h | 65 + .../include/systemd-network/sd-dhcp6-client.h | 134 ++ .../include/systemd-network/sd-dhcp6-lease.h | 52 + .../include/systemd-network/sd-ipv4acd.h | 60 + .../include/systemd-network/sd-ipv4ll.h | 60 + .../include/systemd-network/sd-lldp.h | 182 ++ .../include/systemd-network/sd-ndisc.h | 130 ++ src/libsystemd-network/lldp-internal.h | 55 - src/libsystemd-network/lldp-neighbor.c | 813 --------- src/libsystemd-network/lldp-neighbor.h | 108 -- src/libsystemd-network/lldp-network.c | 77 - src/libsystemd-network/lldp-network.h | 25 - src/libsystemd-network/ndisc-internal.h | 49 - src/libsystemd-network/ndisc-router.c | 778 -------- src/libsystemd-network/ndisc-router.h | 62 - src/libsystemd-network/network-internal.c | 556 ------ src/libsystemd-network/network-internal.h | 81 - src/libsystemd-network/sd-dhcp-client.c | 1904 -------------------- src/libsystemd-network/sd-dhcp-lease.c | 1176 ------------ src/libsystemd-network/sd-dhcp-server.c | 1174 ------------ src/libsystemd-network/sd-dhcp6-client.c | 1333 -------------- src/libsystemd-network/sd-dhcp6-lease.c | 408 ----- src/libsystemd-network/sd-ipv4acd.c | 524 ------ src/libsystemd-network/sd-ipv4ll.c | 344 ---- src/libsystemd-network/sd-lldp.c | 537 ------ src/libsystemd-network/sd-ndisc.c | 420 ----- src/libsystemd-network/src/GNUmakefile | 1 + src/libsystemd-network/src/Makefile | 83 + src/libsystemd-network/src/arp-util.c | 155 ++ src/libsystemd-network/src/dhcp-identifier.c | 129 ++ src/libsystemd-network/src/dhcp-network.c | 236 +++ src/libsystemd-network/src/dhcp-option.c | 262 +++ src/libsystemd-network/src/dhcp-packet.c | 191 ++ src/libsystemd-network/src/dhcp6-network.c | 92 + src/libsystemd-network/src/dhcp6-option.c | 412 +++++ src/libsystemd-network/src/icmp6-util.c | 142 ++ src/libsystemd-network/src/lldp-neighbor.c | 813 +++++++++ src/libsystemd-network/src/lldp-network.c | 78 + src/libsystemd-network/src/ndisc-router.c | 777 ++++++++ src/libsystemd-network/src/network-internal.c | 556 ++++++ src/libsystemd-network/src/sd-dhcp-client.c | 1904 ++++++++++++++++++++ src/libsystemd-network/src/sd-dhcp-lease.c | 1175 ++++++++++++ src/libsystemd-network/src/sd-dhcp-server.c | 1173 ++++++++++++ src/libsystemd-network/src/sd-dhcp6-client.c | 1333 ++++++++++++++ src/libsystemd-network/src/sd-dhcp6-lease.c | 408 +++++ src/libsystemd-network/src/sd-ipv4acd.c | 523 ++++++ src/libsystemd-network/src/sd-ipv4ll.c | 343 ++++ src/libsystemd-network/src/sd-lldp.c | 536 ++++++ src/libsystemd-network/src/sd-ndisc.c | 419 +++++ src/libsystemd-network/test-acd.c | 114 -- src/libsystemd-network/test-dhcp-client.c | 513 ------ src/libsystemd-network/test-dhcp-option.c | 367 ---- src/libsystemd-network/test-dhcp-server.c | 261 --- src/libsystemd-network/test-dhcp6-client.c | 763 -------- src/libsystemd-network/test-ipv4ll-manual.c | 128 -- src/libsystemd-network/test-ipv4ll.c | 221 --- src/libsystemd-network/test-lldp.c | 261 --- src/libsystemd-network/test-ndisc-rs.c | 317 ---- src/libsystemd-network/test/GNUmakefile | 1 + src/libsystemd-network/test/Makefile | 118 ++ src/libsystemd-network/test/test-acd.c | 114 ++ src/libsystemd-network/test/test-dhcp-client.c | 513 ++++++ src/libsystemd-network/test/test-dhcp-option.c | 367 ++++ src/libsystemd-network/test/test-dhcp-server.c | 261 +++ src/libsystemd-network/test/test-dhcp6-client.c | 763 ++++++++ src/libsystemd-network/test/test-ipv4ll-manual.c | 129 ++ src/libsystemd-network/test/test-ipv4ll.c | 220 +++ src/libsystemd-network/test/test-lldp.c | 261 +++ src/libsystemd-network/test/test-ndisc-rs.c | 316 ++++ 108 files changed, 16897 insertions(+), 15759 deletions(-) create mode 120000 src/libsystemd-network/GNUmakefile mode change 120000 => 100644 src/libsystemd-network/Makefile delete mode 100644 src/libsystemd-network/arp-util.c delete mode 100644 src/libsystemd-network/arp-util.h delete mode 100644 src/libsystemd-network/dhcp-identifier.c delete mode 100644 src/libsystemd-network/dhcp-identifier.h delete mode 100644 src/libsystemd-network/dhcp-internal.h delete mode 100644 src/libsystemd-network/dhcp-lease-internal.h delete mode 100644 src/libsystemd-network/dhcp-network.c delete mode 100644 src/libsystemd-network/dhcp-option.c delete mode 100644 src/libsystemd-network/dhcp-packet.c delete mode 100644 src/libsystemd-network/dhcp-protocol.h delete mode 100644 src/libsystemd-network/dhcp-server-internal.h delete mode 100644 src/libsystemd-network/dhcp6-internal.h delete mode 100644 src/libsystemd-network/dhcp6-lease-internal.h delete mode 100644 src/libsystemd-network/dhcp6-network.c delete mode 100644 src/libsystemd-network/dhcp6-option.c delete mode 100644 src/libsystemd-network/dhcp6-protocol.h delete mode 100644 src/libsystemd-network/icmp6-util.c delete mode 100644 src/libsystemd-network/icmp6-util.h create mode 120000 src/libsystemd-network/include/systemd-network/_sd-common.h create mode 100644 src/libsystemd-network/include/systemd-network/arp-util.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp-identifier.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp-lease-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp-protocol.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp-server-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp6-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp6-lease-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/dhcp6-protocol.h create mode 100644 src/libsystemd-network/include/systemd-network/icmp6-util.h create mode 100644 src/libsystemd-network/include/systemd-network/lldp-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/lldp-neighbor.h create mode 100644 src/libsystemd-network/include/systemd-network/lldp-network.h create mode 100644 src/libsystemd-network/include/systemd-network/ndisc-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/ndisc-router.h create mode 100644 src/libsystemd-network/include/systemd-network/network-internal.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-dhcp-client.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-dhcp-server.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-ipv4acd.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-ipv4ll.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-lldp.h create mode 100644 src/libsystemd-network/include/systemd-network/sd-ndisc.h delete mode 100644 src/libsystemd-network/lldp-internal.h delete mode 100644 src/libsystemd-network/lldp-neighbor.c delete mode 100644 src/libsystemd-network/lldp-neighbor.h delete mode 100644 src/libsystemd-network/lldp-network.c delete mode 100644 src/libsystemd-network/lldp-network.h delete mode 100644 src/libsystemd-network/ndisc-internal.h delete mode 100644 src/libsystemd-network/ndisc-router.c delete mode 100644 src/libsystemd-network/ndisc-router.h delete mode 100644 src/libsystemd-network/network-internal.c delete mode 100644 src/libsystemd-network/network-internal.h delete mode 100644 src/libsystemd-network/sd-dhcp-client.c delete mode 100644 src/libsystemd-network/sd-dhcp-lease.c delete mode 100644 src/libsystemd-network/sd-dhcp-server.c delete mode 100644 src/libsystemd-network/sd-dhcp6-client.c delete mode 100644 src/libsystemd-network/sd-dhcp6-lease.c delete mode 100644 src/libsystemd-network/sd-ipv4acd.c delete mode 100644 src/libsystemd-network/sd-ipv4ll.c delete mode 100644 src/libsystemd-network/sd-lldp.c delete mode 100644 src/libsystemd-network/sd-ndisc.c create mode 120000 src/libsystemd-network/src/GNUmakefile create mode 100644 src/libsystemd-network/src/Makefile create mode 100644 src/libsystemd-network/src/arp-util.c create mode 100644 src/libsystemd-network/src/dhcp-identifier.c create mode 100644 src/libsystemd-network/src/dhcp-network.c create mode 100644 src/libsystemd-network/src/dhcp-option.c create mode 100644 src/libsystemd-network/src/dhcp-packet.c create mode 100644 src/libsystemd-network/src/dhcp6-network.c create mode 100644 src/libsystemd-network/src/dhcp6-option.c create mode 100644 src/libsystemd-network/src/icmp6-util.c create mode 100644 src/libsystemd-network/src/lldp-neighbor.c create mode 100644 src/libsystemd-network/src/lldp-network.c create mode 100644 src/libsystemd-network/src/ndisc-router.c create mode 100644 src/libsystemd-network/src/network-internal.c create mode 100644 src/libsystemd-network/src/sd-dhcp-client.c create mode 100644 src/libsystemd-network/src/sd-dhcp-lease.c create mode 100644 src/libsystemd-network/src/sd-dhcp-server.c create mode 100644 src/libsystemd-network/src/sd-dhcp6-client.c create mode 100644 src/libsystemd-network/src/sd-dhcp6-lease.c create mode 100644 src/libsystemd-network/src/sd-ipv4acd.c create mode 100644 src/libsystemd-network/src/sd-ipv4ll.c create mode 100644 src/libsystemd-network/src/sd-lldp.c create mode 100644 src/libsystemd-network/src/sd-ndisc.c delete mode 100644 src/libsystemd-network/test-acd.c delete mode 100644 src/libsystemd-network/test-dhcp-client.c delete mode 100644 src/libsystemd-network/test-dhcp-option.c delete mode 100644 src/libsystemd-network/test-dhcp-server.c delete mode 100644 src/libsystemd-network/test-dhcp6-client.c delete mode 100644 src/libsystemd-network/test-ipv4ll-manual.c delete mode 100644 src/libsystemd-network/test-ipv4ll.c delete mode 100644 src/libsystemd-network/test-lldp.c delete mode 100644 src/libsystemd-network/test-ndisc-rs.c create mode 120000 src/libsystemd-network/test/GNUmakefile create mode 100644 src/libsystemd-network/test/Makefile create mode 100644 src/libsystemd-network/test/test-acd.c create mode 100644 src/libsystemd-network/test/test-dhcp-client.c create mode 100644 src/libsystemd-network/test/test-dhcp-option.c create mode 100644 src/libsystemd-network/test/test-dhcp-server.c create mode 100644 src/libsystemd-network/test/test-dhcp6-client.c create mode 100644 src/libsystemd-network/test/test-ipv4ll-manual.c create mode 100644 src/libsystemd-network/test/test-ipv4ll.c create mode 100644 src/libsystemd-network/test/test-lldp.c create mode 100644 src/libsystemd-network/test/test-ndisc-rs.c (limited to 'src/libsystemd-network') diff --git a/src/libsystemd-network/GNUmakefile b/src/libsystemd-network/GNUmakefile new file mode 120000 index 0000000000..54fdd42278 --- /dev/null +++ b/src/libsystemd-network/GNUmakefile @@ -0,0 +1 @@ +../../GNUmakefile \ No newline at end of file diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile deleted file mode 120000 index d0b0e8e008..0000000000 --- a/src/libsystemd-network/Makefile +++ /dev/null @@ -1 +0,0 @@ -../Makefile \ No newline at end of file diff --git a/src/libsystemd-network/Makefile b/src/libsystemd-network/Makefile new file mode 100644 index 0000000000..8ba25db413 --- /dev/null +++ b/src/libsystemd-network/Makefile @@ -0,0 +1,29 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +nested.subdirs += src +nested.subdirs += test + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd-network/arp-util.c b/src/libsystemd-network/arp-util.c deleted file mode 100644 index 02028bf28a..0000000000 --- a/src/libsystemd-network/arp-util.c +++ /dev/null @@ -1,154 +0,0 @@ -/*** - 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 . -***/ - -#include -#include - -#include "arp-util.h" -#include "fd-util.h" -#include "util.h" - -int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { - struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ - BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - /* Sender Hardware Address must be different from our own */ - BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((uint32_t *) eth_mac))), /* A <- 4 bytes of client's MAC */ - BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ - BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ - BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((uint16_t *) (((char *) eth_mac) + 4)))), /* A <- remainder of client's MAC */ - BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ - BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about*/ - BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ - BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ - BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ - BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ - BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ - BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ - BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ - }; - struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = (struct sock_filter*) filter - }; - union sockaddr_union link = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_ARP), - .ll.sll_ifindex = ifindex, - .ll.sll_halen = ETH_ALEN, - .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - }; - _cleanup_close_ int s = -1; - int r; - - assert(ifindex > 0); - - s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); - if (s < 0) - return -errno; - - r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); - if (r < 0) - return -errno; - - r = bind(s, &link.sa, sizeof(link.ll)); - if (r < 0) - return -errno; - - r = s; - s = -1; - - return r; -} - -static int arp_send_packet(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha, - bool announce) { - union sockaddr_union link = { - .ll.sll_family = AF_PACKET, - .ll.sll_protocol = htobe16(ETH_P_ARP), - .ll.sll_ifindex = ifindex, - .ll.sll_halen = ETH_ALEN, - .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, - }; - struct ether_arp arp = { - .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ - .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ - .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ - .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ - .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ - }; - int r; - - assert(fd >= 0); - assert(pa != 0); - assert(ha); - - memcpy(&arp.arp_sha, ha, ETH_ALEN); - memcpy(&arp.arp_tpa, &pa, sizeof(pa)); - - if (announce) - memcpy(&arp.arp_spa, &pa, sizeof(pa)); - - r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); - if (r < 0) - return -errno; - - return 0; -} - -int arp_send_probe(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha) { - return arp_send_packet(fd, ifindex, pa, ha, false); -} - -int arp_send_announcement(int fd, int ifindex, - be32_t pa, const struct ether_addr *ha) { - return arp_send_packet(fd, ifindex, pa, ha, true); -} diff --git a/src/libsystemd-network/arp-util.h b/src/libsystemd-network/arp-util.h deleted file mode 100644 index 3ef56b002a..0000000000 --- a/src/libsystemd-network/arp-util.h +++ /dev/null @@ -1,32 +0,0 @@ -#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 . -***/ - -#include - -#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 deleted file mode 100644 index a21efc4d06..0000000000 --- a/src/libsystemd-network/dhcp-identifier.c +++ /dev/null @@ -1,129 +0,0 @@ -/*** - This file is part of systemd. - - 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 . -***/ - -#include "libudev.h" -#include "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_validate_duid_len(uint16_t duid_type, size_t duid_len) { - struct duid d; - - assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); - if (duid_len > MAX_DUID_LEN) - return -EINVAL; - - switch (duid_type) { - case DUID_TYPE_LLT: - if (duid_len <= sizeof(d.llt)) - return -EINVAL; - break; - case DUID_TYPE_EN: - if (duid_len != sizeof(d.en)) - return -EINVAL; - break; - case DUID_TYPE_LL: - if (duid_len <= sizeof(d.ll)) - return -EINVAL; - break; - case DUID_TYPE_UUID: - if (duid_len != sizeof(d.uuid)) - return -EINVAL; - break; - default: - /* accept unknown type in order to be forward compatible */ - break; - } - return 0; -} - -int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { - sd_id128_t machine_id; - uint64_t hash; - int r; - - assert(duid); - assert(len); - - r = sd_id128_get_machine(&machine_id); - if (r < 0) - return r; - - unaligned_write_be16(&duid->type, DUID_TYPE_EN); - unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); - - *len = sizeof(duid->type) + sizeof(duid->en); - - /* a bit of snake-oil perhaps, but no need to expose the machine-id - directly; 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 deleted file mode 100644 index 1cc0f9fb71..0000000000 --- a/src/libsystemd-network/dhcp-identifier.h +++ /dev/null @@ -1,74 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - 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 . -***/ - -#include "sd-id128.h" - -#include "macro.h" -#include "sparse-endian.h" -#include "unaligned.h" - -typedef enum DUIDType { - DUID_TYPE_LLT = 1, - DUID_TYPE_EN = 2, - DUID_TYPE_LL = 3, - DUID_TYPE_UUID = 4, - _DUID_TYPE_MAX, - _DUID_TYPE_INVALID = -1, -} DUIDType; - -/* RFC 3315 section 9.1: - * A DUID can be no more than 128 octets long (not including the type code). - */ -#define MAX_DUID_LEN 128 - -/* https://tools.ietf.org/html/rfc3315#section-9.1 */ -struct duid { - be16_t type; - union { - struct { - /* DUID_TYPE_LLT */ - uint16_t htype; - uint32_t time; - uint8_t haddr[0]; - } _packed_ llt; - struct { - /* DUID_TYPE_EN */ - uint32_t pen; - uint8_t id[8]; - } _packed_ en; - struct { - /* DUID_TYPE_LL */ - int16_t htype; - uint8_t haddr[0]; - } _packed_ ll; - struct { - /* DUID_TYPE_UUID */ - sd_id128_t uuid; - } _packed_ uuid; - struct { - uint8_t data[MAX_DUID_LEN]; - } _packed_ raw; - }; -} _packed_; - -int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len); -int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); -int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id); diff --git a/src/libsystemd-network/dhcp-internal.h b/src/libsystemd-network/dhcp-internal.h deleted file mode 100644 index 99f690897d..0000000000 --- a/src/libsystemd-network/dhcp-internal.h +++ /dev/null @@ -1,69 +0,0 @@ -#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 . -***/ - -#include -#include -#include -#include - -#include "sd-dhcp-client.h" - -#include "dhcp-protocol.h" -#include "socket-util.h" - -int dhcp_network_bind_raw_socket(int index, union sockaddr_union *link, - uint32_t xid, const uint8_t *mac_addr, - 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_callback_t)(uint8_t code, uint8_t len, - const void *option, void *userdata); - -int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message); - -int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, - uint8_t type, uint16_t arp_type, size_t optlen, - 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_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__) -#define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/dhcp-lease-internal.h b/src/libsystemd-network/dhcp-lease-internal.h deleted file mode 100644 index 82cae2300a..0000000000 --- a/src/libsystemd-network/dhcp-lease-internal.h +++ /dev/null @@ -1,102 +0,0 @@ -#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 . -***/ - -#include -#include - -#include "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 deleted file mode 100644 index a9f5a0a5de..0000000000 --- a/src/libsystemd-network/dhcp-network.c +++ /dev/null @@ -1,235 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 = htobe16(ETH_P_IP); - link->ll.sll_ifindex = ifindex; - link->ll.sll_hatype = htobe16(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 deleted file mode 100644 index c105196334..0000000000 --- a/src/libsystemd-network/dhcp-option.c +++ /dev/null @@ -1,263 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include - -#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; - - memcpy_safe(&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_callback_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_callback_t cb, void *userdata, char **_error_message) { - _cleanup_free_ char *error_message = NULL; - uint8_t overload = 0; - uint8_t message_type = 0; - int r; - - 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 deleted file mode 100644 index 8be774061d..0000000000 --- a/src/libsystemd-network/dhcp-packet.c +++ /dev/null @@ -1,191 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include - -#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 deleted file mode 100644 index 5cf7abbff9..0000000000 --- a/src/libsystemd-network/dhcp-protocol.h +++ /dev/null @@ -1,113 +0,0 @@ -#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 . -***/ - -#include -#include -#include - -#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 deleted file mode 100644 index 0c76956fad..0000000000 --- a/src/libsystemd-network/dhcp-server-internal.h +++ /dev/null @@ -1,96 +0,0 @@ -#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 . -***/ - -#include "sd-dhcp-server.h" -#include "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; - - bool emit_router; - - Hashmap *leases_by_client_id; - DHCPLease **bound_leases; - DHCPLease invalid_lease; - - uint32_t max_lease_time, default_lease_time; -}; - -typedef struct DHCPRequest { - /* 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 deleted file mode 100644 index 945c3b9721..0000000000 --- a/src/libsystemd-network/dhcp6-internal.h +++ /dev/null @@ -1,81 +0,0 @@ -#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 . -***/ - -#include -#include - -#include "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_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__) -#define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__) - -int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, - size_t optlen, const void *optval); -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 deleted file mode 100644 index 14e708ef63..0000000000 --- a/src/libsystemd-network/dhcp6-lease-internal.h +++ /dev/null @@ -1,74 +0,0 @@ -#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 . -***/ - -#include - -#include "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 deleted file mode 100644 index fd2d60c9d5..0000000000 --- a/src/libsystemd-network/dhcp6-network.c +++ /dev/null @@ -1,91 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 deleted file mode 100644 index 5462e03476..0000000000 --- a/src/libsystemd-network/dhcp6-option.c +++ /dev/null @@ -1,413 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include - -#include "sd-dhcp6-client.h" - -#include "alloc-util.h" -#include "dhcp6-internal.h" -#include "dhcp6-protocol.h" -#include "dns-domain.h" -#include "sparse-endian.h" -#include "strv.h" -#include "unaligned.h" -#include "util.h" - -#define DHCP6_OPTION_IA_NA_LEN 12 -#define DHCP6_OPTION_IA_TA_LEN 4 - -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; - - memcpy_safe(*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 deleted file mode 100644 index 2487c470ab..0000000000 --- a/src/libsystemd-network/dhcp6-protocol.h +++ /dev/null @@ -1,106 +0,0 @@ -#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 . -***/ - -#include -#include - -#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 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 deleted file mode 100644 index c2e4b0e9e3..0000000000 --- a/src/libsystemd-network/icmp6-util.c +++ /dev/null @@ -1,141 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fd-util.h" -#include "icmp6-util.h" -#include "socket-util.h" - -#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } - -#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ - { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } - -int icmp6_bind_router_solicitation(int index) { - struct icmp6_filter filter = { }; - struct ipv6_mreq mreq = { - .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, - .ipv6mr_interface = index, - }; - _cleanup_close_ int s = -1; - char ifname[IF_NAMESIZE] = ""; - static const int zero = 0, one = 1, hops = 255; - int r; - - s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); - if (s < 0) - return -errno; - - ICMP6_FILTER_SETBLOCKALL(&filter); - ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); - r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); - if (r < 0) - return -errno; - - /* RFC 3315, section 6.7, bullet point 2 may indicate that an - IPV6_PKTINFO socket option also applies for ICMPv6 multicast. - Empirical experiments indicates otherwise and therefore an - IPV6_MULTICAST_IF socket option is used here instead */ - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)); - if (r < 0) - return -errno; - - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)); - if (r < 0) - return -errno; - - r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops)); - if (r < 0) - return -errno; - - r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); - if (r < 0) - return -errno; - - r = setsockopt(s, SOL_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); - if (r < 0) - return -errno; - - r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); - if (r < 0) - return -errno; - - if (if_indextoname(index, ifname) == 0) - return -errno; - - r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); - if (r < 0) - return -errno; - - r = s; - s = -1; - return r; -} - -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - struct sockaddr_in6 dst = { - .sin6_family = AF_INET6, - .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, - }; - struct { - struct nd_router_solicit rs; - struct nd_opt_hdr rs_opt; - struct ether_addr rs_opt_mac; - } _packed_ rs = { - .rs.nd_rs_type = ND_ROUTER_SOLICIT, - .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, - .rs_opt.nd_opt_len = 1, - }; - struct iovec iov = { - .iov_base = &rs, - .iov_len = sizeof(rs), - }; - struct msghdr msg = { - .msg_name = &dst, - .msg_namelen = sizeof(dst), - .msg_iov = &iov, - .msg_iovlen = 1, - }; - int r; - - assert(s >= 0); - assert(ether_addr); - - rs.rs_opt_mac = *ether_addr; - - r = sendmsg(s, &msg, 0); - if (r < 0) - return -errno; - - return 0; -} diff --git a/src/libsystemd-network/icmp6-util.h b/src/libsystemd-network/icmp6-util.h deleted file mode 100644 index 2b4dbc76ce..0000000000 --- a/src/libsystemd-network/icmp6-util.h +++ /dev/null @@ -1,25 +0,0 @@ -#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 . -***/ - -#include - -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/include/systemd-network/_sd-common.h b/src/libsystemd-network/include/systemd-network/_sd-common.h new file mode 120000 index 0000000000..653327c267 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/_sd-common.h @@ -0,0 +1 @@ +../../../libsystemd/include/systemd/_sd-common.h \ No newline at end of file diff --git a/src/libsystemd-network/include/systemd-network/arp-util.h b/src/libsystemd-network/include/systemd-network/arp-util.h new file mode 100644 index 0000000000..790085a05b --- /dev/null +++ b/src/libsystemd-network/include/systemd-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 . +***/ + +#include + +#include "systemd-basic/socket-util.h" +#include "systemd-basic/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/include/systemd-network/dhcp-identifier.h b/src/libsystemd-network/include/systemd-network/dhcp-identifier.h new file mode 100644 index 0000000000..187780e7b0 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp-identifier.h @@ -0,0 +1,74 @@ +#pragma once + +/*** + This file is part of systemd. + + 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 . +***/ + +#include + +#include "systemd-basic/macro.h" +#include "systemd-basic/sparse-endian.h" +#include "systemd-basic/unaligned.h" + +typedef enum DUIDType { + DUID_TYPE_LLT = 1, + DUID_TYPE_EN = 2, + DUID_TYPE_LL = 3, + DUID_TYPE_UUID = 4, + _DUID_TYPE_MAX, + _DUID_TYPE_INVALID = -1, +} DUIDType; + +/* RFC 3315 section 9.1: + * A DUID can be no more than 128 octets long (not including the type code). + */ +#define MAX_DUID_LEN 128 + +/* https://tools.ietf.org/html/rfc3315#section-9.1 */ +struct duid { + be16_t type; + union { + struct { + /* DUID_TYPE_LLT */ + uint16_t htype; + uint32_t time; + uint8_t haddr[0]; + } _packed_ llt; + struct { + /* DUID_TYPE_EN */ + uint32_t pen; + uint8_t id[8]; + } _packed_ en; + struct { + /* DUID_TYPE_LL */ + int16_t htype; + uint8_t haddr[0]; + } _packed_ ll; + struct { + /* DUID_TYPE_UUID */ + sd_id128_t uuid; + } _packed_ uuid; + struct { + uint8_t data[MAX_DUID_LEN]; + } _packed_ raw; + }; +} _packed_; + +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len); +int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len); +int dhcp_identifier_set_iaid(int ifindex, uint8_t *mac, size_t mac_len, void *_id); diff --git a/src/libsystemd-network/include/systemd-network/dhcp-internal.h b/src/libsystemd-network/include/systemd-network/dhcp-internal.h new file mode 100644 index 0000000000..f2f44918cb --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp-internal.h @@ -0,0 +1,70 @@ +#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 . +***/ + +#include +#include +#include + +#include + +#include "systemd-basic/socket-util.h" + +#include "dhcp-protocol.h" +#include "sd-dhcp-client.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_callback_t)(uint8_t code, uint8_t len, + const void *option, void *userdata); + +int dhcp_option_parse(DHCPMessage *message, size_t len, dhcp_option_callback_t cb, void *userdata, char **error_message); + +int dhcp_message_init(DHCPMessage *message, uint8_t op, uint32_t xid, + uint8_t type, uint16_t arp_type, size_t optlen, + 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_errno(client, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCP CLIENT (0x%x): " fmt, client->xid, ##__VA_ARGS__) +#define log_dhcp_client(client, fmt, ...) log_dhcp_client_errno(client, 0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/include/systemd-network/dhcp-lease-internal.h b/src/libsystemd-network/include/systemd-network/dhcp-lease-internal.h new file mode 100644 index 0000000000..92dd668876 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp-lease-internal.h @@ -0,0 +1,103 @@ +#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 . +***/ + +#include + +#include + +#include "systemd-basic/list.h" +#include "systemd-basic/util.h" + +#include "dhcp-protocol.h" +#include "sd-dhcp-client.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/include/systemd-network/dhcp-protocol.h b/src/libsystemd-network/include/systemd-network/dhcp-protocol.h new file mode 100644 index 0000000000..7b083afa8b --- /dev/null +++ b/src/libsystemd-network/include/systemd-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 . +***/ + +#include +#include +#include + +#include "systemd-basic/macro.h" +#include "systemd-basic/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/include/systemd-network/dhcp-server-internal.h b/src/libsystemd-network/include/systemd-network/dhcp-server-internal.h new file mode 100644 index 0000000000..bdbe63c450 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp-server-internal.h @@ -0,0 +1,97 @@ +#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 . +***/ + +#include + +#include "systemd-basic/hashmap.h" +#include "systemd-basic/log.h" +#include "systemd-basic/util.h" + +#include "dhcp-internal.h" +#include "sd-dhcp-server.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; + + bool emit_router; + + Hashmap *leases_by_client_id; + DHCPLease **bound_leases; + DHCPLease invalid_lease; + + uint32_t max_lease_time, default_lease_time; +}; + +typedef struct DHCPRequest { + /* 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/include/systemd-network/dhcp6-internal.h b/src/libsystemd-network/include/systemd-network/dhcp6-internal.h new file mode 100644 index 0000000000..6d3178e2d6 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp6-internal.h @@ -0,0 +1,81 @@ +#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 . +***/ + +#include +#include + +#include + +#include "systemd-basic/list.h" +#include "systemd-basic/macro.h" +#include "systemd-basic/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_errno(p, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "DHCPv6 CLIENT: " fmt, ##__VA_ARGS__) +#define log_dhcp6_client(p, fmt, ...) log_dhcp6_client_errno(p, 0, fmt, ##__VA_ARGS__) + +int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code, + size_t optlen, const void *optval); +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/include/systemd-network/dhcp6-lease-internal.h b/src/libsystemd-network/include/systemd-network/dhcp6-lease-internal.h new file mode 100644 index 0000000000..9da270e9a9 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp6-lease-internal.h @@ -0,0 +1,73 @@ +#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 . +***/ + +#include + +#include "dhcp6-internal.h" +#include "sd-dhcp6-lease.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/include/systemd-network/dhcp6-protocol.h b/src/libsystemd-network/include/systemd-network/dhcp6-protocol.h new file mode 100644 index 0000000000..7449b2b20d --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/dhcp6-protocol.h @@ -0,0 +1,106 @@ +#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 . +***/ + +#include +#include + +#include "systemd-basic/macro.h" +#include "systemd-basic/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 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/include/systemd-network/icmp6-util.h b/src/libsystemd-network/include/systemd-network/icmp6-util.h new file mode 100644 index 0000000000..2b4dbc76ce --- /dev/null +++ b/src/libsystemd-network/include/systemd-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 . +***/ + +#include + +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/include/systemd-network/lldp-internal.h b/src/libsystemd-network/include/systemd-network/lldp-internal.h new file mode 100644 index 0000000000..e28b9702a0 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/lldp-internal.h @@ -0,0 +1,56 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +#include "systemd-basic/hashmap.h" +#include "systemd-basic/log.h" +#include "systemd-basic/prioq.h" + +#include "sd-lldp.h" + +struct sd_lldp { + unsigned n_ref; + + int ifindex; + int fd; + + sd_event *event; + int64_t event_priority; + sd_event_source *io_event_source; + sd_event_source *timer_event_source; + + Prioq *neighbor_by_expiry; + Hashmap *neighbor_by_id; + + uint64_t neighbors_max; + + sd_lldp_callback_t callback; + void *userdata; + + uint16_t capability_mask; + + struct ether_addr filter_address; +}; + +#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__) +#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/include/systemd-network/lldp-neighbor.h b/src/libsystemd-network/include/systemd-network/lldp-neighbor.h new file mode 100644 index 0000000000..392a0a268b --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/lldp-neighbor.h @@ -0,0 +1,108 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include + +#include "systemd-basic/hash-funcs.h" +#include "systemd-basic/time-util.h" + +#include "lldp-internal.h" +#include "sd-lldp.h" + +typedef struct LLDPNeighborID { + /* The spec calls this an "MSAP identifier" */ + void *chassis_id; + size_t chassis_id_size; + + void *port_id; + size_t port_id_size; +} LLDPNeighborID; + +struct sd_lldp_neighbor { + /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */ + sd_lldp *lldp; + unsigned n_ref; + + triple_timestamp timestamp; + + usec_t until; + unsigned prioq_idx; + + struct ether_addr source_address; + struct ether_addr destination_address; + + LLDPNeighborID id; + + /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */ + size_t raw_size; + + /* The current read index for the iterative TLV interface */ + size_t rindex; + + /* And a couple of fields parsed out. */ + bool has_ttl:1; + bool has_capabilities:1; + bool has_port_vlan_id:1; + + uint16_t ttl; + + uint16_t system_capabilities; + uint16_t enabled_capabilities; + + char *port_description; + char *system_name; + char *system_description; + + uint16_t port_vlan_id; + + char *chassis_id_as_string; + char *port_id_as_string; +}; + +static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) { + return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor)); +} + +static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1; +} + +static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) { + uint8_t *p; + + p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + return p[1] + (((size_t) (p[0] & 1)) << 8); +} + +static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) { + return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; +} + +extern const struct hash_ops lldp_neighbor_id_hash_ops; +int lldp_neighbor_prioq_compare_func(const void *a, const void *b); + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n); +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); +int lldp_neighbor_parse(sd_lldp_neighbor *n); +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); diff --git a/src/libsystemd-network/include/systemd-network/lldp-network.h b/src/libsystemd-network/include/systemd-network/lldp-network.h new file mode 100644 index 0000000000..43ed54b3b2 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/lldp-network.h @@ -0,0 +1,25 @@ +#pragma once + +/*** + 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 . +***/ + +#include + +int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/libsystemd-network/include/systemd-network/ndisc-internal.h b/src/libsystemd-network/include/systemd-network/ndisc-internal.h new file mode 100644 index 0000000000..0f7f69a33d --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/ndisc-internal.h @@ -0,0 +1,49 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "systemd-basic/log.h" + +#include "sd-ndisc.h" + +struct sd_ndisc { + unsigned n_ref; + + int ifindex; + int fd; + + sd_event *event; + int event_priority; + + struct ether_addr mac_addr; + uint8_t hop_limit; + uint32_t mtu; + + sd_event_source *recv_event_source; + sd_event_source *timeout_event_source; + + unsigned nd_sent; + + sd_ndisc_callback_t callback; + void *userdata; +}; + +#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__) +#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/include/systemd-network/ndisc-router.h b/src/libsystemd-network/include/systemd-network/ndisc-router.h new file mode 100644 index 0000000000..2f1df49198 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/ndisc-router.h @@ -0,0 +1,62 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2014 Intel Corporation. All rights reserved. + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "systemd-basic/time-util.h" + +#include "sd-ndisc.h" + +struct sd_ndisc_router { + unsigned n_ref; + + triple_timestamp timestamp; + struct in6_addr address; + + /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */ + size_t raw_size; + + /* The current read index for the iterative option interface */ + size_t rindex; + + uint64_t flags; + unsigned preference; + uint16_t lifetime; + + uint8_t hop_limit; + uint32_t mtu; +}; + +static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) { + return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router)); +} + +static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) { + return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex; +} + +static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) { + return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0]; +} +static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) { + return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8; +} + +sd_ndisc_router *ndisc_router_new(size_t raw_size); +int ndisc_router_parse(sd_ndisc_router *rt); diff --git a/src/libsystemd-network/include/systemd-network/network-internal.h b/src/libsystemd-network/include/systemd-network/network-internal.h new file mode 100644 index 0000000000..3d0554f12c --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/network-internal.h @@ -0,0 +1,81 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright (C) 2013 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 . +***/ + +#include + +#include "systemd-shared/condition.h" +#include "udev.h" + +#include "sd-dhcp-lease.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_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 config_parse_iaid(const char *unit, const char *filename, unsigned line, + const char *section, unsigned section_line, const char *lvalue, + int ltype, const char *rvalue, void *data, void *userdata); + +int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result); +const char *net_get_name(struct udev_device *device); + +void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size); +int deserialize_in_addrs(struct in_addr **addresses, const char *string); +void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, + size_t size); +int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); + +/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ +struct sd_dhcp_route; + +void serialize_dhcp_routes(FILE *f, const char *key, 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/include/systemd-network/sd-dhcp-client.h b/src/libsystemd-network/include/systemd-network/sd-dhcp-client.h new file mode 100644 index 0000000000..ce1145a575 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp-client.h @@ -0,0 +1,157 @@ +#ifndef foosddhcpclienthfoo +#define foosddhcpclienthfoo + +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "_sd-common.h" +#include "sd-dhcp-lease.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP_CLIENT_EVENT_STOP = 0, + SD_DHCP_CLIENT_EVENT_IP_ACQUIRE = 1, + SD_DHCP_CLIENT_EVENT_IP_CHANGE = 2, + SD_DHCP_CLIENT_EVENT_EXPIRED = 3, + SD_DHCP_CLIENT_EVENT_RENEW = 4, +}; + +enum { + SD_DHCP_OPTION_PAD = 0, + SD_DHCP_OPTION_SUBNET_MASK = 1, + SD_DHCP_OPTION_TIME_OFFSET = 2, + SD_DHCP_OPTION_ROUTER = 3, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER = 6, + SD_DHCP_OPTION_HOST_NAME = 12, + SD_DHCP_OPTION_BOOT_FILE_SIZE = 13, + SD_DHCP_OPTION_DOMAIN_NAME = 15, + SD_DHCP_OPTION_ROOT_PATH = 17, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING = 19, + SD_DHCP_OPTION_ENABLE_IP_FORWARDING_NL = 20, + SD_DHCP_OPTION_POLICY_FILTER = 21, + SD_DHCP_OPTION_INTERFACE_MDR = 22, + SD_DHCP_OPTION_INTERFACE_TTL = 23, + SD_DHCP_OPTION_INTERFACE_MTU_AGING_TIMEOUT = 24, + SD_DHCP_OPTION_INTERFACE_MTU = 26, + SD_DHCP_OPTION_BROADCAST = 28, + SD_DHCP_OPTION_STATIC_ROUTE = 33, + SD_DHCP_OPTION_NTP_SERVER = 42, + SD_DHCP_OPTION_VENDOR_SPECIFIC = 43, + SD_DHCP_OPTION_REQUESTED_IP_ADDRESS = 50, + SD_DHCP_OPTION_IP_ADDRESS_LEASE_TIME = 51, + SD_DHCP_OPTION_OVERLOAD = 52, + SD_DHCP_OPTION_MESSAGE_TYPE = 53, + SD_DHCP_OPTION_SERVER_IDENTIFIER = 54, + SD_DHCP_OPTION_PARAMETER_REQUEST_LIST = 55, + SD_DHCP_OPTION_ERROR_MESSAGE = 56, + SD_DHCP_OPTION_MAXIMUM_MESSAGE_SIZE = 57, + SD_DHCP_OPTION_RENEWAL_T1_TIME = 58, + SD_DHCP_OPTION_REBINDING_T2_TIME = 59, + SD_DHCP_OPTION_VENDOR_CLASS_IDENTIFIER = 60, + SD_DHCP_OPTION_CLIENT_IDENTIFIER = 61, + SD_DHCP_OPTION_FQDN = 81, + SD_DHCP_OPTION_NEW_POSIX_TIMEZONE = 100, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE = 101, + SD_DHCP_OPTION_CLASSLESS_STATIC_ROUTE = 121, + SD_DHCP_OPTION_PRIVATE_BASE = 224, + SD_DHCP_OPTION_PRIVATE_LAST = 254, + SD_DHCP_OPTION_END = 255, +}; + +typedef struct sd_dhcp_client sd_dhcp_client; +typedef void (*sd_dhcp_client_callback_t)(sd_dhcp_client *client, int event, void *userdata); +int sd_dhcp_client_set_callback( + sd_dhcp_client *client, + sd_dhcp_client_callback_t cb, + void *userdata); + +int sd_dhcp_client_set_request_option( + sd_dhcp_client *client, + uint8_t option); +int sd_dhcp_client_set_request_address( + sd_dhcp_client *client, + const struct in_addr *last_address); +int sd_dhcp_client_set_request_broadcast( + sd_dhcp_client *client, + int broadcast); +int sd_dhcp_client_set_ifindex( + sd_dhcp_client *client, + int interface_index); +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp_client_set_client_id( + sd_dhcp_client *client, + uint8_t type, + const uint8_t *data, + size_t data_len); +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp_client_get_client_id( + sd_dhcp_client *client, + uint8_t *type, + const uint8_t **data, + size_t *data_len); +int sd_dhcp_client_set_mtu( + sd_dhcp_client *client, + uint32_t mtu); +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname); +int sd_dhcp_client_set_vendor_class_identifier( + sd_dhcp_client *client, + const char *vci); +int sd_dhcp_client_get_lease( + sd_dhcp_client *client, + sd_dhcp_lease **ret); + +int sd_dhcp_client_stop(sd_dhcp_client *client); +int sd_dhcp_client_start(sd_dhcp_client *client); + +sd_dhcp_client *sd_dhcp_client_ref(sd_dhcp_client *client); +sd_dhcp_client *sd_dhcp_client_unref(sd_dhcp_client *client); + +int sd_dhcp_client_new(sd_dhcp_client **ret); + +int sd_dhcp_client_attach_event( + sd_dhcp_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp_client_detach_event(sd_dhcp_client *client); +sd_event *sd_dhcp_client_get_event(sd_dhcp_client *client); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_client, sd_dhcp_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h b/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h new file mode 100644 index 0000000000..2f565ca825 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp-lease.h @@ -0,0 +1,67 @@ +#ifndef foosddhcpleasehfoo +#define foosddhcpleasehfoo + +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_lease sd_dhcp_lease; +typedef struct sd_dhcp_route sd_dhcp_route; + +sd_dhcp_lease *sd_dhcp_lease_ref(sd_dhcp_lease *lease); +sd_dhcp_lease *sd_dhcp_lease_unref(sd_dhcp_lease *lease); + +int sd_dhcp_lease_get_address(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_lifetime(sd_dhcp_lease *lease, uint32_t *lifetime); +int sd_dhcp_lease_get_t1(sd_dhcp_lease *lease, uint32_t *t1); +int sd_dhcp_lease_get_t2(sd_dhcp_lease *lease, uint32_t *t2); +int sd_dhcp_lease_get_broadcast(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_netmask(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_router(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_next_server(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_server_identifier(sd_dhcp_lease *lease, struct in_addr *addr); +int sd_dhcp_lease_get_dns(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_ntp(sd_dhcp_lease *lease, const struct in_addr **addr); +int sd_dhcp_lease_get_mtu(sd_dhcp_lease *lease, uint16_t *mtu); +int sd_dhcp_lease_get_domainname(sd_dhcp_lease *lease, const char **domainname); +int sd_dhcp_lease_get_hostname(sd_dhcp_lease *lease, const char **hostname); +int sd_dhcp_lease_get_root_path(sd_dhcp_lease *lease, const char **root_path); +int sd_dhcp_lease_get_routes(sd_dhcp_lease *lease, sd_dhcp_route ***routes); +int sd_dhcp_lease_get_vendor_specific(sd_dhcp_lease *lease, const void **data, size_t *data_len); +int sd_dhcp_lease_get_client_id(sd_dhcp_lease *lease, const void **client_id, size_t *client_id_len); +int sd_dhcp_lease_get_timezone(sd_dhcp_lease *lease, const char **timezone); + +int sd_dhcp_route_get_destination(sd_dhcp_route *route, struct in_addr *destination); +int sd_dhcp_route_get_destination_prefix_length(sd_dhcp_route *route, uint8_t *length); +int sd_dhcp_route_get_gateway(sd_dhcp_route *route, struct in_addr *gateway); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_lease, sd_dhcp_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h b/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h new file mode 100644 index 0000000000..bbb2bb203c --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp-server.h @@ -0,0 +1,65 @@ +#ifndef foosddhcpserverhfoo +#define foosddhcpserverhfoo + +/*** + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp_server sd_dhcp_server; + +int sd_dhcp_server_new(sd_dhcp_server **ret, int ifindex); + +sd_dhcp_server *sd_dhcp_server_ref(sd_dhcp_server *server); +sd_dhcp_server *sd_dhcp_server_unref(sd_dhcp_server *server); + +int sd_dhcp_server_attach_event(sd_dhcp_server *client, sd_event *event, int64_t priority); +int sd_dhcp_server_detach_event(sd_dhcp_server *client); +sd_event *sd_dhcp_server_get_event(sd_dhcp_server *client); + +int sd_dhcp_server_is_running(sd_dhcp_server *server); + +int sd_dhcp_server_start(sd_dhcp_server *server); +int sd_dhcp_server_stop(sd_dhcp_server *server); + +int sd_dhcp_server_configure_pool(sd_dhcp_server *server, struct in_addr *address, unsigned char prefixlen, uint32_t offset, uint32_t size); + +int sd_dhcp_server_set_timezone(sd_dhcp_server *server, const char *timezone); +int sd_dhcp_server_set_dns(sd_dhcp_server *server, const struct in_addr ntp[], unsigned n); +int sd_dhcp_server_set_ntp(sd_dhcp_server *server, const struct in_addr dns[], unsigned n); +int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled); + +int sd_dhcp_server_set_max_lease_time(sd_dhcp_server *server, uint32_t t); +int sd_dhcp_server_set_default_lease_time(sd_dhcp_server *server, uint32_t t); + +int sd_dhcp_server_forcerenew(sd_dhcp_server *server); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp_server, sd_dhcp_server_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h b/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h new file mode 100644 index 0000000000..cfd5f35135 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp6-client.h @@ -0,0 +1,134 @@ +#ifndef foosddhcp6clienthfoo +#define foosddhcp6clienthfoo + +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "_sd-common.h" +#include "sd-dhcp6-lease.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_DHCP6_CLIENT_EVENT_STOP = 0, + SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE = 10, + SD_DHCP6_CLIENT_EVENT_RETRANS_MAX = 11, + SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE = 12, + SD_DHCP6_CLIENT_EVENT_INFORMATION_REQUEST = 13, +}; + +enum { + SD_DHCP6_OPTION_CLIENTID = 1, + SD_DHCP6_OPTION_SERVERID = 2, + SD_DHCP6_OPTION_IA_NA = 3, + SD_DHCP6_OPTION_IA_TA = 4, + SD_DHCP6_OPTION_IAADDR = 5, + SD_DHCP6_OPTION_ORO = 6, + SD_DHCP6_OPTION_PREFERENCE = 7, + SD_DHCP6_OPTION_ELAPSED_TIME = 8, + SD_DHCP6_OPTION_RELAY_MSG = 9, + /* option code 10 is unassigned */ + SD_DHCP6_OPTION_AUTH = 11, + SD_DHCP6_OPTION_UNICAST = 12, + SD_DHCP6_OPTION_STATUS_CODE = 13, + SD_DHCP6_OPTION_RAPID_COMMIT = 14, + SD_DHCP6_OPTION_USER_CLASS = 15, + SD_DHCP6_OPTION_VENDOR_CLASS = 16, + SD_DHCP6_OPTION_VENDOR_OPTS = 17, + SD_DHCP6_OPTION_INTERFACE_ID = 18, + SD_DHCP6_OPTION_RECONF_MSG = 19, + SD_DHCP6_OPTION_RECONF_ACCEPT = 20, + + SD_DHCP6_OPTION_DNS_SERVERS = 23, /* RFC 3646 */ + SD_DHCP6_OPTION_DOMAIN_LIST = 24, /* RFC 3646 */ + + SD_DHCP6_OPTION_SNTP_SERVERS = 31, /* RFC 4075, deprecated */ + + /* option code 35 is unassigned */ + + SD_DHCP6_OPTION_NTP_SERVER = 56, /* RFC 5908 */ + + /* option codes 89-142 are unassigned */ + /* option codes 144-65535 are unassigned */ +}; + +typedef struct sd_dhcp6_client sd_dhcp6_client; +typedef void (*sd_dhcp6_client_callback_t)(sd_dhcp6_client *client, int event, void *userdata); +int sd_dhcp6_client_set_callback( + sd_dhcp6_client *client, + sd_dhcp6_client_callback_t cb, + void *userdata); + +int sd_dhcp6_client_set_ifindex( + sd_dhcp6_client *client, + int interface_index); +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address); +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type); +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len); +int sd_dhcp6_client_set_iaid( + sd_dhcp6_client *client, + uint32_t iaid); +int sd_dhcp6_client_set_information_request( + sd_dhcp6_client *client, + int enabled); +int sd_dhcp6_client_get_information_request( + sd_dhcp6_client *client, + int *enabled); +int sd_dhcp6_client_set_request_option( + sd_dhcp6_client *client, + uint16_t option); + +int sd_dhcp6_client_get_lease( + sd_dhcp6_client *client, + sd_dhcp6_lease **ret); + +int sd_dhcp6_client_stop(sd_dhcp6_client *client); +int sd_dhcp6_client_start(sd_dhcp6_client *client); +int sd_dhcp6_client_is_running(sd_dhcp6_client *client); +int sd_dhcp6_client_attach_event( + sd_dhcp6_client *client, + sd_event *event, + int64_t priority); +int sd_dhcp6_client_detach_event(sd_dhcp6_client *client); +sd_event *sd_dhcp6_client_get_event(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_ref(sd_dhcp6_client *client); +sd_dhcp6_client *sd_dhcp6_client_unref(sd_dhcp6_client *client); +int sd_dhcp6_client_new(sd_dhcp6_client **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_client, sd_dhcp6_client_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h b/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h new file mode 100644 index 0000000000..184fbb8e0d --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-dhcp6-lease.h @@ -0,0 +1,52 @@ +#ifndef foosddhcp6leasehfoo +#define foosddhcp6leasehfoo + +/*** + 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 . +***/ + +#include +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +typedef struct sd_dhcp6_lease sd_dhcp6_lease; + +void sd_dhcp6_lease_reset_address_iter(sd_dhcp6_lease *lease); +int sd_dhcp6_lease_get_address(sd_dhcp6_lease *lease, + struct in6_addr *addr, + uint32_t *lifetime_preferred, + uint32_t *lifetime_valid); + +int sd_dhcp6_lease_get_dns(sd_dhcp6_lease *lease, struct in6_addr **addrs); +int sd_dhcp6_lease_get_domains(sd_dhcp6_lease *lease, char ***domains); +int sd_dhcp6_lease_get_ntp_addrs(sd_dhcp6_lease *lease, + struct in6_addr **addrs); +int sd_dhcp6_lease_get_ntp_fqdn(sd_dhcp6_lease *lease, char ***ntp_fqdn); + +sd_dhcp6_lease *sd_dhcp6_lease_ref(sd_dhcp6_lease *lease); +sd_dhcp6_lease *sd_dhcp6_lease_unref(sd_dhcp6_lease *lease); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_dhcp6_lease, sd_dhcp6_lease_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h b/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h new file mode 100644 index 0000000000..e5ccb4b971 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-ipv4acd.h @@ -0,0 +1,60 @@ +#ifndef foosdipv4acdfoo +#define foosdipv4acdfoo + +/*** + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4ACD_EVENT_STOP = 0, + SD_IPV4ACD_EVENT_BIND = 1, + SD_IPV4ACD_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4acd sd_ipv4acd; +typedef void (*sd_ipv4acd_callback_t)(sd_ipv4acd *acd, int event, void *userdata); + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd); +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority); +int sd_ipv4acd_get_address(sd_ipv4acd *acd, struct in_addr *address); +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata); +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr); +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int interface_index); +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address); +int sd_ipv4acd_is_running(sd_ipv4acd *acd); +int sd_ipv4acd_start(sd_ipv4acd *acd); +int sd_ipv4acd_stop(sd_ipv4acd *acd); +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd); +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd); +int sd_ipv4acd_new(sd_ipv4acd **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4acd, sd_ipv4acd_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h b/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h new file mode 100644 index 0000000000..cff1865d05 --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-ipv4ll.h @@ -0,0 +1,60 @@ +#ifndef foosdipv4llfoo +#define foosdipv4llfoo + +/*** + 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 . +***/ + +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +enum { + SD_IPV4LL_EVENT_STOP = 0, + SD_IPV4LL_EVENT_BIND = 1, + SD_IPV4LL_EVENT_CONFLICT = 2, +}; + +typedef struct sd_ipv4ll sd_ipv4ll; +typedef void (*sd_ipv4ll_callback_t)(sd_ipv4ll *ll, int event, void *userdata); + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll); +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority); +int sd_ipv4ll_get_address(sd_ipv4ll *ll, struct in_addr *address); +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata); +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr); +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int interface_index); +int sd_ipv4ll_set_address(sd_ipv4ll *ll, const struct in_addr *address); +int sd_ipv4ll_set_address_seed(sd_ipv4ll *ll, uint64_t seed); +int sd_ipv4ll_is_running(sd_ipv4ll *ll); +int sd_ipv4ll_start(sd_ipv4ll *ll); +int sd_ipv4ll_stop(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_ref(sd_ipv4ll *ll); +sd_ipv4ll *sd_ipv4ll_unref(sd_ipv4ll *ll); +int sd_ipv4ll_new(sd_ipv4ll **ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ipv4ll, sd_ipv4ll_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-lldp.h b/src/libsystemd-network/include/systemd-network/sd-lldp.h new file mode 100644 index 0000000000..928f77f0ab --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-lldp.h @@ -0,0 +1,182 @@ +#ifndef foosdlldphfoo +#define foosdlldphfoo + +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* IEEE 802.3AB Clause 9: TLV Types */ +enum { + SD_LLDP_TYPE_END = 0, + SD_LLDP_TYPE_CHASSIS_ID = 1, + SD_LLDP_TYPE_PORT_ID = 2, + SD_LLDP_TYPE_TTL = 3, + SD_LLDP_TYPE_PORT_DESCRIPTION = 4, + SD_LLDP_TYPE_SYSTEM_NAME = 5, + SD_LLDP_TYPE_SYSTEM_DESCRIPTION = 6, + SD_LLDP_TYPE_SYSTEM_CAPABILITIES = 7, + SD_LLDP_TYPE_MGMT_ADDRESS = 8, + SD_LLDP_TYPE_PRIVATE = 127, +}; + +/* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */ +enum { + SD_LLDP_CHASSIS_SUBTYPE_RESERVED = 0, + SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2, + SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3, + SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4, + SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5, + SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6, + SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +/* IEEE 802.3AB Clause 9.5.3: Port subtype */ +enum { + SD_LLDP_PORT_SUBTYPE_RESERVED = 0, + SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1, + SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2, + SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3, + SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS = 4, + SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5, + SD_LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6, + SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7, +}; + +enum { + SD_LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0, + SD_LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1, + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2, + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3, + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4, + SD_LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5, + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6, + SD_LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7, + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8, + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9, + SD_LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10, +}; + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL ((uint16_t) -1) + +#define SD_LLDP_SYSTEM_CAPABILITIES_ALL_ROUTERS \ + ((uint16_t) \ + (SD_LLDP_SYSTEM_CAPABILITIES_REPEATER| \ + SD_LLDP_SYSTEM_CAPABILITIES_BRIDGE| \ + SD_LLDP_SYSTEM_CAPABILITIES_WLAN_AP| \ + SD_LLDP_SYSTEM_CAPABILITIES_ROUTER| \ + SD_LLDP_SYSTEM_CAPABILITIES_DOCSIS| \ + SD_LLDP_SYSTEM_CAPABILITIES_CVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_SVLAN| \ + SD_LLDP_SYSTEM_CAPABILITIES_TPMR)) + +#define SD_LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 } +#define SD_LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f } + +enum { + SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID = 1, + SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID = 2, + SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME = 3, + SD_LLDP_OUI_802_1_SUBTYPE_PROTOCOL_IDENTITY = 4, + SD_LLDP_OUI_802_1_SUBTYPE_VID_USAGE_DIGEST = 5, + SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID = 6, + SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION = 7, +}; + +typedef struct sd_lldp sd_lldp; +typedef struct sd_lldp_neighbor sd_lldp_neighbor; + +typedef enum sd_lldp_event { + SD_LLDP_EVENT_ADDED = 'a', + SD_LLDP_EVENT_REMOVED = 'r', + SD_LLDP_EVENT_UPDATED = 'u', + SD_LLDP_EVENT_REFRESHED = 'f', +} sd_lldp_event; + +typedef void (*sd_lldp_callback_t)(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata); + +int sd_lldp_new(sd_lldp **ret); +sd_lldp* sd_lldp_ref(sd_lldp *lldp); +sd_lldp* sd_lldp_unref(sd_lldp *lldp); + +int sd_lldp_start(sd_lldp *lldp); +int sd_lldp_stop(sd_lldp *lldp); + +int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority); +int sd_lldp_detach_event(sd_lldp *lldp); +sd_event *sd_lldp_get_event(sd_lldp *lldp); + +int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata); +int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex); + +/* Controls how much and what to store in the neighbors database */ +int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t n); +int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask); +int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *address); + +int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***neighbors); + +int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size); +sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n); +sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n); + +/* Access to LLDP frame metadata */ +int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address); +int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret); +int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +/* High-level, direct, parsed out field access. These fields exist at most once, hence may be queried directly. */ +int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size); +int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec); +int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret); +int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret); +int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret); + +/* Low-level, iterative TLV access. This is for evertyhing else, it iteratively goes through all available TLVs + * (including the ones covered with the calls above), and allows multiple TLVs for the same fields. */ +int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n); +int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type); +int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type); +int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype); +int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype); +int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp, sd_lldp_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_lldp_neighbor, sd_lldp_neighbor_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/include/systemd-network/sd-ndisc.h b/src/libsystemd-network/include/systemd-network/sd-ndisc.h new file mode 100644 index 0000000000..de2329458a --- /dev/null +++ b/src/libsystemd-network/include/systemd-network/sd-ndisc.h @@ -0,0 +1,130 @@ +#ifndef foosdndiscfoo +#define foosdndiscfoo + +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include + +#include "_sd-common.h" + +_SD_BEGIN_DECLARATIONS; + +/* Neightbor Discovery Options, RFC 4861, Section 4.6 and + * https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml#icmpv6-parameters-5 */ +enum { + SD_NDISC_OPTION_SOURCE_LL_ADDRESS = 1, + SD_NDISC_OPTION_TARGET_LL_ADDRESS = 2, + SD_NDISC_OPTION_PREFIX_INFORMATION = 3, + SD_NDISC_OPTION_MTU = 5, + SD_NDISC_OPTION_ROUTE_INFORMATION = 24, + SD_NDISC_OPTION_RDNSS = 25, + SD_NDISC_OPTION_FLAGS_EXTENSION = 26, + SD_NDISC_OPTION_DNSSL = 31, + SD_NDISC_OPTION_CAPTIVE_PORTAL = 37, +}; + +/* Route preference, RFC 4191, Section 2.1 */ +enum { + SD_NDISC_PREFERENCE_LOW = 3U, + SD_NDISC_PREFERENCE_MEDIUM = 0U, + SD_NDISC_PREFERENCE_HIGH = 1U, +}; + +typedef struct sd_ndisc sd_ndisc; +typedef struct sd_ndisc_router sd_ndisc_router; + +typedef enum sd_ndisc_event { + SD_NDISC_EVENT_TIMEOUT = 't', + SD_NDISC_EVENT_ROUTER = 'r', +} sd_ndisc_event; + +typedef void (*sd_ndisc_callback_t)(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata); + +int sd_ndisc_new(sd_ndisc **ret); +sd_ndisc *sd_ndisc_ref(sd_ndisc *nd); +sd_ndisc *sd_ndisc_unref(sd_ndisc *nd); + +int sd_ndisc_start(sd_ndisc *nd); +int sd_ndisc_stop(sd_ndisc *nd); + +int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority); +int sd_ndisc_detach_event(sd_ndisc *nd); +sd_event *sd_ndisc_get_event(sd_ndisc *nd); + +int sd_ndisc_set_callback(sd_ndisc *nd, sd_ndisc_callback_t cb, void *userdata); +int sd_ndisc_set_ifindex(sd_ndisc *nd, int interface_index); +int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr); + +int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *ret); +int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret); + +int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size); +sd_ndisc_router *sd_ndisc_router_ref(sd_ndisc_router *rt); +sd_ndisc_router *sd_ndisc_router_unref(sd_ndisc_router *rt); + +int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret); +int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags); +int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret); +int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime); +int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret); + +/* Generic option access */ +int sd_ndisc_router_option_rewind(sd_ndisc_router *rt); +int sd_ndisc_router_option_next(sd_ndisc_router *rt); +int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type); +int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size); + +/* Specific option access: SD_NDISC_OPTION_PREFIX_INFORMATION */ +int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret); +int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); + +/* Specific option access: SD_NDISC_OPTION_ROUTE_INFORMATION */ +int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); +int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr); +int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *prefixlen); +int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret); + +/* Specific option access: SD_NDISC_OPTION_RDNSS */ +int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret); +int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +/* Specific option access: SD_NDISC_OPTION_DNSSL */ +int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret); +int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret); + +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc, sd_ndisc_unref); +_SD_DEFINE_POINTER_CLEANUP_FUNC(sd_ndisc_router, sd_ndisc_router_unref); + +_SD_END_DECLARATIONS; + +#endif diff --git a/src/libsystemd-network/lldp-internal.h b/src/libsystemd-network/lldp-internal.h deleted file mode 100644 index becc162fab..0000000000 --- a/src/libsystemd-network/lldp-internal.h +++ /dev/null @@ -1,55 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-event.h" -#include "sd-lldp.h" - -#include "hashmap.h" -#include "log.h" -#include "prioq.h" - -struct sd_lldp { - unsigned n_ref; - - int ifindex; - int fd; - - sd_event *event; - int64_t event_priority; - sd_event_source *io_event_source; - sd_event_source *timer_event_source; - - Prioq *neighbor_by_expiry; - Hashmap *neighbor_by_id; - - uint64_t neighbors_max; - - sd_lldp_callback_t callback; - void *userdata; - - uint16_t capability_mask; - - struct ether_addr filter_address; -}; - -#define log_lldp_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__) -#define log_lldp(fmt, ...) log_lldp_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/lldp-neighbor.c b/src/libsystemd-network/lldp-neighbor.c deleted file mode 100644 index 53e29377b3..0000000000 --- a/src/libsystemd-network/lldp-neighbor.c +++ /dev/null @@ -1,813 +0,0 @@ -/*** - This file is part of systemd. - - Copyright 2016 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include "alloc-util.h" -#include "escape.h" -#include "ether-addr-util.h" -#include "hexdecoct.h" -#include "in-addr-util.h" -#include "lldp-internal.h" -#include "lldp-neighbor.h" -#include "unaligned.h" - -static void lldp_neighbor_id_hash_func(const void *p, struct siphash *state) { - const LLDPNeighborID *id = p; - - siphash24_compress(id->chassis_id, id->chassis_id_size, state); - siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state); - siphash24_compress(id->port_id, id->port_id_size, state); - siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state); -} - -static int lldp_neighbor_id_compare_func(const void *a, const void *b) { - const LLDPNeighborID *x = a, *y = b; - int r; - - r = memcmp(x->chassis_id, y->chassis_id, MIN(x->chassis_id_size, y->chassis_id_size)); - if (r != 0) - return r; - - if (x->chassis_id_size < y->chassis_id_size) - return -1; - - if (x->chassis_id_size > y->chassis_id_size) - return 1; - - r = memcmp(x->port_id, y->port_id, MIN(x->port_id_size, y->port_id_size)); - if (r != 0) - return r; - - if (x->port_id_size < y->port_id_size) - return -1; - if (x->port_id_size > y->port_id_size) - return 1; - - return 0; -} - -const struct hash_ops lldp_neighbor_id_hash_ops = { - .hash = lldp_neighbor_id_hash_func, - .compare = lldp_neighbor_id_compare_func -}; - -int lldp_neighbor_prioq_compare_func(const void *a, const void *b) { - const sd_lldp_neighbor *x = a, *y = b; - - if (x->until < y->until) - return -1; - - if (x->until > y->until) - return 1; - - return 0; -} - -_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) { - if (!n) - return NULL; - - assert(n->n_ref > 0 || n->lldp); - n->n_ref++; - - return n; -} - -static void lldp_neighbor_free(sd_lldp_neighbor *n) { - assert(n); - - free(n->id.port_id); - free(n->id.chassis_id); - free(n->port_description); - free(n->system_name); - free(n->system_description); - free(n->chassis_id_as_string); - free(n->port_id_as_string); - free(n); -} - -_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) { - - /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from - * the sd_lldp object. */ - - if (!n) - return NULL; - - assert(n->n_ref > 0); - n->n_ref--; - - if (n->n_ref <= 0 && !n->lldp) - lldp_neighbor_free(n); - - return NULL; -} - -sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) { - - /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */ - - if (!n) - return NULL; - - if (!n->lldp) - return NULL; - - assert_se(hashmap_remove(n->lldp->neighbor_by_id, &n->id) == n); - assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0); - - n->lldp = NULL; - - if (n->n_ref <= 0) - lldp_neighbor_free(n); - - return NULL; -} - -sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) { - sd_lldp_neighbor *n; - - n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size); - if (!n) - return NULL; - - n->raw_size = raw_size; - n->n_ref = 1; - - return n; -} - -static int parse_string(char **s, const void *q, size_t n) { - const char *p = q; - char *k; - - assert(s); - assert(p || n == 0); - - if (*s) { - log_lldp("Found duplicate string, ignoring field."); - return 0; - } - - /* Strip trailing NULs, just to be nice */ - while (n > 0 && p[n-1] == 0) - n--; - - if (n <= 0) /* Ignore empty strings */ - return 0; - - /* Look for inner NULs */ - if (memchr(p, 0, n)) { - log_lldp("Found inner NUL in string, ignoring field."); - return 0; - } - - /* Let's escape weird chars, for security reasons */ - k = cescape_length(p, n); - if (!k) - return -ENOMEM; - - free(*s); - *s = k; - - return 1; -} - -int lldp_neighbor_parse(sd_lldp_neighbor *n) { - struct ether_header h; - const uint8_t *p; - size_t left; - int r; - - assert(n); - - if (n->raw_size < sizeof(struct ether_header)) { - log_lldp("Received truncated packet, ignoring."); - return -EBADMSG; - } - - memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h)); - - if (h.ether_type != htobe16(ETHERTYPE_LLDP)) { - log_lldp("Received packet with wrong type, ignoring."); - return -EBADMSG; - } - - if (h.ether_dhost[0] != 0x01 || - h.ether_dhost[1] != 0x80 || - h.ether_dhost[2] != 0xc2 || - h.ether_dhost[3] != 0x00 || - h.ether_dhost[4] != 0x00 || - !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) { - log_lldp("Received packet with wrong destination address, ignoring."); - return -EBADMSG; - } - - memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr)); - memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr)); - - p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header); - left = n->raw_size - sizeof(struct ether_header); - - for (;;) { - uint8_t type; - uint16_t length; - - if (left < 2) { - log_lldp("TLV lacks header, ignoring."); - return -EBADMSG; - } - - type = p[0] >> 1; - length = p[1] + (((uint16_t) (p[0] & 1)) << 8); - p += 2, left -= 2; - - if (left < length) { - log_lldp("TLV truncated, ignoring datagram."); - return -EBADMSG; - } - - switch (type) { - - case SD_LLDP_TYPE_END: - if (length != 0) { - log_lldp("End marker TLV not zero-sized, ignoring datagram."); - return -EBADMSG; - } - if (left != 0) { - log_lldp("Trailing garbage in datagram, ignoring datagram."); - return -EBADMSG; - } - - goto end_marker; - - case SD_LLDP_TYPE_CHASSIS_ID: - if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */ - log_lldp("Chassis ID field size out of range, ignoring datagram."); - return -EBADMSG; - } - if (n->id.chassis_id) { - log_lldp("Duplicate chassis ID field, ignoring datagram."); - return -EBADMSG; - } - - n->id.chassis_id = memdup(p, length); - if (!n->id.chassis_id) - return -ENOMEM; - - n->id.chassis_id_size = length; - break; - - case SD_LLDP_TYPE_PORT_ID: - if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */ - log_lldp("Port ID field size out of range, ignoring datagram."); - return -EBADMSG; - } - if (n->id.port_id) { - log_lldp("Duplicate port ID field, ignoring datagram."); - return -EBADMSG; - } - - n->id.port_id = memdup(p, length); - if (!n->id.port_id) - return -ENOMEM; - - n->id.port_id_size = length; - break; - - case SD_LLDP_TYPE_TTL: - if (length != 2) { - log_lldp("TTL field has wrong size, ignoring datagram."); - return -EBADMSG; - } - - if (n->has_ttl) { - log_lldp("Duplicate TTL field, ignoring datagram."); - return -EBADMSG; - } - - n->ttl = unaligned_read_be16(p); - n->has_ttl = true; - break; - - case SD_LLDP_TYPE_PORT_DESCRIPTION: - r = parse_string(&n->port_description, p, length); - if (r < 0) - return r; - break; - - case SD_LLDP_TYPE_SYSTEM_NAME: - r = parse_string(&n->system_name, p, length); - if (r < 0) - return r; - break; - - case SD_LLDP_TYPE_SYSTEM_DESCRIPTION: - r = parse_string(&n->system_description, p, length); - if (r < 0) - return r; - break; - - case SD_LLDP_TYPE_SYSTEM_CAPABILITIES: - if (length != 4) - log_lldp("System capabilities field has wrong size, ignoring."); - else { - n->system_capabilities = unaligned_read_be16(p); - n->enabled_capabilities = unaligned_read_be16(p + 2); - n->has_capabilities = true; - } - - break; - - case SD_LLDP_TYPE_PRIVATE: - if (length < 4) - log_lldp("Found private TLV that is too short, ignoring."); - - break; - } - - - p += length, left -= length; - } - -end_marker: - if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) { - log_lldp("One or more mandatory TLV missing in datagram. Ignoring."); - return -EBADMSG; - - } - - n->rindex = sizeof(struct ether_header); - - return 0; -} - -void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) { - assert(n); - - if (n->ttl > 0) { - usec_t base; - - /* Use the packet's timestamp if there is one known */ - base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic()); - if (base <= 0 || base == USEC_INFINITY) - base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */ - - n->until = usec_add(base, n->ttl * USEC_PER_SEC); - } else - n->until = 0; - - if (n->lldp) - prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx); -} - -bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) { - if (a == b) - return true; - - if (!a || !b) - return false; - - if (a->raw_size != b->raw_size) - return false; - - return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0; -} - -_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) { - assert_return(n, -EINVAL); - assert_return(address, -EINVAL); - - *address = n->source_address; - return 0; -} - -_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) { - assert_return(n, -EINVAL); - assert_return(address, -EINVAL); - - *address = n->destination_address; - return 0; -} - -_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - *ret = LLDP_NEIGHBOR_RAW(n); - *size = n->raw_size; - - return 0; -} - -_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { - assert_return(n, -EINVAL); - assert_return(type, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - assert(n->id.chassis_id_size > 0); - - *type = *(uint8_t*) n->id.chassis_id; - *ret = (uint8_t*) n->id.chassis_id + 1; - *size = n->id.chassis_id_size - 1; - - return 0; -} - -static int format_mac_address(const void *data, size_t sz, char **ret) { - struct ether_addr a; - char *k; - - assert(data || sz <= 0); - - if (sz != 7) - return 0; - - memcpy(&a, (uint8_t*) data + 1, sizeof(a)); - - k = new(char, ETHER_ADDR_TO_STRING_MAX); - if (!k) - return -ENOMEM; - - *ret = ether_addr_to_string(&a, k); - return 1; -} - -static int format_network_address(const void *data, size_t sz, char **ret) { - union in_addr_union a; - int family, r; - - if (sz == 6 && ((uint8_t*) data)[1] == 1) { - memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in)); - family = AF_INET; - } else if (sz == 18 && ((uint8_t*) data)[1] == 2) { - memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6)); - family = AF_INET6; - } else - return 0; - - r = in_addr_to_string(family, &a, ret); - if (r < 0) - return r; - return 1; -} - -_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) { - char *k; - int r; - - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (n->chassis_id_as_string) { - *ret = n->chassis_id_as_string; - return 0; - } - - assert(n->id.chassis_id_size > 0); - - switch (*(uint8_t*) n->id.chassis_id) { - - case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT: - case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS: - case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT: - case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME: - case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED: - k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1); - if (!k) - return -ENOMEM; - - goto done; - - case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: - r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k); - if (r < 0) - return r; - if (r > 0) - goto done; - - break; - - case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: - r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k); - if (r < 0) - return r; - if (r > 0) - goto done; - - break; - } - - /* Generic fallback */ - k = hexmem(n->id.chassis_id, n->id.chassis_id_size); - if (!k) - return -ENOMEM; - -done: - *ret = n->chassis_id_as_string = k; - return 0; -} - -_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { - assert_return(n, -EINVAL); - assert_return(type, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - assert(n->id.port_id_size > 0); - - *type = *(uint8_t*) n->id.port_id; - *ret = (uint8_t*) n->id.port_id + 1; - *size = n->id.port_id_size - 1; - - return 0; -} - -_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) { - char *k; - int r; - - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (n->port_id_as_string) { - *ret = n->port_id_as_string; - return 0; - } - - assert(n->id.port_id_size > 0); - - switch (*(uint8_t*) n->id.port_id) { - - case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: - case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT: - case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME: - case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: - k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1); - if (!k) - return -ENOMEM; - - goto done; - - case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS: - r = format_mac_address(n->id.port_id, n->id.port_id_size, &k); - if (r < 0) - return r; - if (r > 0) - goto done; - - break; - - case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS: - r = format_network_address(n->id.port_id, n->id.port_id_size, &k); - if (r < 0) - return r; - if (r > 0) - goto done; - - break; - } - - /* Generic fallback */ - k = hexmem(n->id.port_id, n->id.port_id_size); - if (!k) - return -ENOMEM; - -done: - *ret = n->port_id_as_string = k; - return 0; -} - -_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) { - assert_return(n, -EINVAL); - assert_return(ret_sec, -EINVAL); - - *ret_sec = n->ttl; - return 0; -} - -_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (!n->system_name) - return -ENODATA; - - *ret = n->system_name; - return 0; -} - -_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (!n->system_description) - return -ENODATA; - - *ret = n->system_description; - return 0; -} - -_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (!n->port_description) - return -ENODATA; - - *ret = n->port_description; - return 0; -} - -_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (!n->has_capabilities) - return -ENODATA; - - *ret = n->system_capabilities; - return 0; -} - -_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - - if (!n->has_capabilities) - return -ENODATA; - - *ret = n->enabled_capabilities; - return 0; -} - -_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(raw || raw_size <= 0, -EINVAL); - - n = lldp_neighbor_new(raw_size); - if (!n) - return -ENOMEM; - - memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size); - r = lldp_neighbor_parse(n); - if (r < 0) - return r; - - *ret = n; - n = NULL; - - return r; -} - -_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { - assert_return(n, -EINVAL); - - assert(n->raw_size >= sizeof(struct ether_header)); - n->rindex = sizeof(struct ether_header); - - return n->rindex < n->raw_size; -} - -_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { - size_t length; - - assert_return(n, -EINVAL); - - if (n->rindex == n->raw_size) /* EOF */ - return -ESPIPE; - - if (n->rindex + 2 > n->raw_size) /* Truncated message */ - return -EBADMSG; - - length = LLDP_NEIGHBOR_TLV_LENGTH(n); - if (n->rindex + 2 + length > n->raw_size) - return -EBADMSG; - - n->rindex += 2 + length; - return n->rindex < n->raw_size; -} - -_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) { - assert_return(n, -EINVAL); - assert_return(type, -EINVAL); - - if (n->rindex == n->raw_size) /* EOF */ - return -ESPIPE; - - if (n->rindex + 2 > n->raw_size) - return -EBADMSG; - - *type = LLDP_NEIGHBOR_TLV_TYPE(n); - return 0; -} - -_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) { - uint8_t k; - int r; - - assert_return(n, -EINVAL); - - r = sd_lldp_neighbor_tlv_get_type(n, &k); - if (r < 0) - return r; - - return type == k; -} - -_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype) { - const uint8_t *d; - size_t length; - int r; - - assert_return(n, -EINVAL); - assert_return(oui, -EINVAL); - assert_return(subtype, -EINVAL); - - r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE); - if (r < 0) - return r; - if (r == 0) - return -ENXIO; - - length = LLDP_NEIGHBOR_TLV_LENGTH(n); - if (length < 4) - return -EBADMSG; - - if (n->rindex + 2 + length > n->raw_size) - return -EBADMSG; - - d = LLDP_NEIGHBOR_TLV_DATA(n); - memcpy(oui, d, 3); - *subtype = d[3]; - - return 0; -} - -_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype) { - uint8_t k[3], st; - int r; - - r = sd_lldp_neighbor_tlv_get_oui(n, k, &st); - if (r == -ENXIO) - return 0; - if (r < 0) - return r; - - return memcmp(k, oui, 3) == 0 && st == subtype; -} - -_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { - size_t length; - - assert_return(n, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - /* Note that this returns the full TLV, including the TLV header */ - - if (n->rindex + 2 > n->raw_size) - return -EBADMSG; - - length = LLDP_NEIGHBOR_TLV_LENGTH(n); - if (n->rindex + 2 + length > n->raw_size) - return -EBADMSG; - - *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; - *size = length + 2; - - return 0; -} - -_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) { - assert_return(n, -EINVAL); - assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); - assert_return(clock_supported(clock), -EOPNOTSUPP); - assert_return(ret, -EINVAL); - - if (!triple_timestamp_is_set(&n->timestamp)) - return -ENODATA; - - *ret = triple_timestamp_by_clock(&n->timestamp, clock); - return 0; -} diff --git a/src/libsystemd-network/lldp-neighbor.h b/src/libsystemd-network/lldp-neighbor.h deleted file mode 100644 index c1a7606d06..0000000000 --- a/src/libsystemd-network/lldp-neighbor.h +++ /dev/null @@ -1,108 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright 2016 Lennart Poettering - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -#include "sd-lldp.h" - -#include "hash-funcs.h" -#include "lldp-internal.h" -#include "time-util.h" - -typedef struct LLDPNeighborID { - /* The spec calls this an "MSAP identifier" */ - void *chassis_id; - size_t chassis_id_size; - - void *port_id; - size_t port_id_size; -} LLDPNeighborID; - -struct sd_lldp_neighbor { - /* Neighbor objects stay around as long as they are linked into an "sd_lldp" object or n_ref > 0. */ - sd_lldp *lldp; - unsigned n_ref; - - triple_timestamp timestamp; - - usec_t until; - unsigned prioq_idx; - - struct ether_addr source_address; - struct ether_addr destination_address; - - LLDPNeighborID id; - - /* The raw packet size. The data is appended to the object, accessible via LLDP_NEIGHBOR_RAW() */ - size_t raw_size; - - /* The current read index for the iterative TLV interface */ - size_t rindex; - - /* And a couple of fields parsed out. */ - bool has_ttl:1; - bool has_capabilities:1; - bool has_port_vlan_id:1; - - uint16_t ttl; - - uint16_t system_capabilities; - uint16_t enabled_capabilities; - - char *port_description; - char *system_name; - char *system_description; - - uint16_t port_vlan_id; - - char *chassis_id_as_string; - char *port_id_as_string; -}; - -static inline void *LLDP_NEIGHBOR_RAW(const sd_lldp_neighbor *n) { - return (uint8_t*) n + ALIGN(sizeof(sd_lldp_neighbor)); -} - -static inline uint8_t LLDP_NEIGHBOR_TLV_TYPE(const sd_lldp_neighbor *n) { - return ((uint8_t*) LLDP_NEIGHBOR_RAW(n))[n->rindex] >> 1; -} - -static inline size_t LLDP_NEIGHBOR_TLV_LENGTH(const sd_lldp_neighbor *n) { - uint8_t *p; - - p = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; - return p[1] + (((size_t) (p[0] & 1)) << 8); -} - -static inline void* LLDP_NEIGHBOR_TLV_DATA(const sd_lldp_neighbor *n) { - return ((uint8_t*) LLDP_NEIGHBOR_RAW(n)) + n->rindex + 2; -} - -extern const struct hash_ops lldp_neighbor_id_hash_ops; -int lldp_neighbor_prioq_compare_func(const void *a, const void *b); - -sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n); -sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size); -int lldp_neighbor_parse(sd_lldp_neighbor *n); -void lldp_neighbor_start_ttl(sd_lldp_neighbor *n); -bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b); diff --git a/src/libsystemd-network/lldp-network.c b/src/libsystemd-network/lldp-network.c deleted file mode 100644 index 59c25598e9..0000000000 --- a/src/libsystemd-network/lldp-network.c +++ /dev/null @@ -1,77 +0,0 @@ -/*** - 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 . -***/ - -#include -#include - -#include "fd-util.h" -#include "lldp-network.h" -#include "socket-util.h" - -int lldp_network_bind_raw_socket(int ifindex) { - - static const struct sock_filter filter[] = { - BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */ - BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */ - BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ - BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */ - }; - - static const struct sock_fprog fprog = { - .len = ELEMENTSOF(filter), - .filter = (struct sock_filter*) filter, - }; - - union sockaddr_union saddrll = { - .ll.sll_family = AF_PACKET, - .ll.sll_ifindex = ifindex, - }; - - _cleanup_close_ int fd = -1; - int r; - - assert(ifindex > 0); - - fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, - htobe16(ETHERTYPE_LLDP)); - if (fd < 0) - return -errno; - - r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); - if (r < 0) - return -errno; - - r = bind(fd, &saddrll.sa, sizeof(saddrll.ll)); - if (r < 0) - return -errno; - - r = fd; - fd = -1; - - return r; -} diff --git a/src/libsystemd-network/lldp-network.h b/src/libsystemd-network/lldp-network.h deleted file mode 100644 index c4cf8c79f1..0000000000 --- a/src/libsystemd-network/lldp-network.h +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -/*** - 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 . -***/ - -#include "sd-event.h" - -int lldp_network_bind_raw_socket(int ifindex); diff --git a/src/libsystemd-network/ndisc-internal.h b/src/libsystemd-network/ndisc-internal.h deleted file mode 100644 index 60e183ff8c..0000000000 --- a/src/libsystemd-network/ndisc-internal.h +++ /dev/null @@ -1,49 +0,0 @@ -#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 . -***/ - -#include "log.h" - -#include "sd-ndisc.h" - -struct sd_ndisc { - unsigned n_ref; - - int ifindex; - int fd; - - sd_event *event; - int event_priority; - - struct ether_addr mac_addr; - uint8_t hop_limit; - uint32_t mtu; - - sd_event_source *recv_event_source; - sd_event_source *timeout_event_source; - - unsigned nd_sent; - - sd_ndisc_callback_t callback; - void *userdata; -}; - -#define log_ndisc_errno(error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "NDISC: " fmt, ##__VA_ARGS__) -#define log_ndisc(fmt, ...) log_ndisc_errno(0, fmt, ##__VA_ARGS__) diff --git a/src/libsystemd-network/ndisc-router.c b/src/libsystemd-network/ndisc-router.c deleted file mode 100644 index 41ff2b353a..0000000000 --- a/src/libsystemd-network/ndisc-router.c +++ /dev/null @@ -1,778 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include - -#include "sd-ndisc.h" - -#include "alloc-util.h" -#include "dns-domain.h" -#include "hostname-util.h" -#include "missing.h" -#include "ndisc-internal.h" -#include "ndisc-router.h" -#include "strv.h" - -_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) { - if (!rt) - return NULL; - - assert(rt->n_ref > 0); - rt->n_ref++; - - return rt; -} - -_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) { - if (!rt) - return NULL; - - assert(rt->n_ref > 0); - rt->n_ref--; - - if (rt->n_ref > 0) - return NULL; - - return mfree(rt); -} - -sd_ndisc_router *ndisc_router_new(size_t raw_size) { - sd_ndisc_router *rt; - - rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size); - if (!rt) - return NULL; - - rt->raw_size = raw_size; - rt->n_ref = 1; - - return rt; -} - -_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) { - _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; - int r; - - assert_return(ret, -EINVAL); - assert_return(raw || raw_size <= 0, -EINVAL); - - rt = ndisc_router_new(raw_size); - if (!rt) - return -ENOMEM; - - memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size); - r = ndisc_router_parse(rt); - if (r < 0) - return r; - - *ret = rt; - rt = NULL; - - return r; -} - -_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { - assert_return(rt, -EINVAL); - assert_return(ret_addr, -EINVAL); - - if (in6_addr_is_null(&rt->address)) - return -ENODATA; - - *ret_addr = rt->address; - return 0; -} - -_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) { - assert_return(rt, -EINVAL); - assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); - assert_return(clock_supported(clock), -EOPNOTSUPP); - assert_return(ret, -EINVAL); - - if (!triple_timestamp_is_set(&rt->timestamp)) - return -ENODATA; - - *ret = triple_timestamp_by_clock(&rt->timestamp, clock); - return 0; -} - -_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - *ret = NDISC_ROUTER_RAW(rt); - *size = rt->raw_size; - - return 0; -} - -int ndisc_router_parse(sd_ndisc_router *rt) { - struct nd_router_advert *a; - const uint8_t *p; - bool has_mtu = false, has_flag_extension = false; - size_t left; - - assert(rt); - - if (rt->raw_size < sizeof(struct nd_router_advert)) { - log_ndisc("Too small to be a router advertisement, ignoring."); - return -EBADMSG; - } - - /* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */ - a = NDISC_ROUTER_RAW(rt); - - if (a->nd_ra_type != ND_ROUTER_ADVERT) { - log_ndisc("Received ND packet that is not a router advertisement, ignoring."); - return -EBADMSG; - } - - if (a->nd_ra_code != 0) { - log_ndisc("Received ND packet with wrong RA code, ignoring."); - return -EBADMSG; - } - - rt->hop_limit = a->nd_ra_curhoplimit; - rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */ - rt->lifetime = be16toh(a->nd_ra_router_lifetime); - - rt->preference = (rt->flags >> 3) & 3; - if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) - rt->preference = SD_NDISC_PREFERENCE_MEDIUM; - - p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert); - left = rt->raw_size - sizeof(struct nd_router_advert); - - for (;;) { - uint8_t type; - size_t length; - - if (left == 0) - break; - - if (left < 2) { - log_ndisc("Option lacks header, ignoring datagram."); - return -EBADMSG; - } - - type = p[0]; - length = p[1] * 8; - - if (length == 0) { - log_ndisc("Zero-length option, ignoring datagram."); - return -EBADMSG; - } - if (left < length) { - log_ndisc("Option truncated, ignoring datagram."); - return -EBADMSG; - } - - switch (type) { - - case SD_NDISC_OPTION_PREFIX_INFORMATION: - - if (length != 4*8) { - log_ndisc("Prefix option of invalid size, ignoring datagram."); - return -EBADMSG; - } - - if (p[2] > 128) { - log_ndisc("Bad prefix length, ignoring datagram."); - return -EBADMSG; - } - - break; - - case SD_NDISC_OPTION_MTU: { - uint32_t m; - - if (has_mtu) { - log_ndisc("MTU option specified twice, ignoring."); - continue; - } - - if (length != 8) { - log_ndisc("MTU option of invalid size, ignoring datagram."); - return -EBADMSG; - } - - m = be32toh(*(uint32_t*) (p + 4)); - if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */ - rt->mtu = m; - - has_mtu = true; - break; - } - - case SD_NDISC_OPTION_ROUTE_INFORMATION: - if (length < 1*8 || length > 3*8) { - log_ndisc("Route information option of invalid size, ignoring datagram."); - return -EBADMSG; - } - - if (p[2] > 128) { - log_ndisc("Bad route prefix length, ignoring datagram."); - return -EBADMSG; - } - - break; - - case SD_NDISC_OPTION_RDNSS: - if (length < 3*8 || (length % (2*8)) != 1*8) { - log_ndisc("RDNSS option has invalid size."); - return -EBADMSG; - } - - break; - - case SD_NDISC_OPTION_FLAGS_EXTENSION: - - if (has_flag_extension) { - log_ndisc("Flags extension option specified twice, ignoring."); - continue; - } - - if (length < 1*8) { - log_ndisc("Flags extension option has invalid size."); - return -EBADMSG; - } - - /* Add in the additional flags bits */ - rt->flags |= - ((uint64_t) p[2] << 8) | - ((uint64_t) p[3] << 16) | - ((uint64_t) p[4] << 24) | - ((uint64_t) p[5] << 32) | - ((uint64_t) p[6] << 40) | - ((uint64_t) p[7] << 48); - - has_flag_extension = true; - break; - - case SD_NDISC_OPTION_DNSSL: - if (length < 2*8) { - log_ndisc("DNSSL option has invalid size."); - return -EBADMSG; - } - - break; - } - - p += length, left -= length; - } - - rt->rindex = sizeof(struct nd_router_advert); - return 0; -} - -_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->hop_limit; - return 0; -} - -_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) { - assert_return(rt, -EINVAL); - assert_return(ret_flags, -EINVAL); - - *ret_flags = rt->flags; - return 0; -} - -_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) { - assert_return(rt, -EINVAL); - assert_return(ret_lifetime, -EINVAL); - - *ret_lifetime = rt->lifetime; - return 0; -} - -_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - *ret = rt->preference; - return 0; -} - -_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - if (rt->mtu <= 0) - return -ENODATA; - - *ret = rt->mtu; - return 0; -} - -_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { - assert_return(rt, -EINVAL); - - assert(rt->raw_size >= sizeof(struct nd_router_advert)); - rt->rindex = sizeof(struct nd_router_advert); - - return rt->rindex < rt->raw_size; -} - -_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) { - size_t length; - - assert_return(rt, -EINVAL); - - if (rt->rindex == rt->raw_size) /* EOF */ - return -ESPIPE; - - if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ - return -EBADMSG; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (rt->rindex + length > rt->raw_size) - return -EBADMSG; - - rt->rindex += length; - return rt->rindex < rt->raw_size; -} - -_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - if (rt->rindex == rt->raw_size) /* EOF */ - return -ESPIPE; - - if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ - return -EBADMSG; - - *ret = NDISC_ROUTER_OPTION_TYPE(rt); - return 0; -} - -_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { - uint8_t k; - int r; - - assert_return(rt, -EINVAL); - - r = sd_ndisc_router_option_get_type(rt, &k); - if (r < 0) - return r; - - return type == k; -} - -_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) { - size_t length; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - assert_return(size, -EINVAL); - - /* Note that this returns the full option, including the option header */ - - if (rt->rindex + 2 > rt->raw_size) - return -EBADMSG; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (rt->rindex + length > rt->raw_size) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - *size = length; - - return 0; -} - -static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) { - struct nd_opt_prefix_info *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length != sizeof(struct nd_opt_prefix_info)) - return -EBADMSG; - - ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); - if (ri->nd_opt_pi_prefix_len > 128) - return -EBADMSG; - - *ret = ri; - return 0; -} - -_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) { - struct nd_opt_prefix_info *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &ri); - if (r < 0) - return r; - - *ret = be32toh(ri->nd_opt_pi_valid_time); - return 0; -} - -_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret = be32toh(pi->nd_opt_pi_preferred_time); - return 0; -} - -_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret = pi->nd_opt_pi_flags_reserved; - return 0; -} - -_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret_addr, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - *ret_addr = pi->nd_opt_pi_prefix; - return 0; -} - -_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - struct nd_opt_prefix_info *pi; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_prefix_info(rt, &pi); - if (r < 0) - return r; - - if (pi->nd_opt_pi_prefix_len > 128) - return -EBADMSG; - - *ret = pi->nd_opt_pi_prefix_len; - return 0; -} - -static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) { - uint8_t *ri; - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 1*8 || length > 3*8) - return -EBADMSG; - - ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - - if (ri[2] > 128) - return -EBADMSG; - - *ret = ri; - return 0; -} - -_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = be32toh(*(uint32_t*) (ri + 4)); - return 0; -} - -_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret_addr, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - zero(*ret_addr); - memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8); - - return 0; -} - -_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = ri[2]; - return 0; -} - -_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_route_info(rt, &ri); - if (r < 0) - return r; - - *ret = (ri[3] >> 3) & 3; - if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) - *ret = SD_NDISC_PREFERENCE_MEDIUM; - - return 0; -} - -static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) { - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 3*8 || (length % (2*8)) != 1*8) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - return 0; -} - -_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_rdnss_info(rt, &ri); - if (r < 0) - return r; - - *ret = (const struct in6_addr*) (ri + 8); - return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16; -} - -_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_rdnss_info(rt, &ri); - if (r < 0) - return r; - - *ret = be32toh(*(uint32_t*) (ri + 4)); - return 0; -} - -static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) { - size_t length; - int r; - - assert(rt); - assert(ret); - - r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL); - if (r < 0) - return r; - if (r == 0) - return -EMEDIUMTYPE; - - length = NDISC_ROUTER_OPTION_LENGTH(rt); - if (length < 2*8) - return -EBADMSG; - - *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; - return 0; -} - -_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) { - _cleanup_strv_free_ char **l = NULL; - _cleanup_free_ char *e = NULL; - size_t allocated = 0, n = 0, left; - uint8_t *ri, *p; - bool first = true; - int r; - unsigned k = 0; - - assert_return(rt, -EINVAL); - assert_return(ret, -EINVAL); - - r = get_dnssl_info(rt, &ri); - if (r < 0) - return r; - - p = ri + 8; - left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8; - - for (;;) { - if (left == 0) { - - if (n > 0) /* Not properly NUL terminated */ - return -EBADMSG; - - break; - } - - if (*p == 0) { - /* Found NUL termination */ - - if (n > 0) { - _cleanup_free_ char *normalized = NULL; - - e[n] = 0; - r = dns_name_normalize(e, &normalized); - if (r < 0) - return r; - - /* Ignore the root domain name or "localhost" and friends */ - if (!is_localhost(normalized) && - !dns_name_is_root(normalized)) { - - if (strv_push(&l, normalized) < 0) - return -ENOMEM; - - normalized = NULL; - k++; - } - } - - n = 0; - first = true; - p++, left--; - continue; - } - - /* Check for compression (which is not allowed) */ - if (*p > 63) - return -EBADMSG; - - if (1U + *p + 1U > left) - return -EBADMSG; - - if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U)) - return -ENOMEM; - - if (first) - first = false; - else - e[n++] = '.'; - - r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX); - if (r < 0) - return r; - - n += r; - - left -= 1 + *p; - p += 1 + *p; - } - - if (strv_isempty(l)) { - *ret = NULL; - return 0; - } - - *ret = l; - l = NULL; - - return k; -} - -_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) { - uint8_t *ri; - int r; - - assert_return(rt, -EINVAL); - assert_return(ret_sec, -EINVAL); - - r = get_dnssl_info(rt, &ri); - if (r < 0) - return r; - - *ret_sec = be32toh(*(uint32_t*) (ri + 4)); - return 0; -} diff --git a/src/libsystemd-network/ndisc-router.h b/src/libsystemd-network/ndisc-router.h deleted file mode 100644 index 1fe703da63..0000000000 --- a/src/libsystemd-network/ndisc-router.h +++ /dev/null @@ -1,62 +0,0 @@ -#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 . -***/ - -#include "sd-ndisc.h" - -#include "time-util.h" - -struct sd_ndisc_router { - unsigned n_ref; - - triple_timestamp timestamp; - struct in6_addr address; - - /* The raw packet size. The data is appended to the object, accessible via NDIS_ROUTER_RAW() */ - size_t raw_size; - - /* The current read index for the iterative option interface */ - size_t rindex; - - uint64_t flags; - unsigned preference; - uint16_t lifetime; - - uint8_t hop_limit; - uint32_t mtu; -}; - -static inline void* NDISC_ROUTER_RAW(const sd_ndisc_router *rt) { - return (uint8_t*) rt + ALIGN(sizeof(sd_ndisc_router)); -} - -static inline void *NDISC_ROUTER_OPTION_DATA(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_RAW(rt)) + rt->rindex; -} - -static inline uint8_t NDISC_ROUTER_OPTION_TYPE(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[0]; -} -static inline size_t NDISC_ROUTER_OPTION_LENGTH(const sd_ndisc_router *rt) { - return ((uint8_t*) NDISC_ROUTER_OPTION_DATA(rt))[1] * 8; -} - -sd_ndisc_router *ndisc_router_new(size_t raw_size); -int ndisc_router_parse(sd_ndisc_router *rt); diff --git a/src/libsystemd-network/network-internal.c b/src/libsystemd-network/network-internal.c deleted file mode 100644 index 9d78b953fc..0000000000 --- a/src/libsystemd-network/network-internal.c +++ /dev/null @@ -1,556 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2013 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 . -***/ - -#include -#include -#include - -#include "sd-ndisc.h" - -#include "alloc-util.h" -#include "condition.h" -#include "conf-parser.h" -#include "dhcp-lease-internal.h" -#include "ether-addr-util.h" -#include "hexdecoct.h" -#include "log.h" -#include "network-internal.h" -#include "parse-util.h" -#include "siphash24.h" -#include "socket-util.h" -#include "string-util.h" -#include "strv.h" -#include "utf8.h" -#include "util.h" - -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) <= 0) - return false; - - if (match_virt && condition_test(match_virt) <= 0) - return false; - - if (match_kernel && condition_test(match_kernel) <= 0) - return false; - - if (match_arch && condition_test(match_arch) <= 0) - 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_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; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - for (;;) { - _cleanup_free_ char *word = NULL; - - r = extract_first_word(&rvalue, &word, NULL, 0); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue); - return 0; - } - if (r == 0) - break; - - if (!ifname_valid(word)) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); - return 0; - } - - r = strv_push(sv, word); - if (r < 0) - return log_oom(); - - word = NULL; - } - - 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; - const char *start; - size_t offset; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - n = new0(struct ether_addr, 1); - if (!n) - return log_oom(); - - start = rvalue + strspn(rvalue, WHITESPACE); - r = ether_addr_from_string(start, n, &offset); - - if (r || (start[offset + strspn(start + offset, WHITESPACE)] != '\0')) { - log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring assignment: %s", rvalue); - free(n); - return 0; - } - - free(*hwaddr); - *hwaddr = n; - - return 0; -} - -int config_parse_iaid(const char *unit, - const char *filename, - unsigned line, - const char *section, - unsigned section_line, - const char *lvalue, - int ltype, - const char *rvalue, - void *data, - void *userdata) { - uint32_t iaid; - int r; - - assert(filename); - assert(lvalue); - assert(rvalue); - assert(data); - - r = safe_atou32(rvalue, &iaid); - if (r < 0) { - log_syntax(unit, LOG_ERR, filename, line, r, - "Unable to read IAID, ignoring assignment: %s", rvalue); - return 0; - } - - *((uint32_t *)data) = iaid; - - return 0; -} - -void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { - unsigned i; - - 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; - - assert(ret); - assert(string); - - for (;;) { - _cleanup_free_ char *word = NULL; - struct in_addr *new_addresses; - int r; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - new_addresses = realloc(addresses, (size + 1) * sizeof(struct in_addr)); - if (!new_addresses) - return -ENOMEM; - else - addresses = new_addresses; - - r = inet_pton(AF_INET, word, &(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++) { - char buffer[INET6_ADDRSTRLEN]; - - fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f); - - if (i < size - 1) - fputc(' ', f); - } -} - -int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { - _cleanup_free_ struct in6_addr *addresses = NULL; - int size = 0; - - assert(ret); - assert(string); - - for (;;) { - _cleanup_free_ char *word = NULL; - struct in6_addr *new_addresses; - int r; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - new_addresses = realloc(addresses, (size + 1) * sizeof(struct in6_addr)); - if (!new_addresses) - return -ENOMEM; - else - addresses = new_addresses; - - r = inet_pton(AF_INET6, word, &(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; - - assert(ret); - assert(ret_size); - assert(ret_allocated); - assert(string); - - /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ - for (;;) { - _cleanup_free_ char *word = NULL; - char *tok, *tok_end; - unsigned n; - int r; - - r = extract_first_word(&string, &word, NULL, 0); - if (r < 0) - return r; - if (r == 0) - break; - - if (!GREEDY_REALLOC(routes, allocated, size + 1)) - return -ENOMEM; - - tok = word; - - /* 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 deleted file mode 100644 index 5bcd577167..0000000000 --- a/src/libsystemd-network/network-internal.h +++ /dev/null @@ -1,81 +0,0 @@ -#pragma once - -/*** - This file is part of systemd. - - Copyright (C) 2013 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 . -***/ - -#include - -#include "sd-dhcp-lease.h" - -#include "condition.h" -#include "udev.h" - -bool net_match_config(const struct ether_addr *match_mac, - char * const *match_path, - 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_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 config_parse_iaid(const char *unit, const char *filename, unsigned line, - const char *section, unsigned section_line, const char *lvalue, - int ltype, const char *rvalue, void *data, void *userdata); - -int net_get_unique_predictable_data(struct udev_device *device, uint64_t *result); -const char *net_get_name(struct udev_device *device); - -void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size); -int deserialize_in_addrs(struct in_addr **addresses, const char *string); -void serialize_in6_addrs(FILE *f, const struct in6_addr *addresses, - size_t size); -int deserialize_in6_addrs(struct in6_addr **addresses, const char *string); - -/* don't include "dhcp-lease-internal.h" as it causes conflicts between netinet/ip.h and linux/ip.h */ -struct sd_dhcp_route; - -void serialize_dhcp_routes(FILE *f, const char *key, 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 deleted file mode 100644 index 5ccb23922c..0000000000 --- a/src/libsystemd-network/sd-dhcp-client.c +++ /dev/null @@ -1,1904 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include "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 ifindex; - 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) */ - be32_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_callback_t callback; - 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_callback_t cb, - void *userdata) { - - assert_return(client, -EINVAL); - - client->callback = 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_ifindex(sd_dhcp_client *client, int ifindex) { - - assert_return(client, -EINVAL); - assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); - assert_return(ifindex > 0, -EINVAL); - - client->ifindex = ifindex; - return 0; -} - -int sd_dhcp_client_set_mac( - sd_dhcp_client *client, - const uint8_t *addr, - size_t addr_len, - uint16_t arp_type) { - - 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; -} - -/** - * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid - * without further modification. Otherwise, if duid_type is supported, DUID - * is set based on that type. Otherwise, an error is returned. - */ -int sd_dhcp_client_set_iaid_duid( - sd_dhcp_client *client, - uint32_t iaid, - uint16_t duid_type, - const void *duid, - size_t duid_len) { - - DHCP_CLIENT_DONT_DESTROY(client); - int r; - size_t len; - - assert_return(client, -EINVAL); - assert_return(duid_len == 0 || duid != NULL, -EINVAL); - - if (duid != NULL) { - r = dhcp_validate_duid_len(duid_type, duid_len); - if (r < 0) - return r; - } - - zero(client->client_id); - client->client_id.type = 255; - - /* If IAID is not configured, generate it. */ - if (iaid == 0) { - r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, - client->mac_addr_len, - &client->client_id.ns.iaid); - if (r < 0) - return r; - } else - client->client_id.ns.iaid = htobe32(iaid); - - if (duid != NULL) { - client->client_id.ns.duid.type = htobe16(duid_type); - memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); - len = sizeof(client->client_id.ns.duid.type) + duid_len; - } else if (duid_type == DUID_TYPE_EN) { - r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); - if (r < 0) - return r; - } else - return -EOPNOTSUPP; - - client->client_id_len = sizeof(client->client_id.type) + len + - sizeof(client->client_id.ns.iaid); - - if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { - log_dhcp_client(client, "Configured IAID+DUID, restarting."); - client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); - sd_dhcp_client_start(client); - } - - return 0; -} - -int sd_dhcp_client_set_hostname( - sd_dhcp_client *client, - const char *hostname) { - - char *new_hostname = NULL; - - assert_return(client, -EINVAL); - - if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname)) - return -EINVAL; - - if (streq_ptr(client->hostname, hostname)) - return 0; - - 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); - - if (client->state != DHCP_STATE_BOUND && - client->state != DHCP_STATE_RENEWING && - client->state != DHCP_STATE_REBINDING) - return -EADDRNOTAVAIL; - - if (ret) - *ret = client->lease; - - return 0; -} - -static void client_notify(sd_dhcp_client *client, int event) { - assert(client); - - if (client->callback) - client->callback(client, event, client->userdata); -} - -static int client_initialize(sd_dhcp_client *client) { - assert_return(client, -EINVAL); - - client->receive_message = sd_event_source_unref(client->receive_message); - - client->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 = NULL; - 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->ifindex, 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; - - assert(client); - - 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) { - assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0); - usec += client->start_delay; - } - - r = sd_event_add_time(client->event, - &client->timeout_resend, - clock_boottime_or_monotonic(), - 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->ifindex > 0, -EINVAL); - assert_return(client->fd < 0, -EBUSY); - assert_return(client->xid == 0, -EINVAL); - assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY); - - client->xid = random_u32(); - - r = dhcp_network_bind_raw_socket(client->ifindex, &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; - - assert(client); - - client->receive_message = sd_event_source_unref(client->receive_message); - client->fd = asynchronous_close(client->fd); - - client->state = DHCP_STATE_REBINDING; - client->attempt = 1; - - r = dhcp_network_bind_raw_socket(client->ifindex, &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 = {}; - 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 = recv(fd, message, buflen, 0); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return log_dhcp_client_errno(client, errno, "Could not receive message from UDP socket: %m"); - } - if ((size_t) len < sizeof(DHCPMessage)) { - log_dhcp_client(client, "Too small to be a DHCP message: ignoring"); - return 0; - } - - if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { - log_dhcp_client(client, "Not a DHCP message: ignoring"); - 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->ifindex); - - 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, int64_t 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) { - assert_return(client, 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); - return mfree(client); -} - -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->ifindex = -1; - client->fd = -1; - client->attempt = 1; - client->mtu = DHCP_DEFAULT_MIN_SIZE; - - client->req_opts_size = ELEMENTSOF(default_req_opts); - client->req_opts = memdup(default_req_opts, client->req_opts_size); - if (!client->req_opts) - return -ENOMEM; - - *ret = client; - client = NULL; - - return 0; -} diff --git a/src/libsystemd-network/sd-dhcp-lease.c b/src/libsystemd-network/sd-dhcp-lease.c deleted file mode 100644 index 8387b185c0..0000000000 --- a/src/libsystemd-network/sd-dhcp-lease.c +++ /dev/null @@ -1,1176 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-dhcp-lease.h" - -#include "alloc-util.h" -#include "dhcp-lease-internal.h" -#include "dhcp-protocol.h" -#include "dns-domain.h" -#include "fd-util.h" -#include "fileio.h" -#include "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); - return mfree(lease); -} - -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 = NULL; - - 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 deleted file mode 100644 index f16314a37f..0000000000 --- a/src/libsystemd-network/sd-dhcp-server.c +++ /dev/null @@ -1,1174 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "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" -#include "unaligned.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); - return mfree(server); -} - -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, int64_t 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 = htobe16(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, - uint16_t destination_port, - DHCPMessage *message, size_t len) { - union sockaddr_union dest = { - .in.sin_family = AF_INET, - .in.sin_port = htobe16(destination_port), - .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; - uint16_t destination_port = DHCP_PORT_CLIENT; - 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; - destination_port = DHCP_PORT_SERVER; - if (type == DHCP_NAK) - packet->dhcp.flags = htobe16(0x8000); - } else if (req->message->ciaddr && type != DHCP_NAK) - destination = req->message->ciaddr; - - if (destination != INADDR_ANY) - return dhcp_server_send_udp(server, destination, - destination_port, &packet->dhcp, - sizeof(DHCPMessage) + optoffset); - else if (requested_broadcast(req) || type == DHCP_NAK) - return dhcp_server_send_udp(server, INADDR_BROADCAST, - destination_port, &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; - - if (server->emit_router) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, &server->address); - if (r < 0) - return r; - } - - r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset); - if (r < 0) - 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; - - if (server->emit_router) { - r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_ROUTER, 4, &server->address); - if (r < 0) - return r; - } - - if (server->n_dns > 0) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_DOMAIN_NAME_SERVER, - sizeof(struct in_addr) * server->n_dns, server->dns); - if (r < 0) - return r; - } - - if (server->n_ntp > 0) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_NTP_SERVER, - sizeof(struct in_addr) * server->n_ntp, server->ntp); - if (r < 0) - return r; - } - - if (server->timezone) { - r = dhcp_option_append( - &packet->dhcp, req->max_optlen, &offset, 0, - SD_DHCP_OPTION_NEW_TZDB_TIMEZONE, - strlen(server->timezone), server->timezone); - if (r < 0) - return r; - } - - r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset); - if (r < 0) - 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, DHCP_PORT_CLIENT, - &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 = unaligned_read_be32(option); - - break; - case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: - if (len == 4) - memcpy(&req->requested_ip, option, sizeof(be32_t)); - - break; - case SD_DHCP_OPTION_SERVER_IDENTIFIER: - if (len == 4) - memcpy(&req->server_id, option, sizeof(be32_t)); - - 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 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) - req->max_optlen = unaligned_read_be16(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; -} - -int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled) { - assert_return(server, -EINVAL); - - if (enabled == server->emit_router) - return 0; - - server->emit_router = enabled; - - return 1; -} diff --git a/src/libsystemd-network/sd-dhcp6-client.c b/src/libsystemd-network/sd-dhcp6-client.c deleted file mode 100644 index e81215f7d7..0000000000 --- a/src/libsystemd-network/sd-dhcp6-client.c +++ /dev/null @@ -1,1333 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include - -#include "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 ifindex; - 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_callback_t callback; - 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_callback_t cb, - void *userdata) { - - assert_return(client, -EINVAL); - - client->callback = cb; - client->userdata = userdata; - - return 0; -} - -int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) { - - assert_return(client, -EINVAL); - assert_return(ifindex >= -1, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - - client->ifindex = ifindex; - return 0; -} - -int sd_dhcp6_client_set_local_address( - sd_dhcp6_client *client, - const struct in6_addr *local_address) { - - assert_return(client, -EINVAL); - assert_return(local_address, -EINVAL); - assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL); - - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - - client->local_address = *local_address; - - return 0; -} - -int sd_dhcp6_client_set_mac( - sd_dhcp6_client *client, - const uint8_t *addr, size_t addr_len, - uint16_t arp_type) { - - 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); -} - -/** - * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid - * without further modification. Otherwise, if duid_type is supported, DUID - * is set based on that type. Otherwise, an error is returned. - */ -int sd_dhcp6_client_set_duid( - sd_dhcp6_client *client, - uint16_t duid_type, - const void *duid, - size_t duid_len) { - - int r; - assert_return(client, -EINVAL); - assert_return(duid_len == 0 || duid != NULL, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - - if (duid != NULL) { - r = dhcp_validate_duid_len(duid_type, duid_len); - if (r < 0) - return r; - } - - if (duid != NULL) { - client->duid.type = htobe16(duid_type); - memcpy(&client->duid.raw.data, duid, duid_len); - client->duid_len = sizeof(client->duid.type) + duid_len; - } else if (duid_type == DUID_TYPE_EN) { - r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); - if (r < 0) - return r; - } else - return -EOPNOTSUPP; - - return 0; -} - -int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { - assert_return(client, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - - client->ia_na.id = htobe32(iaid); - - return 0; -} - -int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { - assert_return(client, -EINVAL); - assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); - - client->information_request = enabled; - - return 0; -} - -int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, 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) { - assert(client); - - if (client->callback) - client->callback(client, event, client->userdata); -} - -static void client_set_lease(sd_dhcp6_client *client, sd_dhcp6_lease *lease) { - assert(client); - - if (client->lease) { - dhcp6_lease_clear_timers(&client->lease->ia); - sd_dhcp6_lease_unref(client->lease); - } - - client->lease = lease; -} - -static int client_reset(sd_dhcp6_client *client) { - assert(client); - - 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; - - assert(client); - - 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(s); - assert(client); - assert(client->lease); - - 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(s); - assert(client); - assert(client->lease); - - 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->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.id); - if (r < 0) - return r; - - return 0; -} - -static int client_parse_message( - sd_dhcp6_client *client, - DHCP6Message *message, - size_t len, - sd_dhcp6_lease *lease) { - int r; - uint8_t *optval, *option, *id = NULL; - uint16_t optcode, status; - size_t optlen, id_len; - bool clientid = false; - be32_t iaid_lease; - - assert(client); - assert(message); - assert(len >= sizeof(DHCP6Message)); - assert(lease); - - option = (uint8_t *)message + sizeof(DHCP6Message); - len -= sizeof(DHCP6Message); - - while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, - &optval)) >= 0) { - switch (optcode) { - case 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) { - _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - bool rapid_commit; - int r; - - assert(client); - assert(reply); - - if (reply->type != DHCP6_REPLY) - return 0; - - 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) { - _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; - uint8_t pref_advertise = 0, pref_lease = 0; - int r; - - if (advertise->type != DHCP6_ADVERTISE) - return 0; - - 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 = recv(fd, message, buflen, 0); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m"); - - } - if ((size_t) len < sizeof(DHCP6Message)) { - log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); - return 0; - } - - 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->ifindex > 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) { - enum DHCP6State state = DHCP6_STATE_SOLICITATION; - int r = 0; - - assert_return(client, -EINVAL); - assert_return(client->event, -EINVAL); - assert_return(client->ifindex > 0, -EINVAL); - assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL); - - if (!IN_SET(client->state, DHCP6_STATE_STOPPED)) - return -EBUSY; - - r = client_reset(client); - if (r < 0) - 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->ifindex, &client->local_address); - if (r < 0) { - _cleanup_free_ char *p = NULL; - - (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p); - return log_dhcp6_client_errno(client, r, - "Failed to bind to UDP socket at address %s: %m", strna(p)); - } - - client->fd = r; - - 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, int64_t 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) { - assert_return(client, 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); - return mfree(client); -} - -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->ifindex = -1; - client->fd = -1; - - client->req_opts_len = ELEMENTSOF(default_req_opts); - client->req_opts = new0(be16_t, client->req_opts_len); - if (!client->req_opts) - return -ENOMEM; - - 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 deleted file mode 100644 index ab59977a3f..0000000000 --- a/src/libsystemd-network/sd-dhcp6-lease.c +++ /dev/null @@ -1,408 +0,0 @@ -/*** - 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 . -***/ - -#include - -#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); - return mfree(lease); -} - -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 deleted file mode 100644 index 4dd343c101..0000000000 --- a/src/libsystemd-network/sd-ipv4acd.c +++ /dev/null @@ -1,524 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-ipv4acd.h" - -#include "alloc-util.h" -#include "arp-util.h" -#include "ether-addr-util.h" -#include "fd-util.h" -#include "in-addr-util.h" -#include "list.h" -#include "random-util.h" -#include "siphash24.h" -#include "string-util.h" -#include "util.h" - -/* Constants from the RFC */ -#define PROBE_WAIT_USEC (1U * USEC_PER_SEC) -#define PROBE_NUM 3U -#define PROBE_MIN_USEC (1U * USEC_PER_SEC) -#define PROBE_MAX_USEC (2U * USEC_PER_SEC) -#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC) -#define ANNOUNCE_NUM 2U -#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC) -#define MAX_CONFLICTS 10U -#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC) -#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC) - -typedef enum IPv4ACDState { - IPV4ACD_STATE_INIT, - IPV4ACD_STATE_STARTED, - IPV4ACD_STATE_WAITING_PROBE, - IPV4ACD_STATE_PROBING, - IPV4ACD_STATE_WAITING_ANNOUNCE, - IPV4ACD_STATE_ANNOUNCING, - IPV4ACD_STATE_RUNNING, - _IPV4ACD_STATE_MAX, - _IPV4ACD_STATE_INVALID = -1 -} IPv4ACDState; - -struct sd_ipv4acd { - unsigned n_ref; - - IPv4ACDState state; - int ifindex; - int fd; - - unsigned n_iteration; - unsigned n_conflict; - - sd_event_source *receive_message_event_source; - sd_event_source *timer_event_source; - - usec_t defend_window; - be32_t address; - - /* External */ - struct ether_addr mac_addr; - - sd_event *event; - int event_priority; - sd_ipv4acd_callback_t callback; - void* userdata; -}; - -#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__) -#define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__) - -static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) { - assert(acd); - assert(st < _IPV4ACD_STATE_MAX); - - if (st == acd->state && !reset_counter) - acd->n_iteration++; - else { - acd->state = st; - acd->n_iteration = 0; - } -} - -static void ipv4acd_reset(sd_ipv4acd *acd) { - assert(acd); - - acd->timer_event_source = sd_event_source_unref(acd->timer_event_source); - acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source); - - acd->fd = safe_close(acd->fd); - - ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true); -} - -sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd) { - if (!acd) - return NULL; - - assert_se(acd->n_ref >= 1); - acd->n_ref++; - - return acd; -} - -sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) { - if (!acd) - return NULL; - - assert_se(acd->n_ref >= 1); - acd->n_ref--; - - if (acd->n_ref > 0) - return NULL; - - ipv4acd_reset(acd); - sd_ipv4acd_detach_event(acd); - - return mfree(acd); -} - -int sd_ipv4acd_new(sd_ipv4acd **ret) { - _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL; - - assert_return(ret, -EINVAL); - - acd = new0(sd_ipv4acd, 1); - if (!acd) - return -ENOMEM; - - acd->n_ref = 1; - acd->state = IPV4ACD_STATE_INIT; - acd->ifindex = -1; - acd->fd = -1; - - *ret = acd; - acd = NULL; - - return 0; -} - -static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) { - assert(acd); - - if (!acd->callback) - return; - - acd->callback(acd, event, acd->userdata); -} - -int sd_ipv4acd_stop(sd_ipv4acd *acd) { - assert_return(acd, -EINVAL); - - ipv4acd_reset(acd); - - log_ipv4acd(acd, "STOPPED"); - - ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP); - - return 0; -} - -static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); - -static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) { - _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; - usec_t next_timeout, time_now; - int r; - - assert(acd); - - next_timeout = usec; - - if (random_usec > 0) - next_timeout += (usec_t) random_u64() % random_usec; - - assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0); - - r = sd_event_add_time(acd->event, &timer, clock_boottime_or_monotonic(), time_now + next_timeout, 0, ipv4acd_on_timeout, acd); - if (r < 0) - return r; - - r = sd_event_source_set_priority(timer, acd->event_priority); - if (r < 0) - return r; - - (void) sd_event_source_set_description(timer, "ipv4acd-timer"); - - sd_event_source_unref(acd->timer_event_source); - acd->timer_event_source = timer; - timer = NULL; - - return 0; -} - -static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) { - assert(acd); - assert(arp); - - /* see the BPF */ - if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0) - return true; - - /* the TPA matched instead of the SPA, this is not a conflict */ - return false; -} - -static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - sd_ipv4acd *acd = userdata; - int r = 0; - - assert(acd); - - switch (acd->state) { - - case IPV4ACD_STATE_STARTED: - ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); - - if (acd->n_conflict >= MAX_CONFLICTS) { - char ts[FORMAT_TIMESPAN_MAX]; - log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); - - r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); - if (r < 0) - goto fail; - - acd->n_conflict = 0; - } else { - r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); - if (r < 0) - goto fail; - } - - break; - - case IPV4ACD_STATE_WAITING_PROBE: - case IPV4ACD_STATE_PROBING: - /* Send a probe */ - r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); - if (r < 0) { - log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); - goto fail; - } else { - _cleanup_free_ char *address = NULL; - union in_addr_union addr = { .in.s_addr = acd->address }; - - (void) in_addr_to_string(AF_INET, &addr, &address); - log_ipv4acd(acd, "Probing %s", strna(address)); - } - - if (acd->n_iteration < PROBE_NUM - 2) { - ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); - - r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); - if (r < 0) - goto fail; - } else { - ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); - - r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); - if (r < 0) - goto fail; - } - - break; - - case IPV4ACD_STATE_ANNOUNCING: - if (acd->n_iteration >= ANNOUNCE_NUM - 1) { - ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false); - break; - } - - /* fall through */ - - case IPV4ACD_STATE_WAITING_ANNOUNCE: - /* Send announcement packet */ - r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); - if (r < 0) { - log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); - goto fail; - } else - log_ipv4acd(acd, "ANNOUNCE"); - - ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); - - r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0); - if (r < 0) - goto fail; - - if (acd->n_iteration == 0) { - acd->n_conflict = 0; - ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND); - } - - break; - - default: - assert_not_reached("Invalid state."); - } - - return 0; - -fail: - sd_ipv4acd_stop(acd); - return 0; -} - -static void ipv4acd_on_conflict(sd_ipv4acd *acd) { - _cleanup_free_ char *address = NULL; - union in_addr_union addr = { .in.s_addr = acd->address }; - - assert(acd); - - acd->n_conflict++; - - (void) in_addr_to_string(AF_INET, &addr, &address); - log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); - - ipv4acd_reset(acd); - ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); -} - -static int ipv4acd_on_packet( - sd_event_source *s, - int fd, - uint32_t revents, - void *userdata) { - - sd_ipv4acd *acd = userdata; - struct ether_arp packet; - ssize_t n; - int r; - - assert(s); - assert(acd); - assert(fd >= 0); - - n = recv(fd, &packet, sizeof(struct ether_arp), 0); - if (n < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m"); - goto fail; - } - if ((size_t) n != sizeof(struct ether_arp)) { - log_ipv4acd(acd, "Ignoring too short ARP packet."); - return 0; - } - - switch (acd->state) { - - case IPV4ACD_STATE_ANNOUNCING: - case IPV4ACD_STATE_RUNNING: - - if (ipv4acd_arp_conflict(acd, &packet)) { - usec_t ts; - - assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); - - /* Defend address */ - if (ts > acd->defend_window) { - acd->defend_window = ts + DEFEND_INTERVAL_USEC; - r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); - if (r < 0) { - log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); - goto fail; - } else - log_ipv4acd(acd, "DEFEND"); - - } else - ipv4acd_on_conflict(acd); - } - break; - - case IPV4ACD_STATE_WAITING_PROBE: - case IPV4ACD_STATE_PROBING: - case IPV4ACD_STATE_WAITING_ANNOUNCE: - /* BPF ensures this packet indicates a conflict */ - ipv4acd_on_conflict(acd); - break; - - default: - assert_not_reached("Invalid state."); - } - - return 0; - -fail: - sd_ipv4acd_stop(acd); - return 0; -} - -int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) { - assert_return(acd, -EINVAL); - assert_return(ifindex > 0, -EINVAL); - assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); - - acd->ifindex = ifindex; - - return 0; -} - -int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) { - assert_return(acd, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); - - acd->mac_addr = *addr; - - return 0; -} - -int sd_ipv4acd_detach_event(sd_ipv4acd *acd) { - assert_return(acd, -EINVAL); - - acd->event = sd_event_unref(acd->event); - - return 0; -} - -int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) { - int r; - - assert_return(acd, -EINVAL); - assert_return(!acd->event, -EBUSY); - - if (event) - acd->event = sd_event_ref(event); - else { - r = sd_event_default(&acd->event); - if (r < 0) - return r; - } - - acd->event_priority = priority; - - return 0; -} - -int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) { - assert_return(acd, -EINVAL); - - acd->callback = cb; - acd->userdata = userdata; - - return 0; -} - -int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) { - assert_return(acd, -EINVAL); - assert_return(address, -EINVAL); - assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); - - acd->address = address->s_addr; - - return 0; -} - -int sd_ipv4acd_is_running(sd_ipv4acd *acd) { - assert_return(acd, false); - - return acd->state != IPV4ACD_STATE_INIT; -} - -int sd_ipv4acd_start(sd_ipv4acd *acd) { - int r; - - assert_return(acd, -EINVAL); - assert_return(acd->event, -EINVAL); - assert_return(acd->ifindex > 0, -EINVAL); - assert_return(acd->address != 0, -EINVAL); - assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); - assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); - - r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); - if (r < 0) - return r; - - safe_close(acd->fd); - acd->fd = r; - acd->defend_window = 0; - acd->n_conflict = 0; - - r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message"); - - r = ipv4acd_set_next_wakeup(acd, 0, 0); - if (r < 0) - goto fail; - - ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); - return 0; - -fail: - ipv4acd_reset(acd); - return r; -} diff --git a/src/libsystemd-network/sd-ipv4ll.c b/src/libsystemd-network/sd-ipv4ll.c deleted file mode 100644 index 13209261f9..0000000000 --- a/src/libsystemd-network/sd-ipv4ll.c +++ /dev/null @@ -1,344 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-ipv4acd.h" -#include "sd-ipv4ll.h" - -#include "alloc-util.h" -#include "ether-addr-util.h" -#include "in-addr-util.h" -#include "list.h" -#include "random-util.h" -#include "siphash24.h" -#include "sparse-endian.h" -#include "string-util.h" -#include "util.h" - -#define IPV4LL_NETWORK UINT32_C(0xA9FE0000) -#define IPV4LL_NETMASK UINT32_C(0xFFFF0000) - -#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 ether_addr mac; - - struct { - le64_t value; - le64_t generation; - } seed; - bool seed_set; - - /* External */ - be32_t claimed_address; - - sd_ipv4ll_callback_t callback; - void* userdata; -}; - -#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__) -#define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__) - -static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, 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); - return mfree(ll); -} - -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) { - assert_return(ll, -EINVAL); - - return sd_ipv4acd_stop(ll->acd); -} - -int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) { - assert_return(ll, -EINVAL); - assert_return(ifindex > 0, -EINVAL); - assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - - return sd_ipv4acd_set_ifindex(ll->acd, ifindex); -} - -int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { - int r; - - assert_return(ll, -EINVAL); - assert_return(addr, -EINVAL); - assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - - r = sd_ipv4acd_set_mac(ll->acd, addr); - if (r < 0) - return r; - - ll->mac = *addr; - return 0; -} - -int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { - assert_return(ll, -EINVAL); - - return sd_ipv4acd_detach_event(ll->acd); -} - -int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) { - assert_return(ll, -EINVAL); - - return sd_ipv4acd_attach_event(ll->acd, event, priority); -} - -int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) { - assert_return(ll, -EINVAL); - - ll->callback = 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, uint64_t seed) { - assert_return(ll, -EINVAL); - assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - - ll->seed.value = htole64(seed); - ll->seed_set = true; - - 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) { - assert(address); - - if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address)) - return false; - - return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U); -} - -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; -} - -#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b) - -static int ipv4ll_pick_address(sd_ipv4ll *ll) { - _cleanup_free_ char *address = NULL; - be32_t addr; - - assert(ll); - - do { - uint64_t h; - - h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes); - - /* Increase the generation counter by one */ - ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1); - - addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK); - } while (addr == ll->address || - IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U)); - - (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address); - log_ipv4ll(ll, "Picked new IP address %s.", strna(address)); - - return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr }); -} - -#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) - -int sd_ipv4ll_start(sd_ipv4ll *ll) { - int r; - bool picked_address = false; - - assert_return(ll, -EINVAL); - assert_return(!ether_addr_is_null(&ll->mac), -EINVAL); - assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); - - /* If no random seed is set, generate some from the MAC address */ - if (!ll->seed_set) - ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes)); - - /* Restart the generation counter. */ - ll->seed.generation = 0; - - if (ll->address == 0) { - r = ipv4ll_pick_address(ll); - if (r < 0) - return r; - - picked_address = true; - } - - r = sd_ipv4acd_start(ll->acd); - if (r < 0) { - - /* We couldn't start? If so, let's forget the picked address again, the user might make a change and - * retry, and we want the new data to take effect when picking an address. */ - if (picked_address) - ll->address = 0; - - return r; - } - - return 0; -} - -static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { - assert(ll); - - if (ll->callback) - ll->callback(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 deleted file mode 100644 index 0702241506..0000000000 --- a/src/libsystemd-network/sd-lldp.c +++ /dev/null @@ -1,537 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "sd-lldp.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "lldp-internal.h" -#include "lldp-neighbor.h" -#include "lldp-network.h" -#include "socket-util.h" -#include "ether-addr-util.h" - -#define LLDP_DEFAULT_NEIGHBORS_MAX 128U - -static void lldp_flush_neighbors(sd_lldp *lldp) { - sd_lldp_neighbor *n; - - assert(lldp); - - while ((n = hashmap_first(lldp->neighbor_by_id))) - lldp_neighbor_unlink(n); -} - -static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) { - assert(lldp); - - log_lldp("Invoking callback for '%c'.", event); - - if (!lldp->callback) - return; - - lldp->callback(lldp, event, n, lldp->userdata); -} - -static int lldp_make_space(sd_lldp *lldp, size_t extra) { - usec_t t = USEC_INFINITY; - bool changed = false; - - assert(lldp); - - /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries - * are free. */ - - for (;;) { - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - - n = prioq_peek(lldp->neighbor_by_expiry); - if (!n) - break; - - sd_lldp_neighbor_ref(n); - - if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra)) - goto remove_one; - - if (t == USEC_INFINITY) - t = now(clock_boottime_or_monotonic()); - - if (n->until > t) - break; - - remove_one: - lldp_neighbor_unlink(n); - lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n); - changed = true; - } - - return changed; -} - -static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { - assert(lldp); - assert(n); - - /* Don't keep data with a zero TTL */ - if (n->ttl <= 0) - return false; - - /* Filter out data from the filter address */ - if (!ether_addr_is_null(&lldp->filter_address) && - ether_addr_equal(&lldp->filter_address, &n->source_address)) - return false; - - /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with - * no caps field set. */ - if (n->has_capabilities && - (n->enabled_capabilities & lldp->capability_mask) == 0) - return false; - - /* Keep everything else */ - return true; -} - -static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor); - -static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL; - bool keep; - int r; - - assert(lldp); - assert(n); - assert(!n->lldp); - - keep = lldp_keep_neighbor(lldp, n); - - /* First retrieve the old entry for this MSAP */ - old = hashmap_get(lldp->neighbor_by_id, &n->id); - if (old) { - sd_lldp_neighbor_ref(old); - - if (!keep) { - lldp_neighbor_unlink(old); - lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); - return 0; - } - - if (lldp_neighbor_equal(n, old)) { - /* Is this equal, then restart the TTL counter, but don't do anyting else. */ - old->timestamp = n->timestamp; - lldp_start_timer(lldp, old); - lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old); - return 0; - } - - /* Data changed, remove the old entry, and add a new one */ - lldp_neighbor_unlink(old); - - } else if (!keep) - return 0; - - /* Then, make room for at least one new neighbor */ - lldp_make_space(lldp, 1); - - r = hashmap_put(lldp->neighbor_by_id, &n->id, n); - if (r < 0) - goto finish; - - r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx); - if (r < 0) { - assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n); - goto finish; - } - - n->lldp = lldp; - - lldp_start_timer(lldp, n); - lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n); - - return 1; - -finish: - if (old) - lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); - - return r; -} - -static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) { - int r; - - assert(lldp); - assert(n); - - r = lldp_neighbor_parse(n); - if (r == -EBADMSG) /* Ignore bad messages */ - return 0; - if (r < 0) - return r; - - r = lldp_add_neighbor(lldp, n); - if (r < 0) { - log_lldp_errno(r, "Failed to add datagram. Ignoring."); - return 0; - } - - log_lldp("Successfully processed LLDP datagram."); - return 0; -} - -static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; - ssize_t space, length; - sd_lldp *lldp = userdata; - struct timespec ts; - - assert(fd >= 0); - assert(lldp); - - space = next_datagram_size_fd(fd); - if (space < 0) - return log_lldp_errno(space, "Failed to determine datagram size to read: %m"); - - n = lldp_neighbor_new(space); - if (!n) - return -ENOMEM; - - length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT); - if (length < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return log_lldp_errno(errno, "Failed to read LLDP datagram: %m"); - } - - if ((size_t) length != n->raw_size) { - log_lldp("Packet size mismatch."); - return -EINVAL; - } - - /* Try to get the timestamp of this packet if it is known */ - if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0) - triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts)); - else - triple_timestamp_get(&n->timestamp); - - return lldp_handle_datagram(lldp, n); -} - -static void lldp_reset(sd_lldp *lldp) { - assert(lldp); - - lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); - lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); - lldp->fd = safe_close(lldp->fd); -} - -_public_ int sd_lldp_start(sd_lldp *lldp) { - int r; - - assert_return(lldp, -EINVAL); - assert_return(lldp->event, -EINVAL); - assert_return(lldp->ifindex > 0, -EINVAL); - - if (lldp->fd >= 0) - return 0; - - assert(!lldp->io_event_source); - - lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex); - if (lldp->fd < 0) - return lldp->fd; - - r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); - - log_lldp("Started LLDP client"); - return 1; - -fail: - lldp_reset(lldp); - return r; -} - -_public_ int sd_lldp_stop(sd_lldp *lldp) { - assert_return(lldp, -EINVAL); - - if (lldp->fd < 0) - return 0; - - log_lldp("Stopping LLDP client"); - - lldp_reset(lldp); - lldp_flush_neighbors(lldp); - - return 1; -} - -_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) { - int r; - - assert_return(lldp, -EINVAL); - assert_return(lldp->fd < 0, -EBUSY); - assert_return(!lldp->event, -EBUSY); - - if (event) - lldp->event = sd_event_ref(event); - else { - r = sd_event_default(&lldp->event); - if (r < 0) - return r; - } - - lldp->event_priority = priority; - - return 0; -} - -_public_ int sd_lldp_detach_event(sd_lldp *lldp) { - - assert_return(lldp, -EINVAL); - assert_return(lldp->fd < 0, -EBUSY); - - lldp->event = sd_event_unref(lldp->event); - return 0; -} - -_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) { - assert_return(lldp, NULL); - - return lldp->event; -} - -_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) { - assert_return(lldp, -EINVAL); - - lldp->callback = cb; - lldp->userdata = userdata; - - return 0; -} - -_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) { - assert_return(lldp, -EINVAL); - assert_return(ifindex > 0, -EINVAL); - assert_return(lldp->fd < 0, -EBUSY); - - lldp->ifindex = ifindex; - return 0; -} - -_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) { - - if (!lldp) - return NULL; - - assert(lldp->n_ref > 0); - lldp->n_ref++; - - return lldp; -} - -_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) { - - if (!lldp) - return NULL; - - assert(lldp->n_ref > 0); - lldp->n_ref --; - - if (lldp->n_ref > 0) - return NULL; - - lldp_reset(lldp); - sd_lldp_detach_event(lldp); - lldp_flush_neighbors(lldp); - - hashmap_free(lldp->neighbor_by_id); - prioq_free(lldp->neighbor_by_expiry); - return mfree(lldp); -} - -_public_ int sd_lldp_new(sd_lldp **ret) { - _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; - int r; - - assert_return(ret, -EINVAL); - - lldp = new0(sd_lldp, 1); - if (!lldp) - return -ENOMEM; - - lldp->n_ref = 1; - lldp->fd = -1; - lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX; - lldp->capability_mask = (uint16_t) -1; - - lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops); - if (!lldp->neighbor_by_id) - return -ENOMEM; - - r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func); - if (r < 0) - return r; - - *ret = lldp; - lldp = NULL; - - return 0; -} - -static int neighbor_compare_func(const void *a, const void *b) { - const sd_lldp_neighbor * const*x = a, * const *y = b; - - return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id); -} - -static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { - sd_lldp *lldp = userdata; - int r, q; - - r = lldp_make_space(lldp, 0); - if (r < 0) - return log_lldp_errno(r, "Failed to make space: %m"); - - q = lldp_start_timer(lldp, NULL); - if (q < 0) - return log_lldp_errno(q, "Failed to restart timer: %m"); - - return 0; -} - -static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) { - sd_lldp_neighbor *n; - int r; - - assert(lldp); - - if (neighbor) - lldp_neighbor_start_ttl(neighbor); - - n = prioq_peek(lldp->neighbor_by_expiry); - if (!n) { - - if (lldp->timer_event_source) - return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF); - - return 0; - } - - if (lldp->timer_event_source) { - r = sd_event_source_set_time(lldp->timer_event_source, n->until); - if (r < 0) - return r; - - return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT); - } - - if (!lldp->event) - return 0; - - r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp); - if (r < 0) - return r; - - r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority); - if (r < 0) - return r; - - (void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer"); - return 0; -} - -_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) { - sd_lldp_neighbor **l = NULL, *n; - Iterator i; - int k = 0, r; - - assert_return(lldp, -EINVAL); - assert_return(ret, -EINVAL); - - if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */ - *ret = NULL; - return 0; - } - - l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id)); - if (!l) - return -ENOMEM; - - r = lldp_start_timer(lldp, NULL); - if (r < 0) { - free(l); - return r; - } - - HASHMAP_FOREACH(n, lldp->neighbor_by_id, i) - l[k++] = sd_lldp_neighbor_ref(n); - - assert((size_t) k == hashmap_size(lldp->neighbor_by_id)); - - /* Return things in a stable order */ - qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func); - *ret = l; - - return k; -} - -_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) { - assert_return(lldp, -EINVAL); - assert_return(m <= 0, -EINVAL); - - lldp->neighbors_max = m; - lldp_make_space(lldp, 0); - - return 0; -} - -_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) { - assert_return(lldp, -EINVAL); - assert_return(mask != 0, -EINVAL); - - lldp->capability_mask = mask; - - return 0; -} - -_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) { - assert_return(lldp, -EINVAL); - - /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so - * that our own can be filtered out here. */ - - if (addr) - lldp->filter_address = *addr; - else - zero(lldp->filter_address); - - return 0; -} diff --git a/src/libsystemd-network/sd-ndisc.c b/src/libsystemd-network/sd-ndisc.c deleted file mode 100644 index 1d3be9b862..0000000000 --- a/src/libsystemd-network/sd-ndisc.c +++ /dev/null @@ -1,420 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#include "sd-ndisc.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "icmp6-util.h" -#include "in-addr-util.h" -#include "ndisc-internal.h" -#include "ndisc-router.h" -#include "socket-util.h" -#include "string-util.h" -#include "util.h" - -#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC) -#define NDISC_MAX_ROUTER_SOLICITATIONS 3U - -static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) { - assert(ndisc); - - log_ndisc("Invoking callback for '%c'.", event); - - if (!ndisc->callback) - return; - - ndisc->callback(ndisc, event, rt, ndisc->userdata); -} - -_public_ int sd_ndisc_set_callback( - sd_ndisc *nd, - sd_ndisc_callback_t callback, - void *userdata) { - - assert_return(nd, -EINVAL); - - nd->callback = callback; - nd->userdata = userdata; - - return 0; -} - -_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { - assert_return(nd, -EINVAL); - assert_return(ifindex > 0, -EINVAL); - assert_return(nd->fd < 0, -EBUSY); - - nd->ifindex = ifindex; - return 0; -} - -_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { - assert_return(nd, -EINVAL); - - if (mac_addr) - nd->mac_addr = *mac_addr; - else - zero(nd->mac_addr); - - return 0; -} - -_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { - int r; - - assert_return(nd, -EINVAL); - assert_return(nd->fd < 0, -EBUSY); - assert_return(!nd->event, -EBUSY); - - if (event) - nd->event = sd_event_ref(event); - else { - r = sd_event_default(&nd->event); - if (r < 0) - return 0; - } - - nd->event_priority = priority; - - return 0; -} - -_public_ int sd_ndisc_detach_event(sd_ndisc *nd) { - - assert_return(nd, -EINVAL); - assert_return(nd->fd < 0, -EBUSY); - - nd->event = sd_event_unref(nd->event); - return 0; -} - -_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) { - assert_return(nd, NULL); - - return nd->event; -} - -_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) { - - if (!nd) - return NULL; - - assert(nd->n_ref > 0); - nd->n_ref++; - - return nd; -} - -static int ndisc_reset(sd_ndisc *nd) { - assert(nd); - - nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); - nd->recv_event_source = sd_event_source_unref(nd->recv_event_source); - nd->fd = safe_close(nd->fd); - - return 0; -} - -_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) { - - if (!nd) - return NULL; - - assert(nd->n_ref > 0); - nd->n_ref--; - - if (nd->n_ref > 0) - return NULL; - - ndisc_reset(nd); - sd_ndisc_detach_event(nd); - return mfree(nd); -} - -_public_ int sd_ndisc_new(sd_ndisc **ret) { - _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; - - assert_return(ret, -EINVAL); - - nd = new0(sd_ndisc, 1); - if (!nd) - return -ENOMEM; - - nd->n_ref = 1; - nd->fd = -1; - - *ret = nd; - nd = NULL; - - return 0; -} - -_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) { - assert_return(nd, -EINVAL); - assert_return(mtu, -EINVAL); - - if (nd->mtu == 0) - return -ENODATA; - - *mtu = nd->mtu; - return 0; -} - -_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) { - assert_return(nd, -EINVAL); - assert_return(ret, -EINVAL); - - if (nd->hop_limit == 0) - return -ENODATA; - - *ret = nd->hop_limit; - return 0; -} - -static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { - int r; - - assert(nd); - assert(rt); - - r = ndisc_router_parse(rt); - if (r == -EBADMSG) /* Bad packet */ - return 0; - if (r < 0) - return 0; - - /* Update global variables we keep */ - if (rt->mtu > 0) - nd->mtu = rt->mtu; - if (rt->hop_limit > 0) - nd->hop_limit = rt->hop_limit; - - log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec", - rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none", - rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium", - rt->lifetime); - - ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); - return 0; -} - -static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { - _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; - sd_ndisc *nd = userdata; - union { - struct cmsghdr cmsghdr; - uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */ - CMSG_SPACE(sizeof(struct timeval))]; - } control = {}; - struct iovec iov = {}; - union sockaddr_union sa = {}; - struct msghdr msg = { - .msg_name = &sa.sa, - .msg_namelen = sizeof(sa), - .msg_iov = &iov, - .msg_iovlen = 1, - .msg_control = &control, - .msg_controllen = sizeof(control), - }; - struct cmsghdr *cmsg; - ssize_t len, buflen; - - assert(s); - assert(nd); - assert(nd->event); - - buflen = next_datagram_size_fd(fd); - if (buflen < 0) - return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m"); - - rt = ndisc_router_new(buflen); - if (!rt) - return -ENOMEM; - - iov.iov_base = NDISC_ROUTER_RAW(rt); - iov.iov_len = rt->raw_size; - - len = recvmsg(fd, &msg, MSG_DONTWAIT); - if (len < 0) { - if (errno == EAGAIN || errno == EINTR) - return 0; - - return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m"); - } - - if ((size_t) len != rt->raw_size) { - log_ndisc("Packet size mismatch."); - return -EINVAL; - } - - if (msg.msg_namelen == sizeof(struct sockaddr_in6) && - sa.in6.sin6_family == AF_INET6) { - - if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) { - _cleanup_free_ char *addr = NULL; - - (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr); - log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr)); - return 0; - } - - rt->address = sa.in6.sin6_addr; - - } else if (msg.msg_namelen > 0) { - log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen); - return -EINVAL; - } - - /* namelen == 0 only happens when running the test-suite over a socketpair */ - - assert(!(msg.msg_flags & MSG_CTRUNC)); - assert(!(msg.msg_flags & MSG_TRUNC)); - - CMSG_FOREACH(cmsg, &msg) { - if (cmsg->cmsg_level == SOL_IPV6 && - cmsg->cmsg_type == IPV6_HOPLIMIT && - cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { - int hops = *(int*) CMSG_DATA(cmsg); - - if (hops != 255) { - log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops); - return 0; - } - } - - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SO_TIMESTAMP && - cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) - triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg))); - } - - if (!triple_timestamp_is_set(&rt->timestamp)) - triple_timestamp_get(&rt->timestamp); - - nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); - - return ndisc_handle_datagram(nd, rt); -} - -static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { - sd_ndisc *nd = userdata; - usec_t time_now, next_timeout; - int r; - - assert(s); - assert(nd); - assert(nd->event); - - if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) { - nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); - ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); - return 0; - } - - r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); - if (r < 0) { - log_ndisc_errno(r, "Error sending Router Solicitation: %m"); - goto fail; - } - - log_ndisc("Sent Router Solicitation"); - nd->nd_sent++; - - assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); - next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL; - - r = sd_event_source_set_time(nd->timeout_event_source, next_timeout); - if (r < 0) { - log_ndisc_errno(r, "Error updating timer: %m"); - goto fail; - } - - r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT); - if (r < 0) { - log_ndisc_errno(r, "Error reenabling timer: %m"); - goto fail; - } - - return 0; - -fail: - sd_ndisc_stop(nd); - return 0; -} - -_public_ int sd_ndisc_stop(sd_ndisc *nd) { - assert_return(nd, -EINVAL); - - if (nd->fd < 0) - return 0; - - log_ndisc("Stopping IPv6 Router Solicitation client"); - - ndisc_reset(nd); - return 1; -} - -_public_ int sd_ndisc_start(sd_ndisc *nd) { - int r; - - assert_return(nd, -EINVAL); - assert_return(nd->event, -EINVAL); - assert_return(nd->ifindex > 0, -EINVAL); - - if (nd->fd >= 0) - return 0; - - assert(!nd->recv_event_source); - assert(!nd->timeout_event_source); - - nd->fd = icmp6_bind_router_solicitation(nd->ifindex); - if (nd->fd < 0) - return nd->fd; - - r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message"); - - r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd); - if (r < 0) - goto fail; - - r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority); - if (r < 0) - goto fail; - - (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout"); - - log_ndisc("Started IPv6 Router Solicitation client"); - return 1; - -fail: - ndisc_reset(nd); - return r; -} diff --git a/src/libsystemd-network/src/GNUmakefile b/src/libsystemd-network/src/GNUmakefile new file mode 120000 index 0000000000..95e5924740 --- /dev/null +++ b/src/libsystemd-network/src/GNUmakefile @@ -0,0 +1 @@ +../../../GNUmakefile \ No newline at end of file diff --git a/src/libsystemd-network/src/Makefile b/src/libsystemd-network/src/Makefile new file mode 100644 index 0000000000..c636e3cab1 --- /dev/null +++ b/src/libsystemd-network/src/Makefile @@ -0,0 +1,83 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +noinst_LTLIBRARIES += \ + libsystemd-network.la + +libsystemd_network_la_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/ndisc-internal.h \ + src/libsystemd-network/ndisc-router.h \ + src/libsystemd-network/ndisc-router.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-internal.h \ + src/libsystemd-network/lldp-network.h \ + src/libsystemd-network/lldp-network.c \ + src/libsystemd-network/lldp-neighbor.h \ + src/libsystemd-network/lldp-neighbor.c \ + src/libsystemd-network/sd-lldp.c + +libsystemd_network_la_LIBADD = \ + $(KMOD_LIBS) + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd-network/src/arp-util.c b/src/libsystemd-network/src/arp-util.c new file mode 100644 index 0000000000..2edb97be32 --- /dev/null +++ b/src/libsystemd-network/src/arp-util.c @@ -0,0 +1,155 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/arp-util.h" + +int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) { + struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ + BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Hardware Address must be different from our own */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((uint32_t *) eth_mac))), /* A <- 4 bytes of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */ + BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((uint16_t *) (((char *) eth_mac) + 4)))), /* A <- remainder of client's MAC */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about*/ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */ + BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */ + BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */ + BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter + }; + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + _cleanup_close_ int s = -1; + int r; + + assert(ifindex > 0); + + s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(s, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + r = s; + s = -1; + + return r; +} + +static int arp_send_packet(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha, + bool announce) { + union sockaddr_union link = { + .ll.sll_family = AF_PACKET, + .ll.sll_protocol = htobe16(ETH_P_ARP), + .ll.sll_ifindex = ifindex, + .ll.sll_halen = ETH_ALEN, + .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }, + }; + struct ether_arp arp = { + .ea_hdr.ar_hrd = htobe16(ARPHRD_ETHER), /* HTYPE */ + .ea_hdr.ar_pro = htobe16(ETHERTYPE_IP), /* PTYPE */ + .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */ + .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */ + .ea_hdr.ar_op = htobe16(ARPOP_REQUEST), /* REQUEST */ + }; + int r; + + assert(fd >= 0); + assert(pa != 0); + assert(ha); + + memcpy(&arp.arp_sha, ha, ETH_ALEN); + memcpy(&arp.arp_tpa, &pa, sizeof(pa)); + + if (announce) + memcpy(&arp.arp_spa, &pa, sizeof(pa)); + + r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll)); + if (r < 0) + return -errno; + + return 0; +} + +int arp_send_probe(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, false); +} + +int arp_send_announcement(int fd, int ifindex, + be32_t pa, const struct ether_addr *ha) { + return arp_send_packet(fd, ifindex, pa, ha, true); +} diff --git a/src/libsystemd-network/src/dhcp-identifier.c b/src/libsystemd-network/src/dhcp-identifier.c new file mode 100644 index 0000000000..afe0b9a404 --- /dev/null +++ b/src/libsystemd-network/src/dhcp-identifier.c @@ -0,0 +1,129 @@ +/*** + This file is part of systemd. + + 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 . +***/ + +#include +#include + +#include "systemd-basic/siphash24.h" +#include "systemd-basic/sparse-endian.h" +#include "systemd-basic/virt.h" +#include "systemd-network/dhcp-identifier.h" +#include "systemd-network/dhcp6-protocol.h" +#include "systemd-network/network-internal.h" +#include "systemd-shared/udev-util.h" + +#define SYSTEMD_PEN 43793 +#define HASH_KEY SD_ID128_MAKE(80,11,8c,c2,fe,4a,03,ee,3e,d6,0c,6f,36,39,14,09) + +int dhcp_validate_duid_len(uint16_t duid_type, size_t duid_len) { + struct duid d; + + assert_cc(sizeof(d.raw) >= MAX_DUID_LEN); + if (duid_len > MAX_DUID_LEN) + return -EINVAL; + + switch (duid_type) { + case DUID_TYPE_LLT: + if (duid_len <= sizeof(d.llt)) + return -EINVAL; + break; + case DUID_TYPE_EN: + if (duid_len != sizeof(d.en)) + return -EINVAL; + break; + case DUID_TYPE_LL: + if (duid_len <= sizeof(d.ll)) + return -EINVAL; + break; + case DUID_TYPE_UUID: + if (duid_len != sizeof(d.uuid)) + return -EINVAL; + break; + default: + /* accept unknown type in order to be forward compatible */ + break; + } + return 0; +} + +int dhcp_identifier_set_duid_en(struct duid *duid, size_t *len) { + sd_id128_t machine_id; + uint64_t hash; + int r; + + assert(duid); + assert(len); + + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return r; + + unaligned_write_be16(&duid->type, DUID_TYPE_EN); + unaligned_write_be32(&duid->en.pen, SYSTEMD_PEN); + + *len = sizeof(duid->type) + sizeof(duid->en); + + /* a bit of snake-oil perhaps, but no need to expose the machine-id + directly; 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/src/dhcp-network.c b/src/libsystemd-network/src/dhcp-network.c new file mode 100644 index 0000000000..2cdadee730 --- /dev/null +++ b/src/libsystemd-network/src/dhcp-network.c @@ -0,0 +1,236 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-network/dhcp-internal.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 = htobe16(ETH_P_IP); + link->ll.sll_ifindex = ifindex; + link->ll.sll_hatype = htobe16(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/src/dhcp-option.c b/src/libsystemd-network/src/dhcp-option.c new file mode 100644 index 0000000000..a848ed2841 --- /dev/null +++ b/src/libsystemd-network/src/dhcp-option.c @@ -0,0 +1,262 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/utf8.h" +#include "systemd-network/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; + + memcpy_safe(&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_callback_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_callback_t cb, void *userdata, char **_error_message) { + _cleanup_free_ char *error_message = NULL; + uint8_t overload = 0; + uint8_t message_type = 0; + int r; + + 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/src/dhcp-packet.c b/src/libsystemd-network/src/dhcp-packet.c new file mode 100644 index 0000000000..cdb54caedc --- /dev/null +++ b/src/libsystemd-network/src/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 . +***/ + +#include +#include +#include +#include + +#include "systemd-network/dhcp-internal.h" +#include "systemd-network/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/src/dhcp6-network.c b/src/libsystemd-network/src/dhcp6-network.c new file mode 100644 index 0000000000..469e3ddfdf --- /dev/null +++ b/src/libsystemd-network/src/dhcp6-network.c @@ -0,0 +1,92 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-network/dhcp6-internal.h" +#include "systemd-network/dhcp6-protocol.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/src/dhcp6-option.c b/src/libsystemd-network/src/dhcp6-option.c new file mode 100644 index 0000000000..ecbf201661 --- /dev/null +++ b/src/libsystemd-network/src/dhcp6-option.c @@ -0,0 +1,412 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/sparse-endian.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/unaligned.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp6-internal.h" +#include "systemd-network/dhcp6-protocol.h" +#include "systemd-network/sd-dhcp6-client.h" +#include "systemd-shared/dns-domain.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; + + memcpy_safe(*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/src/icmp6-util.c b/src/libsystemd-network/src/icmp6-util.c new file mode 100644 index 0000000000..4280d32e3d --- /dev/null +++ b/src/libsystemd-network/src/icmp6-util.c @@ -0,0 +1,142 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-network/icmp6-util.h" + +#define IN6ADDR_ALL_ROUTERS_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 } } } + +#define IN6ADDR_ALL_NODES_MULTICAST_INIT \ + { { { 0xff, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } } } + +int icmp6_bind_router_solicitation(int index) { + struct icmp6_filter filter = { }; + struct ipv6_mreq mreq = { + .ipv6mr_multiaddr = IN6ADDR_ALL_NODES_MULTICAST_INIT, + .ipv6mr_interface = index, + }; + _cleanup_close_ int s = -1; + char ifname[IF_NAMESIZE] = ""; + static const int zero = 0, one = 1, hops = 255; + int r; + + s = socket(AF_INET6, SOCK_RAW | SOCK_CLOEXEC | SOCK_NONBLOCK, IPPROTO_ICMPV6); + if (s < 0) + return -errno; + + ICMP6_FILTER_SETBLOCKALL(&filter); + ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); + r = setsockopt(s, IPPROTO_ICMPV6, ICMP6_FILTER, &filter, sizeof(filter)); + if (r < 0) + return -errno; + + /* RFC 3315, section 6.7, bullet point 2 may indicate that an + IPV6_PKTINFO socket option also applies for ICMPv6 multicast. + Empirical experiments indicates otherwise and therefore an + IPV6_MULTICAST_IF socket option is used here instead */ + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &zero, sizeof(zero)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof(hops)); + if (r < 0) + return -errno; + + r = setsockopt(s, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mreq, sizeof(mreq)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_IPV6, IPV6_RECVHOPLIMIT, &one, sizeof(one)); + if (r < 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_TIMESTAMP, &one, sizeof(one)); + if (r < 0) + return -errno; + + if (if_indextoname(index, ifname) == 0) + return -errno; + + r = setsockopt(s, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen(ifname)); + if (r < 0) + return -errno; + + r = s; + s = -1; + return r; +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + struct sockaddr_in6 dst = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ALL_ROUTERS_MULTICAST_INIT, + }; + struct { + struct nd_router_solicit rs; + struct nd_opt_hdr rs_opt; + struct ether_addr rs_opt_mac; + } _packed_ rs = { + .rs.nd_rs_type = ND_ROUTER_SOLICIT, + .rs_opt.nd_opt_type = ND_OPT_SOURCE_LINKADDR, + .rs_opt.nd_opt_len = 1, + }; + struct iovec iov = { + .iov_base = &rs, + .iov_len = sizeof(rs), + }; + struct msghdr msg = { + .msg_name = &dst, + .msg_namelen = sizeof(dst), + .msg_iov = &iov, + .msg_iovlen = 1, + }; + int r; + + assert(s >= 0); + assert(ether_addr); + + rs.rs_opt_mac = *ether_addr; + + r = sendmsg(s, &msg, 0); + if (r < 0) + return -errno; + + return 0; +} diff --git a/src/libsystemd-network/src/lldp-neighbor.c b/src/libsystemd-network/src/lldp-neighbor.c new file mode 100644 index 0000000000..6509b1479d --- /dev/null +++ b/src/libsystemd-network/src/lldp-neighbor.c @@ -0,0 +1,813 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/escape.h" +#include "systemd-basic/ether-addr-util.h" +#include "systemd-basic/hexdecoct.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/unaligned.h" +#include "systemd-network/lldp-internal.h" +#include "systemd-network/lldp-neighbor.h" + +static void lldp_neighbor_id_hash_func(const void *p, struct siphash *state) { + const LLDPNeighborID *id = p; + + siphash24_compress(id->chassis_id, id->chassis_id_size, state); + siphash24_compress(&id->chassis_id_size, sizeof(id->chassis_id_size), state); + siphash24_compress(id->port_id, id->port_id_size, state); + siphash24_compress(&id->port_id_size, sizeof(id->port_id_size), state); +} + +static int lldp_neighbor_id_compare_func(const void *a, const void *b) { + const LLDPNeighborID *x = a, *y = b; + int r; + + r = memcmp(x->chassis_id, y->chassis_id, MIN(x->chassis_id_size, y->chassis_id_size)); + if (r != 0) + return r; + + if (x->chassis_id_size < y->chassis_id_size) + return -1; + + if (x->chassis_id_size > y->chassis_id_size) + return 1; + + r = memcmp(x->port_id, y->port_id, MIN(x->port_id_size, y->port_id_size)); + if (r != 0) + return r; + + if (x->port_id_size < y->port_id_size) + return -1; + if (x->port_id_size > y->port_id_size) + return 1; + + return 0; +} + +const struct hash_ops lldp_neighbor_id_hash_ops = { + .hash = lldp_neighbor_id_hash_func, + .compare = lldp_neighbor_id_compare_func +}; + +int lldp_neighbor_prioq_compare_func(const void *a, const void *b) { + const sd_lldp_neighbor *x = a, *y = b; + + if (x->until < y->until) + return -1; + + if (x->until > y->until) + return 1; + + return 0; +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_ref(sd_lldp_neighbor *n) { + if (!n) + return NULL; + + assert(n->n_ref > 0 || n->lldp); + n->n_ref++; + + return n; +} + +static void lldp_neighbor_free(sd_lldp_neighbor *n) { + assert(n); + + free(n->id.port_id); + free(n->id.chassis_id); + free(n->port_description); + free(n->system_name); + free(n->system_description); + free(n->chassis_id_as_string); + free(n->port_id_as_string); + free(n); +} + +_public_ sd_lldp_neighbor *sd_lldp_neighbor_unref(sd_lldp_neighbor *n) { + + /* Drops one reference from the neighbor. Note that the object is not freed unless it is already unlinked from + * the sd_lldp object. */ + + if (!n) + return NULL; + + assert(n->n_ref > 0); + n->n_ref--; + + if (n->n_ref <= 0 && !n->lldp) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_unlink(sd_lldp_neighbor *n) { + + /* Removes the neighbor object from the LLDP object, and frees it if it also has no other reference. */ + + if (!n) + return NULL; + + if (!n->lldp) + return NULL; + + assert_se(hashmap_remove(n->lldp->neighbor_by_id, &n->id) == n); + assert_se(prioq_remove(n->lldp->neighbor_by_expiry, n, &n->prioq_idx) >= 0); + + n->lldp = NULL; + + if (n->n_ref <= 0) + lldp_neighbor_free(n); + + return NULL; +} + +sd_lldp_neighbor *lldp_neighbor_new(size_t raw_size) { + sd_lldp_neighbor *n; + + n = malloc0(ALIGN(sizeof(sd_lldp_neighbor)) + raw_size); + if (!n) + return NULL; + + n->raw_size = raw_size; + n->n_ref = 1; + + return n; +} + +static int parse_string(char **s, const void *q, size_t n) { + const char *p = q; + char *k; + + assert(s); + assert(p || n == 0); + + if (*s) { + log_lldp("Found duplicate string, ignoring field."); + return 0; + } + + /* Strip trailing NULs, just to be nice */ + while (n > 0 && p[n-1] == 0) + n--; + + if (n <= 0) /* Ignore empty strings */ + return 0; + + /* Look for inner NULs */ + if (memchr(p, 0, n)) { + log_lldp("Found inner NUL in string, ignoring field."); + return 0; + } + + /* Let's escape weird chars, for security reasons */ + k = cescape_length(p, n); + if (!k) + return -ENOMEM; + + free(*s); + *s = k; + + return 1; +} + +int lldp_neighbor_parse(sd_lldp_neighbor *n) { + struct ether_header h; + const uint8_t *p; + size_t left; + int r; + + assert(n); + + if (n->raw_size < sizeof(struct ether_header)) { + log_lldp("Received truncated packet, ignoring."); + return -EBADMSG; + } + + memcpy(&h, LLDP_NEIGHBOR_RAW(n), sizeof(h)); + + if (h.ether_type != htobe16(ETHERTYPE_LLDP)) { + log_lldp("Received packet with wrong type, ignoring."); + return -EBADMSG; + } + + if (h.ether_dhost[0] != 0x01 || + h.ether_dhost[1] != 0x80 || + h.ether_dhost[2] != 0xc2 || + h.ether_dhost[3] != 0x00 || + h.ether_dhost[4] != 0x00 || + !IN_SET(h.ether_dhost[5], 0x00, 0x03, 0x0e)) { + log_lldp("Received packet with wrong destination address, ignoring."); + return -EBADMSG; + } + + memcpy(&n->source_address, h.ether_shost, sizeof(struct ether_addr)); + memcpy(&n->destination_address, h.ether_dhost, sizeof(struct ether_addr)); + + p = (const uint8_t*) LLDP_NEIGHBOR_RAW(n) + sizeof(struct ether_header); + left = n->raw_size - sizeof(struct ether_header); + + for (;;) { + uint8_t type; + uint16_t length; + + if (left < 2) { + log_lldp("TLV lacks header, ignoring."); + return -EBADMSG; + } + + type = p[0] >> 1; + length = p[1] + (((uint16_t) (p[0] & 1)) << 8); + p += 2, left -= 2; + + if (left < length) { + log_lldp("TLV truncated, ignoring datagram."); + return -EBADMSG; + } + + switch (type) { + + case SD_LLDP_TYPE_END: + if (length != 0) { + log_lldp("End marker TLV not zero-sized, ignoring datagram."); + return -EBADMSG; + } + if (left != 0) { + log_lldp("Trailing garbage in datagram, ignoring datagram."); + return -EBADMSG; + } + + goto end_marker; + + case SD_LLDP_TYPE_CHASSIS_ID: + if (length < 2 || length > 256) { /* includes the chassis subtype, hence one extra byte */ + log_lldp("Chassis ID field size out of range, ignoring datagram."); + return -EBADMSG; + } + if (n->id.chassis_id) { + log_lldp("Duplicate chassis ID field, ignoring datagram."); + return -EBADMSG; + } + + n->id.chassis_id = memdup(p, length); + if (!n->id.chassis_id) + return -ENOMEM; + + n->id.chassis_id_size = length; + break; + + case SD_LLDP_TYPE_PORT_ID: + if (length < 2 || length > 256) { /* includes the port subtype, hence one extra byte */ + log_lldp("Port ID field size out of range, ignoring datagram."); + return -EBADMSG; + } + if (n->id.port_id) { + log_lldp("Duplicate port ID field, ignoring datagram."); + return -EBADMSG; + } + + n->id.port_id = memdup(p, length); + if (!n->id.port_id) + return -ENOMEM; + + n->id.port_id_size = length; + break; + + case SD_LLDP_TYPE_TTL: + if (length != 2) { + log_lldp("TTL field has wrong size, ignoring datagram."); + return -EBADMSG; + } + + if (n->has_ttl) { + log_lldp("Duplicate TTL field, ignoring datagram."); + return -EBADMSG; + } + + n->ttl = unaligned_read_be16(p); + n->has_ttl = true; + break; + + case SD_LLDP_TYPE_PORT_DESCRIPTION: + r = parse_string(&n->port_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_NAME: + r = parse_string(&n->system_name, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_DESCRIPTION: + r = parse_string(&n->system_description, p, length); + if (r < 0) + return r; + break; + + case SD_LLDP_TYPE_SYSTEM_CAPABILITIES: + if (length != 4) + log_lldp("System capabilities field has wrong size, ignoring."); + else { + n->system_capabilities = unaligned_read_be16(p); + n->enabled_capabilities = unaligned_read_be16(p + 2); + n->has_capabilities = true; + } + + break; + + case SD_LLDP_TYPE_PRIVATE: + if (length < 4) + log_lldp("Found private TLV that is too short, ignoring."); + + break; + } + + + p += length, left -= length; + } + +end_marker: + if (!n->id.chassis_id || !n->id.port_id || !n->has_ttl) { + log_lldp("One or more mandatory TLV missing in datagram. Ignoring."); + return -EBADMSG; + + } + + n->rindex = sizeof(struct ether_header); + + return 0; +} + +void lldp_neighbor_start_ttl(sd_lldp_neighbor *n) { + assert(n); + + if (n->ttl > 0) { + usec_t base; + + /* Use the packet's timestamp if there is one known */ + base = triple_timestamp_by_clock(&n->timestamp, clock_boottime_or_monotonic()); + if (base <= 0 || base == USEC_INFINITY) + base = now(clock_boottime_or_monotonic()); /* Otherwise, take the current time */ + + n->until = usec_add(base, n->ttl * USEC_PER_SEC); + } else + n->until = 0; + + if (n->lldp) + prioq_reshuffle(n->lldp->neighbor_by_expiry, n, &n->prioq_idx); +} + +bool lldp_neighbor_equal(const sd_lldp_neighbor *a, const sd_lldp_neighbor *b) { + if (a == b) + return true; + + if (!a || !b) + return false; + + if (a->raw_size != b->raw_size) + return false; + + return memcmp(LLDP_NEIGHBOR_RAW(a), LLDP_NEIGHBOR_RAW(b), a->raw_size) == 0; +} + +_public_ int sd_lldp_neighbor_get_source_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->source_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_destination_address(sd_lldp_neighbor *n, struct ether_addr* address) { + assert_return(n, -EINVAL); + assert_return(address, -EINVAL); + + *address = n->destination_address; + return 0; +} + +_public_ int sd_lldp_neighbor_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + *ret = LLDP_NEIGHBOR_RAW(n); + *size = n->raw_size; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_chassis_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.chassis_id_size > 0); + + *type = *(uint8_t*) n->id.chassis_id; + *ret = (uint8_t*) n->id.chassis_id + 1; + *size = n->id.chassis_id_size - 1; + + return 0; +} + +static int format_mac_address(const void *data, size_t sz, char **ret) { + struct ether_addr a; + char *k; + + assert(data || sz <= 0); + + if (sz != 7) + return 0; + + memcpy(&a, (uint8_t*) data + 1, sizeof(a)); + + k = new(char, ETHER_ADDR_TO_STRING_MAX); + if (!k) + return -ENOMEM; + + *ret = ether_addr_to_string(&a, k); + return 1; +} + +static int format_network_address(const void *data, size_t sz, char **ret) { + union in_addr_union a; + int family, r; + + if (sz == 6 && ((uint8_t*) data)[1] == 1) { + memcpy(&a.in, (uint8_t*) data + 2, sizeof(a.in)); + family = AF_INET; + } else if (sz == 18 && ((uint8_t*) data)[1] == 2) { + memcpy(&a.in6, (uint8_t*) data + 2, sizeof(a.in6)); + family = AF_INET6; + } else + return 0; + + r = in_addr_to_string(family, &a, ret); + if (r < 0) + return r; + return 1; +} + +_public_ int sd_lldp_neighbor_get_chassis_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->chassis_id_as_string) { + *ret = n->chassis_id_as_string; + return 0; + } + + assert(n->id.chassis_id_size > 0); + + switch (*(uint8_t*) n->id.chassis_id) { + + case SD_LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.chassis_id + 1, n->id.chassis_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.chassis_id, n->id.chassis_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.chassis_id, n->id.chassis_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->chassis_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id(sd_lldp_neighbor *n, uint8_t *type, const void **ret, size_t *size) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + assert(n->id.port_id_size > 0); + + *type = *(uint8_t*) n->id.port_id; + *ret = (uint8_t*) n->id.port_id + 1; + *size = n->id.port_id_size - 1; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_id_as_string(sd_lldp_neighbor *n, const char **ret) { + char *k; + int r; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (n->port_id_as_string) { + *ret = n->port_id_as_string; + return 0; + } + + assert(n->id.port_id_size > 0); + + switch (*(uint8_t*) n->id.port_id) { + + case SD_LLDP_PORT_SUBTYPE_INTERFACE_ALIAS: + case SD_LLDP_PORT_SUBTYPE_PORT_COMPONENT: + case SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME: + case SD_LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED: + k = cescape_length((char*) n->id.port_id + 1, n->id.port_id_size - 1); + if (!k) + return -ENOMEM; + + goto done; + + case SD_LLDP_PORT_SUBTYPE_MAC_ADDRESS: + r = format_mac_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + + case SD_LLDP_PORT_SUBTYPE_NETWORK_ADDRESS: + r = format_network_address(n->id.port_id, n->id.port_id_size, &k); + if (r < 0) + return r; + if (r > 0) + goto done; + + break; + } + + /* Generic fallback */ + k = hexmem(n->id.port_id, n->id.port_id_size); + if (!k) + return -ENOMEM; + +done: + *ret = n->port_id_as_string = k; + return 0; +} + +_public_ int sd_lldp_neighbor_get_ttl(sd_lldp_neighbor *n, uint16_t *ret_sec) { + assert_return(n, -EINVAL); + assert_return(ret_sec, -EINVAL); + + *ret_sec = n->ttl; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_name(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_name) + return -ENODATA; + + *ret = n->system_name; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->system_description) + return -ENODATA; + + *ret = n->system_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_port_description(sd_lldp_neighbor *n, const char **ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->port_description) + return -ENODATA; + + *ret = n->port_description; + return 0; +} + +_public_ int sd_lldp_neighbor_get_system_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->system_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_get_enabled_capabilities(sd_lldp_neighbor *n, uint16_t *ret) { + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + + if (!n->has_capabilities) + return -ENODATA; + + *ret = n->enabled_capabilities; + return 0; +} + +_public_ int sd_lldp_neighbor_from_raw(sd_lldp_neighbor **ret, const void *raw, size_t raw_size) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(raw || raw_size <= 0, -EINVAL); + + n = lldp_neighbor_new(raw_size); + if (!n) + return -ENOMEM; + + memcpy(LLDP_NEIGHBOR_RAW(n), raw, raw_size); + r = lldp_neighbor_parse(n); + if (r < 0) + return r; + + *ret = n; + n = NULL; + + return r; +} + +_public_ int sd_lldp_neighbor_tlv_rewind(sd_lldp_neighbor *n) { + assert_return(n, -EINVAL); + + assert(n->raw_size >= sizeof(struct ether_header)); + n->rindex = sizeof(struct ether_header); + + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_next(sd_lldp_neighbor *n) { + size_t length; + + assert_return(n, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) /* Truncated message */ + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + n->rindex += 2 + length; + return n->rindex < n->raw_size; +} + +_public_ int sd_lldp_neighbor_tlv_get_type(sd_lldp_neighbor *n, uint8_t *type) { + assert_return(n, -EINVAL); + assert_return(type, -EINVAL); + + if (n->rindex == n->raw_size) /* EOF */ + return -ESPIPE; + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + *type = LLDP_NEIGHBOR_TLV_TYPE(n); + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_type(sd_lldp_neighbor *n, uint8_t type) { + uint8_t k; + int r; + + assert_return(n, -EINVAL); + + r = sd_lldp_neighbor_tlv_get_type(n, &k); + if (r < 0) + return r; + + return type == k; +} + +_public_ int sd_lldp_neighbor_tlv_get_oui(sd_lldp_neighbor *n, uint8_t oui[3], uint8_t *subtype) { + const uint8_t *d; + size_t length; + int r; + + assert_return(n, -EINVAL); + assert_return(oui, -EINVAL); + assert_return(subtype, -EINVAL); + + r = sd_lldp_neighbor_tlv_is_type(n, SD_LLDP_TYPE_PRIVATE); + if (r < 0) + return r; + if (r == 0) + return -ENXIO; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (length < 4) + return -EBADMSG; + + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + d = LLDP_NEIGHBOR_TLV_DATA(n); + memcpy(oui, d, 3); + *subtype = d[3]; + + return 0; +} + +_public_ int sd_lldp_neighbor_tlv_is_oui(sd_lldp_neighbor *n, const uint8_t oui[3], uint8_t subtype) { + uint8_t k[3], st; + int r; + + r = sd_lldp_neighbor_tlv_get_oui(n, k, &st); + if (r == -ENXIO) + return 0; + if (r < 0) + return r; + + return memcmp(k, oui, 3) == 0 && st == subtype; +} + +_public_ int sd_lldp_neighbor_tlv_get_raw(sd_lldp_neighbor *n, const void **ret, size_t *size) { + size_t length; + + assert_return(n, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + /* Note that this returns the full TLV, including the TLV header */ + + if (n->rindex + 2 > n->raw_size) + return -EBADMSG; + + length = LLDP_NEIGHBOR_TLV_LENGTH(n); + if (n->rindex + 2 + length > n->raw_size) + return -EBADMSG; + + *ret = (uint8_t*) LLDP_NEIGHBOR_RAW(n) + n->rindex; + *size = length + 2; + + return 0; +} + +_public_ int sd_lldp_neighbor_get_timestamp(sd_lldp_neighbor *n, clockid_t clock, uint64_t *ret) { + assert_return(n, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&n->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&n->timestamp, clock); + return 0; +} diff --git a/src/libsystemd-network/src/lldp-network.c b/src/libsystemd-network/src/lldp-network.c new file mode 100644 index 0000000000..6c9ed81e69 --- /dev/null +++ b/src/libsystemd-network/src/lldp-network.c @@ -0,0 +1,78 @@ +/*** + 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 . +***/ + +#include + +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-network/lldp-network.h" + +int lldp_network_bind_raw_socket(int ifindex) { + + static const struct sock_filter filter[] = { + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ethhdr, h_dest)), /* A <- 4 bytes of destination MAC */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ethhdr, h_proto)), /* A <- protocol */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */ + BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */ + BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */ + }; + + static const struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = (struct sock_filter*) filter, + }; + + union sockaddr_union saddrll = { + .ll.sll_family = AF_PACKET, + .ll.sll_ifindex = ifindex, + }; + + _cleanup_close_ int fd = -1; + int r; + + assert(ifindex > 0); + + fd = socket(PF_PACKET, SOCK_RAW|SOCK_CLOEXEC|SOCK_NONBLOCK, + htobe16(ETHERTYPE_LLDP)); + if (fd < 0) + return -errno; + + r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + r = bind(fd, &saddrll.sa, sizeof(saddrll.ll)); + if (r < 0) + return -errno; + + r = fd; + fd = -1; + + return r; +} diff --git a/src/libsystemd-network/src/ndisc-router.c b/src/libsystemd-network/src/ndisc-router.c new file mode 100644 index 0000000000..a1051dadc5 --- /dev/null +++ b/src/libsystemd-network/src/ndisc-router.c @@ -0,0 +1,777 @@ +/*** + 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 . +***/ + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/hostname-util.h" +#include "systemd-basic/missing.h" +#include "systemd-basic/strv.h" +#include "systemd-network/ndisc-internal.h" +#include "systemd-network/ndisc-router.h" +#include "systemd-network/sd-ndisc.h" +#include "systemd-shared/dns-domain.h" + +_public_ sd_ndisc_router* sd_ndisc_router_ref(sd_ndisc_router *rt) { + if (!rt) + return NULL; + + assert(rt->n_ref > 0); + rt->n_ref++; + + return rt; +} + +_public_ sd_ndisc_router* sd_ndisc_router_unref(sd_ndisc_router *rt) { + if (!rt) + return NULL; + + assert(rt->n_ref > 0); + rt->n_ref--; + + if (rt->n_ref > 0) + return NULL; + + return mfree(rt); +} + +sd_ndisc_router *ndisc_router_new(size_t raw_size) { + sd_ndisc_router *rt; + + rt = malloc0(ALIGN(sizeof(sd_ndisc_router)) + raw_size); + if (!rt) + return NULL; + + rt->raw_size = raw_size; + rt->n_ref = 1; + + return rt; +} + +_public_ int sd_ndisc_router_from_raw(sd_ndisc_router **ret, const void *raw, size_t raw_size) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + int r; + + assert_return(ret, -EINVAL); + assert_return(raw || raw_size <= 0, -EINVAL); + + rt = ndisc_router_new(raw_size); + if (!rt) + return -ENOMEM; + + memcpy(NDISC_ROUTER_RAW(rt), raw, raw_size); + r = ndisc_router_parse(rt); + if (r < 0) + return r; + + *ret = rt; + rt = NULL; + + return r; +} + +_public_ int sd_ndisc_router_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + if (in6_addr_is_null(&rt->address)) + return -ENODATA; + + *ret_addr = rt->address; + return 0; +} + +_public_ int sd_ndisc_router_get_timestamp(sd_ndisc_router *rt, clockid_t clock, uint64_t *ret) { + assert_return(rt, -EINVAL); + assert_return(TRIPLE_TIMESTAMP_HAS_CLOCK(clock), -EOPNOTSUPP); + assert_return(clock_supported(clock), -EOPNOTSUPP); + assert_return(ret, -EINVAL); + + if (!triple_timestamp_is_set(&rt->timestamp)) + return -ENODATA; + + *ret = triple_timestamp_by_clock(&rt->timestamp, clock); + return 0; +} + +_public_ int sd_ndisc_router_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + *ret = NDISC_ROUTER_RAW(rt); + *size = rt->raw_size; + + return 0; +} + +int ndisc_router_parse(sd_ndisc_router *rt) { + struct nd_router_advert *a; + const uint8_t *p; + bool has_mtu = false, has_flag_extension = false; + size_t left; + + assert(rt); + + if (rt->raw_size < sizeof(struct nd_router_advert)) { + log_ndisc("Too small to be a router advertisement, ignoring."); + return -EBADMSG; + } + + /* Router advertisement packets are neatly aligned to 64bit boundaries, hence we can access them directly */ + a = NDISC_ROUTER_RAW(rt); + + if (a->nd_ra_type != ND_ROUTER_ADVERT) { + log_ndisc("Received ND packet that is not a router advertisement, ignoring."); + return -EBADMSG; + } + + if (a->nd_ra_code != 0) { + log_ndisc("Received ND packet with wrong RA code, ignoring."); + return -EBADMSG; + } + + rt->hop_limit = a->nd_ra_curhoplimit; + rt->flags = a->nd_ra_flags_reserved; /* the first 8bit */ + rt->lifetime = be16toh(a->nd_ra_router_lifetime); + + rt->preference = (rt->flags >> 3) & 3; + if (!IN_SET(rt->preference, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) + rt->preference = SD_NDISC_PREFERENCE_MEDIUM; + + p = (const uint8_t*) NDISC_ROUTER_RAW(rt) + sizeof(struct nd_router_advert); + left = rt->raw_size - sizeof(struct nd_router_advert); + + for (;;) { + uint8_t type; + size_t length; + + if (left == 0) + break; + + if (left < 2) { + log_ndisc("Option lacks header, ignoring datagram."); + return -EBADMSG; + } + + type = p[0]; + length = p[1] * 8; + + if (length == 0) { + log_ndisc("Zero-length option, ignoring datagram."); + return -EBADMSG; + } + if (left < length) { + log_ndisc("Option truncated, ignoring datagram."); + return -EBADMSG; + } + + switch (type) { + + case SD_NDISC_OPTION_PREFIX_INFORMATION: + + if (length != 4*8) { + log_ndisc("Prefix option of invalid size, ignoring datagram."); + return -EBADMSG; + } + + if (p[2] > 128) { + log_ndisc("Bad prefix length, ignoring datagram."); + return -EBADMSG; + } + + break; + + case SD_NDISC_OPTION_MTU: { + uint32_t m; + + if (has_mtu) { + log_ndisc("MTU option specified twice, ignoring."); + continue; + } + + if (length != 8) { + log_ndisc("MTU option of invalid size, ignoring datagram."); + return -EBADMSG; + } + + m = be32toh(*(uint32_t*) (p + 4)); + if (m >= IPV6_MIN_MTU) /* ignore invalidly small MTUs */ + rt->mtu = m; + + has_mtu = true; + break; + } + + case SD_NDISC_OPTION_ROUTE_INFORMATION: + if (length < 1*8 || length > 3*8) { + log_ndisc("Route information option of invalid size, ignoring datagram."); + return -EBADMSG; + } + + if (p[2] > 128) { + log_ndisc("Bad route prefix length, ignoring datagram."); + return -EBADMSG; + } + + break; + + case SD_NDISC_OPTION_RDNSS: + if (length < 3*8 || (length % (2*8)) != 1*8) { + log_ndisc("RDNSS option has invalid size."); + return -EBADMSG; + } + + break; + + case SD_NDISC_OPTION_FLAGS_EXTENSION: + + if (has_flag_extension) { + log_ndisc("Flags extension option specified twice, ignoring."); + continue; + } + + if (length < 1*8) { + log_ndisc("Flags extension option has invalid size."); + return -EBADMSG; + } + + /* Add in the additional flags bits */ + rt->flags |= + ((uint64_t) p[2] << 8) | + ((uint64_t) p[3] << 16) | + ((uint64_t) p[4] << 24) | + ((uint64_t) p[5] << 32) | + ((uint64_t) p[6] << 40) | + ((uint64_t) p[7] << 48); + + has_flag_extension = true; + break; + + case SD_NDISC_OPTION_DNSSL: + if (length < 2*8) { + log_ndisc("DNSSL option has invalid size."); + return -EBADMSG; + } + + break; + } + + p += length, left -= length; + } + + rt->rindex = sizeof(struct nd_router_advert); + return 0; +} + +_public_ int sd_ndisc_router_get_hop_limit(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->hop_limit; + return 0; +} + +_public_ int sd_ndisc_router_get_flags(sd_ndisc_router *rt, uint64_t *ret_flags) { + assert_return(rt, -EINVAL); + assert_return(ret_flags, -EINVAL); + + *ret_flags = rt->flags; + return 0; +} + +_public_ int sd_ndisc_router_get_lifetime(sd_ndisc_router *rt, uint16_t *ret_lifetime) { + assert_return(rt, -EINVAL); + assert_return(ret_lifetime, -EINVAL); + + *ret_lifetime = rt->lifetime; + return 0; +} + +_public_ int sd_ndisc_router_get_preference(sd_ndisc_router *rt, unsigned *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + *ret = rt->preference; + return 0; +} + +_public_ int sd_ndisc_router_get_mtu(sd_ndisc_router *rt, uint32_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + if (rt->mtu <= 0) + return -ENODATA; + + *ret = rt->mtu; + return 0; +} + +_public_ int sd_ndisc_router_option_rewind(sd_ndisc_router *rt) { + assert_return(rt, -EINVAL); + + assert(rt->raw_size >= sizeof(struct nd_router_advert)); + rt->rindex = sizeof(struct nd_router_advert); + + return rt->rindex < rt->raw_size; +} + +_public_ int sd_ndisc_router_option_next(sd_ndisc_router *rt) { + size_t length; + + assert_return(rt, -EINVAL); + + if (rt->rindex == rt->raw_size) /* EOF */ + return -ESPIPE; + + if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ + return -EBADMSG; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (rt->rindex + length > rt->raw_size) + return -EBADMSG; + + rt->rindex += length; + return rt->rindex < rt->raw_size; +} + +_public_ int sd_ndisc_router_option_get_type(sd_ndisc_router *rt, uint8_t *ret) { + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + if (rt->rindex == rt->raw_size) /* EOF */ + return -ESPIPE; + + if (rt->rindex + 2 > rt->raw_size) /* Truncated message */ + return -EBADMSG; + + *ret = NDISC_ROUTER_OPTION_TYPE(rt); + return 0; +} + +_public_ int sd_ndisc_router_option_is_type(sd_ndisc_router *rt, uint8_t type) { + uint8_t k; + int r; + + assert_return(rt, -EINVAL); + + r = sd_ndisc_router_option_get_type(rt, &k); + if (r < 0) + return r; + + return type == k; +} + +_public_ int sd_ndisc_router_option_get_raw(sd_ndisc_router *rt, const void **ret, size_t *size) { + size_t length; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + assert_return(size, -EINVAL); + + /* Note that this returns the full option, including the option header */ + + if (rt->rindex + 2 > rt->raw_size) + return -EBADMSG; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (rt->rindex + length > rt->raw_size) + return -EBADMSG; + + *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + *size = length; + + return 0; +} + +static int get_prefix_info(sd_ndisc_router *rt, struct nd_opt_prefix_info **ret) { + struct nd_opt_prefix_info *ri; + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_PREFIX_INFORMATION); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length != sizeof(struct nd_opt_prefix_info)) + return -EBADMSG; + + ri = (struct nd_opt_prefix_info*) ((uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex); + if (ri->nd_opt_pi_prefix_len > 128) + return -EBADMSG; + + *ret = ri; + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_valid_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + struct nd_opt_prefix_info *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &ri); + if (r < 0) + return r; + + *ret = be32toh(ri->nd_opt_pi_valid_time); + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_preferred_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + *ret = be32toh(pi->nd_opt_pi_preferred_time); + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_flags(sd_ndisc_router *rt, uint8_t *ret) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + *ret = pi->nd_opt_pi_flags_reserved; + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + *ret_addr = pi->nd_opt_pi_prefix; + return 0; +} + +_public_ int sd_ndisc_router_prefix_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { + struct nd_opt_prefix_info *pi; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_prefix_info(rt, &pi); + if (r < 0) + return r; + + if (pi->nd_opt_pi_prefix_len > 128) + return -EBADMSG; + + *ret = pi->nd_opt_pi_prefix_len; + return 0; +} + +static int get_route_info(sd_ndisc_router *rt, uint8_t **ret) { + uint8_t *ri; + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_ROUTE_INFORMATION); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length < 1*8 || length > 3*8) + return -EBADMSG; + + ri = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + + if (ri[2] > 128) + return -EBADMSG; + + *ret = ri; + return 0; +} + +_public_ int sd_ndisc_router_route_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + *ret = be32toh(*(uint32_t*) (ri + 4)); + return 0; +} + +_public_ int sd_ndisc_router_route_get_address(sd_ndisc_router *rt, struct in6_addr *ret_addr) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_addr, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + zero(*ret_addr); + memcpy(ret_addr, ri + 8, NDISC_ROUTER_OPTION_LENGTH(rt) - 8); + + return 0; +} + +_public_ int sd_ndisc_router_route_get_prefixlen(sd_ndisc_router *rt, unsigned *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + *ret = ri[2]; + return 0; +} + +_public_ int sd_ndisc_router_route_get_preference(sd_ndisc_router *rt, unsigned *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_route_info(rt, &ri); + if (r < 0) + return r; + + *ret = (ri[3] >> 3) & 3; + if (!IN_SET(*ret, SD_NDISC_PREFERENCE_LOW, SD_NDISC_PREFERENCE_HIGH)) + *ret = SD_NDISC_PREFERENCE_MEDIUM; + + return 0; +} + +static int get_rdnss_info(sd_ndisc_router *rt, uint8_t **ret) { + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_RDNSS); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length < 3*8 || (length % (2*8)) != 1*8) + return -EBADMSG; + + *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + return 0; +} + +_public_ int sd_ndisc_router_rdnss_get_addresses(sd_ndisc_router *rt, const struct in6_addr **ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_rdnss_info(rt, &ri); + if (r < 0) + return r; + + *ret = (const struct in6_addr*) (ri + 8); + return (NDISC_ROUTER_OPTION_LENGTH(rt) - 8) / 16; +} + +_public_ int sd_ndisc_router_rdnss_get_lifetime(sd_ndisc_router *rt, uint32_t *ret) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_rdnss_info(rt, &ri); + if (r < 0) + return r; + + *ret = be32toh(*(uint32_t*) (ri + 4)); + return 0; +} + +static int get_dnssl_info(sd_ndisc_router *rt, uint8_t **ret) { + size_t length; + int r; + + assert(rt); + assert(ret); + + r = sd_ndisc_router_option_is_type(rt, SD_NDISC_OPTION_DNSSL); + if (r < 0) + return r; + if (r == 0) + return -EMEDIUMTYPE; + + length = NDISC_ROUTER_OPTION_LENGTH(rt); + if (length < 2*8) + return -EBADMSG; + + *ret = (uint8_t*) NDISC_ROUTER_RAW(rt) + rt->rindex; + return 0; +} + +_public_ int sd_ndisc_router_dnssl_get_domains(sd_ndisc_router *rt, char ***ret) { + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *e = NULL; + size_t allocated = 0, n = 0, left; + uint8_t *ri, *p; + bool first = true; + int r; + unsigned k = 0; + + assert_return(rt, -EINVAL); + assert_return(ret, -EINVAL); + + r = get_dnssl_info(rt, &ri); + if (r < 0) + return r; + + p = ri + 8; + left = NDISC_ROUTER_OPTION_LENGTH(rt) - 8; + + for (;;) { + if (left == 0) { + + if (n > 0) /* Not properly NUL terminated */ + return -EBADMSG; + + break; + } + + if (*p == 0) { + /* Found NUL termination */ + + if (n > 0) { + _cleanup_free_ char *normalized = NULL; + + e[n] = 0; + r = dns_name_normalize(e, &normalized); + if (r < 0) + return r; + + /* Ignore the root domain name or "localhost" and friends */ + if (!is_localhost(normalized) && + !dns_name_is_root(normalized)) { + + if (strv_push(&l, normalized) < 0) + return -ENOMEM; + + normalized = NULL; + k++; + } + } + + n = 0; + first = true; + p++, left--; + continue; + } + + /* Check for compression (which is not allowed) */ + if (*p > 63) + return -EBADMSG; + + if (1U + *p + 1U > left) + return -EBADMSG; + + if (!GREEDY_REALLOC(e, allocated, n + !first + DNS_LABEL_ESCAPED_MAX + 1U)) + return -ENOMEM; + + if (first) + first = false; + else + e[n++] = '.'; + + r = dns_label_escape((char*) p+1, *p, e + n, DNS_LABEL_ESCAPED_MAX); + if (r < 0) + return r; + + n += r; + + left -= 1 + *p; + p += 1 + *p; + } + + if (strv_isempty(l)) { + *ret = NULL; + return 0; + } + + *ret = l; + l = NULL; + + return k; +} + +_public_ int sd_ndisc_router_dnssl_get_lifetime(sd_ndisc_router *rt, uint32_t *ret_sec) { + uint8_t *ri; + int r; + + assert_return(rt, -EINVAL); + assert_return(ret_sec, -EINVAL); + + r = get_dnssl_info(rt, &ri); + if (r < 0) + return r; + + *ret_sec = be32toh(*(uint32_t*) (ri + 4)); + return 0; +} diff --git a/src/libsystemd-network/src/network-internal.c b/src/libsystemd-network/src/network-internal.c new file mode 100644 index 0000000000..13b3bad81b --- /dev/null +++ b/src/libsystemd-network/src/network-internal.c @@ -0,0 +1,556 @@ +/*** + This file is part of systemd. + + Copyright (C) 2013 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 . +***/ + +#include +#include + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/ether-addr-util.h" +#include "systemd-basic/hexdecoct.h" +#include "systemd-basic/log.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/siphash24.h" +#include "systemd-basic/socket-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/utf8.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp-lease-internal.h" +#include "systemd-network/network-internal.h" +#include "systemd-network/sd-ndisc.h" +#include "systemd-shared/condition.h" +#include "systemd-shared/conf-parser.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) <= 0) + return false; + + if (match_virt && condition_test(match_virt) <= 0) + return false; + + if (match_kernel && condition_test(match_kernel) <= 0) + return false; + + if (match_arch && condition_test(match_arch) <= 0) + 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_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; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + for (;;) { + _cleanup_free_ char *word = NULL; + + r = extract_first_word(&rvalue, &word, NULL, 0); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Failed to parse interface name list: %s", rvalue); + return 0; + } + if (r == 0) + break; + + if (!ifname_valid(word)) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Interface name is not valid or too long, ignoring assignment: %s", rvalue); + return 0; + } + + r = strv_push(sv, word); + if (r < 0) + return log_oom(); + + word = NULL; + } + + 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; + const char *start; + size_t offset; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + n = new0(struct ether_addr, 1); + if (!n) + return log_oom(); + + start = rvalue + strspn(rvalue, WHITESPACE); + r = ether_addr_from_string(start, n, &offset); + + if (r || (start[offset + strspn(start + offset, WHITESPACE)] != '\0')) { + log_syntax(unit, LOG_ERR, filename, line, 0, "Not a valid MAC address, ignoring assignment: %s", rvalue); + free(n); + return 0; + } + + free(*hwaddr); + *hwaddr = n; + + return 0; +} + +int config_parse_iaid(const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + uint32_t iaid; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + r = safe_atou32(rvalue, &iaid); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, r, + "Unable to read IAID, ignoring assignment: %s", rvalue); + return 0; + } + + *((uint32_t *)data) = iaid; + + return 0; +} + +void serialize_in_addrs(FILE *f, const struct in_addr *addresses, size_t size) { + unsigned i; + + 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; + + assert(ret); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in_addr *new_addresses; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + new_addresses = realloc(addresses, (size + 1) * sizeof(struct in_addr)); + if (!new_addresses) + return -ENOMEM; + else + addresses = new_addresses; + + r = inet_pton(AF_INET, word, &(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++) { + char buffer[INET6_ADDRSTRLEN]; + + fputs(inet_ntop(AF_INET6, addresses+i, buffer, sizeof(buffer)), f); + + if (i < size - 1) + fputc(' ', f); + } +} + +int deserialize_in6_addrs(struct in6_addr **ret, const char *string) { + _cleanup_free_ struct in6_addr *addresses = NULL; + int size = 0; + + assert(ret); + assert(string); + + for (;;) { + _cleanup_free_ char *word = NULL; + struct in6_addr *new_addresses; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + new_addresses = realloc(addresses, (size + 1) * sizeof(struct in6_addr)); + if (!new_addresses) + return -ENOMEM; + else + addresses = new_addresses; + + r = inet_pton(AF_INET6, word, &(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; + + assert(ret); + assert(ret_size); + assert(ret_allocated); + assert(string); + + /* WORD FORMAT: dst_ip/dst_prefixlen,gw_ip */ + for (;;) { + _cleanup_free_ char *word = NULL; + char *tok, *tok_end; + unsigned n; + int r; + + r = extract_first_word(&string, &word, NULL, 0); + if (r < 0) + return r; + if (r == 0) + break; + + if (!GREEDY_REALLOC(routes, allocated, size + 1)) + return -ENOMEM; + + tok = word; + + /* 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/src/sd-dhcp-client.c b/src/libsystemd-network/src/sd-dhcp-client.c new file mode 100644 index 0000000000..fa6393a2e2 --- /dev/null +++ b/src/libsystemd-network/src/sd-dhcp-client.c @@ -0,0 +1,1904 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/async.h" +#include "systemd-basic/hostname-util.h" +#include "systemd-basic/random-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp-identifier.h" +#include "systemd-network/dhcp-internal.h" +#include "systemd-network/dhcp-lease-internal.h" +#include "systemd-network/dhcp-protocol.h" +#include "systemd-network/sd-dhcp-client.h" +#include "systemd-shared/dns-domain.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 ifindex; + 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) */ + be32_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_callback_t callback; + 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_callback_t cb, + void *userdata) { + + assert_return(client, -EINVAL); + + client->callback = 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_ifindex(sd_dhcp_client *client, int ifindex) { + + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED), -EBUSY); + assert_return(ifindex > 0, -EINVAL); + + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp_client_set_mac( + sd_dhcp_client *client, + const uint8_t *addr, + size_t addr_len, + uint16_t arp_type) { + + 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; +} + +/** + * Sets IAID and DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +int sd_dhcp_client_set_iaid_duid( + sd_dhcp_client *client, + uint32_t iaid, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + + DHCP_CLIENT_DONT_DESTROY(client); + int r; + size_t len; + + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid != NULL, -EINVAL); + + if (duid != NULL) { + r = dhcp_validate_duid_len(duid_type, duid_len); + if (r < 0) + return r; + } + + zero(client->client_id); + client->client_id.type = 255; + + /* If IAID is not configured, generate it. */ + if (iaid == 0) { + r = dhcp_identifier_set_iaid(client->ifindex, client->mac_addr, + client->mac_addr_len, + &client->client_id.ns.iaid); + if (r < 0) + return r; + } else + client->client_id.ns.iaid = htobe32(iaid); + + if (duid != NULL) { + client->client_id.ns.duid.type = htobe16(duid_type); + memcpy(&client->client_id.ns.duid.raw.data, duid, duid_len); + len = sizeof(client->client_id.ns.duid.type) + duid_len; + } else if (duid_type == DUID_TYPE_EN) { + r = dhcp_identifier_set_duid_en(&client->client_id.ns.duid, &len); + if (r < 0) + return r; + } else + return -EOPNOTSUPP; + + client->client_id_len = sizeof(client->client_id.type) + len + + sizeof(client->client_id.ns.iaid); + + if (!IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_STOPPED)) { + log_dhcp_client(client, "Configured IAID+DUID, restarting."); + client_stop(client, SD_DHCP_CLIENT_EVENT_STOP); + sd_dhcp_client_start(client); + } + + return 0; +} + +int sd_dhcp_client_set_hostname( + sd_dhcp_client *client, + const char *hostname) { + + char *new_hostname = NULL; + + assert_return(client, -EINVAL); + + if (!hostname_is_valid(hostname, false) && !dns_name_is_valid(hostname)) + return -EINVAL; + + if (streq_ptr(client->hostname, hostname)) + return 0; + + 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); + + if (client->state != DHCP_STATE_BOUND && + client->state != DHCP_STATE_RENEWING && + client->state != DHCP_STATE_REBINDING) + return -EADDRNOTAVAIL; + + if (ret) + *ret = client->lease; + + return 0; +} + +static void client_notify(sd_dhcp_client *client, int event) { + assert(client); + + if (client->callback) + client->callback(client, event, client->userdata); +} + +static int client_initialize(sd_dhcp_client *client) { + assert_return(client, -EINVAL); + + client->receive_message = sd_event_source_unref(client->receive_message); + + client->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 = NULL; + 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->ifindex, 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; + + assert(client); + + 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) { + assert_se(sd_event_now(client->event, clock_boottime_or_monotonic(), &usec) >= 0); + usec += client->start_delay; + } + + r = sd_event_add_time(client->event, + &client->timeout_resend, + clock_boottime_or_monotonic(), + 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->ifindex > 0, -EINVAL); + assert_return(client->fd < 0, -EBUSY); + assert_return(client->xid == 0, -EINVAL); + assert_return(IN_SET(client->state, DHCP_STATE_INIT, DHCP_STATE_INIT_REBOOT), -EBUSY); + + client->xid = random_u32(); + + r = dhcp_network_bind_raw_socket(client->ifindex, &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; + + assert(client); + + client->receive_message = sd_event_source_unref(client->receive_message); + client->fd = asynchronous_close(client->fd); + + client->state = DHCP_STATE_REBINDING; + client->attempt = 1; + + r = dhcp_network_bind_raw_socket(client->ifindex, &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 = {}; + 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 = recv(fd, message, buflen, 0); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_dhcp_client_errno(client, errno, "Could not receive message from UDP socket: %m"); + } + if ((size_t) len < sizeof(DHCPMessage)) { + log_dhcp_client(client, "Too small to be a DHCP message: ignoring"); + return 0; + } + + if (be32toh(message->magic) != DHCP_MAGIC_COOKIE) { + log_dhcp_client(client, "Not a DHCP message: ignoring"); + 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->ifindex); + + 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, int64_t 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) { + assert_return(client, 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); + return mfree(client); +} + +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->ifindex = -1; + client->fd = -1; + client->attempt = 1; + client->mtu = DHCP_DEFAULT_MIN_SIZE; + + client->req_opts_size = ELEMENTSOF(default_req_opts); + client->req_opts = memdup(default_req_opts, client->req_opts_size); + if (!client->req_opts) + return -ENOMEM; + + *ret = client; + client = NULL; + + return 0; +} diff --git a/src/libsystemd-network/src/sd-dhcp-lease.c b/src/libsystemd-network/src/sd-dhcp-lease.c new file mode 100644 index 0000000000..26f8a61cab --- /dev/null +++ b/src/libsystemd-network/src/sd-dhcp-lease.c @@ -0,0 +1,1175 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/fileio.h" +#include "systemd-basic/hexdecoct.h" +#include "systemd-basic/hostname-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/stdio-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/unaligned.h" +#include "systemd-network/dhcp-lease-internal.h" +#include "systemd-network/dhcp-protocol.h" +#include "systemd-network/network-internal.h" +#include "systemd-network/sd-dhcp-lease.h" +#include "systemd-shared/dns-domain.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); + return mfree(lease); +} + +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 = NULL; + + 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/src/sd-dhcp-server.c b/src/libsystemd-network/src/sd-dhcp-server.c new file mode 100644 index 0000000000..b92093ab05 --- /dev/null +++ b/src/libsystemd-network/src/sd-dhcp-server.c @@ -0,0 +1,1173 @@ +/*** + 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 . +***/ + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/siphash24.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/unaligned.h" +#include "systemd-network/dhcp-internal.h" +#include "systemd-network/dhcp-server-internal.h" +#include "systemd-network/sd-dhcp-server.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); + return mfree(server); +} + +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, int64_t 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 = htobe16(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, + uint16_t destination_port, + DHCPMessage *message, size_t len) { + union sockaddr_union dest = { + .in.sin_family = AF_INET, + .in.sin_port = htobe16(destination_port), + .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; + uint16_t destination_port = DHCP_PORT_CLIENT; + 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; + destination_port = DHCP_PORT_SERVER; + if (type == DHCP_NAK) + packet->dhcp.flags = htobe16(0x8000); + } else if (req->message->ciaddr && type != DHCP_NAK) + destination = req->message->ciaddr; + + if (destination != INADDR_ANY) + return dhcp_server_send_udp(server, destination, + destination_port, &packet->dhcp, + sizeof(DHCPMessage) + optoffset); + else if (requested_broadcast(req) || type == DHCP_NAK) + return dhcp_server_send_udp(server, INADDR_BROADCAST, + destination_port, &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; + + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, &server->address); + if (r < 0) + return r; + } + + r = dhcp_server_send_packet(server, req, packet, DHCP_OFFER, offset); + if (r < 0) + 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; + + if (server->emit_router) { + r = dhcp_option_append(&packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_ROUTER, 4, &server->address); + if (r < 0) + return r; + } + + if (server->n_dns > 0) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_DOMAIN_NAME_SERVER, + sizeof(struct in_addr) * server->n_dns, server->dns); + if (r < 0) + return r; + } + + if (server->n_ntp > 0) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_NTP_SERVER, + sizeof(struct in_addr) * server->n_ntp, server->ntp); + if (r < 0) + return r; + } + + if (server->timezone) { + r = dhcp_option_append( + &packet->dhcp, req->max_optlen, &offset, 0, + SD_DHCP_OPTION_NEW_TZDB_TIMEZONE, + strlen(server->timezone), server->timezone); + if (r < 0) + return r; + } + + r = dhcp_server_send_packet(server, req, packet, DHCP_ACK, offset); + if (r < 0) + 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, DHCP_PORT_CLIENT, + &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 = unaligned_read_be32(option); + + break; + case SD_DHCP_OPTION_REQUESTED_IP_ADDRESS: + if (len == 4) + memcpy(&req->requested_ip, option, sizeof(be32_t)); + + break; + case SD_DHCP_OPTION_SERVER_IDENTIFIER: + if (len == 4) + memcpy(&req->server_id, option, sizeof(be32_t)); + + 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 && unaligned_read_be16(option) >= sizeof(DHCPPacket)) + req->max_optlen = unaligned_read_be16(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; +} + +int sd_dhcp_server_set_emit_router(sd_dhcp_server *server, int enabled) { + assert_return(server, -EINVAL); + + if (enabled == server->emit_router) + return 0; + + server->emit_router = enabled; + + return 1; +} diff --git a/src/libsystemd-network/src/sd-dhcp6-client.c b/src/libsystemd-network/src/sd-dhcp6-client.c new file mode 100644 index 0000000000..d2c83e4043 --- /dev/null +++ b/src/libsystemd-network/src/sd-dhcp6-client.c @@ -0,0 +1,1333 @@ +/*** + 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 . +***/ + +#include +#include +#include + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/random-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-basic/string-table.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp-identifier.h" +#include "systemd-network/dhcp6-internal.h" +#include "systemd-network/dhcp6-lease-internal.h" +#include "systemd-network/dhcp6-protocol.h" +#include "systemd-network/network-internal.h" +#include "systemd-network/sd-dhcp6-client.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 ifindex; + 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_callback_t callback; + 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_callback_t cb, + void *userdata) { + + assert_return(client, -EINVAL); + + client->callback = cb; + client->userdata = userdata; + + return 0; +} + +int sd_dhcp6_client_set_ifindex(sd_dhcp6_client *client, int ifindex) { + + assert_return(client, -EINVAL); + assert_return(ifindex >= -1, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ifindex = ifindex; + return 0; +} + +int sd_dhcp6_client_set_local_address( + sd_dhcp6_client *client, + const struct in6_addr *local_address) { + + assert_return(client, -EINVAL); + assert_return(local_address, -EINVAL); + assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) local_address) > 0, -EINVAL); + + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->local_address = *local_address; + + return 0; +} + +int sd_dhcp6_client_set_mac( + sd_dhcp6_client *client, + const uint8_t *addr, size_t addr_len, + uint16_t arp_type) { + + 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); +} + +/** + * Sets DUID. If duid is non-null, the DUID is set to duid_type + duid + * without further modification. Otherwise, if duid_type is supported, DUID + * is set based on that type. Otherwise, an error is returned. + */ +int sd_dhcp6_client_set_duid( + sd_dhcp6_client *client, + uint16_t duid_type, + const void *duid, + size_t duid_len) { + + int r; + assert_return(client, -EINVAL); + assert_return(duid_len == 0 || duid != NULL, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + if (duid != NULL) { + r = dhcp_validate_duid_len(duid_type, duid_len); + if (r < 0) + return r; + } + + if (duid != NULL) { + client->duid.type = htobe16(duid_type); + memcpy(&client->duid.raw.data, duid, duid_len); + client->duid_len = sizeof(client->duid.type) + duid_len; + } else if (duid_type == DUID_TYPE_EN) { + r = dhcp_identifier_set_duid_en(&client->duid, &client->duid_len); + if (r < 0) + return r; + } else + return -EOPNOTSUPP; + + return 0; +} + +int sd_dhcp6_client_set_iaid(sd_dhcp6_client *client, uint32_t iaid) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->ia_na.id = htobe32(iaid); + + return 0; +} + +int sd_dhcp6_client_set_information_request(sd_dhcp6_client *client, int enabled) { + assert_return(client, -EINVAL); + assert_return(IN_SET(client->state, DHCP6_STATE_STOPPED), -EBUSY); + + client->information_request = enabled; + + return 0; +} + +int sd_dhcp6_client_get_information_request(sd_dhcp6_client *client, 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) { + assert(client); + + if (client->callback) + client->callback(client, event, client->userdata); +} + +static void client_set_lease(sd_dhcp6_client *client, sd_dhcp6_lease *lease) { + assert(client); + + if (client->lease) { + dhcp6_lease_clear_timers(&client->lease->ia); + sd_dhcp6_lease_unref(client->lease); + } + + client->lease = lease; +} + +static int client_reset(sd_dhcp6_client *client) { + assert(client); + + 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; + + assert(client); + + 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(s); + assert(client); + assert(client->lease); + + 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(s); + assert(client); + assert(client->lease); + + 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->ifindex, client->mac_addr, client->mac_addr_len, &client->ia_na.id); + if (r < 0) + return r; + + return 0; +} + +static int client_parse_message( + sd_dhcp6_client *client, + DHCP6Message *message, + size_t len, + sd_dhcp6_lease *lease) { + int r; + uint8_t *optval, *option, *id = NULL; + uint16_t optcode, status; + size_t optlen, id_len; + bool clientid = false; + be32_t iaid_lease; + + assert(client); + assert(message); + assert(len >= sizeof(DHCP6Message)); + assert(lease); + + option = (uint8_t *)message + sizeof(DHCP6Message); + len -= sizeof(DHCP6Message); + + while ((r = dhcp6_option_parse(&option, &len, &optcode, &optlen, + &optval)) >= 0) { + switch (optcode) { + case 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) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + bool rapid_commit; + int r; + + assert(client); + assert(reply); + + if (reply->type != DHCP6_REPLY) + return 0; + + 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) { + _cleanup_(sd_dhcp6_lease_unrefp) sd_dhcp6_lease *lease = NULL; + uint8_t pref_advertise = 0, pref_lease = 0; + int r; + + if (advertise->type != DHCP6_ADVERTISE) + return 0; + + 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 = recv(fd, message, buflen, 0); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_dhcp6_client_errno(client, errno, "Could not receive message from UDP socket: %m"); + + } + if ((size_t) len < sizeof(DHCP6Message)) { + log_dhcp6_client(client, "Too small to be DHCP6 message: ignoring"); + return 0; + } + + 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->ifindex > 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) { + enum DHCP6State state = DHCP6_STATE_SOLICITATION; + int r = 0; + + assert_return(client, -EINVAL); + assert_return(client->event, -EINVAL); + assert_return(client->ifindex > 0, -EINVAL); + assert_return(in_addr_is_link_local(AF_INET6, (const union in_addr_union *) &client->local_address) > 0, -EINVAL); + + if (!IN_SET(client->state, DHCP6_STATE_STOPPED)) + return -EBUSY; + + r = client_reset(client); + if (r < 0) + 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->ifindex, &client->local_address); + if (r < 0) { + _cleanup_free_ char *p = NULL; + + (void) in_addr_to_string(AF_INET6, (const union in_addr_union*) &client->local_address, &p); + return log_dhcp6_client_errno(client, r, + "Failed to bind to UDP socket at address %s: %m", strna(p)); + } + + client->fd = r; + + 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, int64_t 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) { + assert_return(client, 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); + return mfree(client); +} + +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->ifindex = -1; + client->fd = -1; + + client->req_opts_len = ELEMENTSOF(default_req_opts); + client->req_opts = new0(be16_t, client->req_opts_len); + if (!client->req_opts) + return -ENOMEM; + + 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/src/sd-dhcp6-lease.c b/src/libsystemd-network/src/sd-dhcp6-lease.c new file mode 100644 index 0000000000..bd1d4026f5 --- /dev/null +++ b/src/libsystemd-network/src/sd-dhcp6-lease.c @@ -0,0 +1,408 @@ +/*** + 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 . +***/ + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/strv.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp6-lease-internal.h" +#include "systemd-network/dhcp6-protocol.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); + return mfree(lease); +} + +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/src/sd-ipv4acd.c b/src/libsystemd-network/src/sd-ipv4acd.c new file mode 100644 index 0000000000..90ffc8ccea --- /dev/null +++ b/src/libsystemd-network/src/sd-ipv4acd.c @@ -0,0 +1,523 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/ether-addr-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/list.h" +#include "systemd-basic/random-util.h" +#include "systemd-basic/siphash24.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/arp-util.h" +#include "systemd-network/sd-ipv4acd.h" + +/* Constants from the RFC */ +#define PROBE_WAIT_USEC (1U * USEC_PER_SEC) +#define PROBE_NUM 3U +#define PROBE_MIN_USEC (1U * USEC_PER_SEC) +#define PROBE_MAX_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_WAIT_USEC (2U * USEC_PER_SEC) +#define ANNOUNCE_NUM 2U +#define ANNOUNCE_INTERVAL_USEC (2U * USEC_PER_SEC) +#define MAX_CONFLICTS 10U +#define RATE_LIMIT_INTERVAL_USEC (60U * USEC_PER_SEC) +#define DEFEND_INTERVAL_USEC (10U * USEC_PER_SEC) + +typedef enum IPv4ACDState { + IPV4ACD_STATE_INIT, + IPV4ACD_STATE_STARTED, + IPV4ACD_STATE_WAITING_PROBE, + IPV4ACD_STATE_PROBING, + IPV4ACD_STATE_WAITING_ANNOUNCE, + IPV4ACD_STATE_ANNOUNCING, + IPV4ACD_STATE_RUNNING, + _IPV4ACD_STATE_MAX, + _IPV4ACD_STATE_INVALID = -1 +} IPv4ACDState; + +struct sd_ipv4acd { + unsigned n_ref; + + IPv4ACDState state; + int ifindex; + int fd; + + unsigned n_iteration; + unsigned n_conflict; + + sd_event_source *receive_message_event_source; + sd_event_source *timer_event_source; + + usec_t defend_window; + be32_t address; + + /* External */ + struct ether_addr mac_addr; + + sd_event *event; + int event_priority; + sd_ipv4acd_callback_t callback; + void* userdata; +}; + +#define log_ipv4acd_errno(acd, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4ACD: " fmt, ##__VA_ARGS__) +#define log_ipv4acd(acd, fmt, ...) log_ipv4acd_errno(acd, 0, fmt, ##__VA_ARGS__) + +static void ipv4acd_set_state(sd_ipv4acd *acd, IPv4ACDState st, bool reset_counter) { + assert(acd); + assert(st < _IPV4ACD_STATE_MAX); + + if (st == acd->state && !reset_counter) + acd->n_iteration++; + else { + acd->state = st; + acd->n_iteration = 0; + } +} + +static void ipv4acd_reset(sd_ipv4acd *acd) { + assert(acd); + + acd->timer_event_source = sd_event_source_unref(acd->timer_event_source); + acd->receive_message_event_source = sd_event_source_unref(acd->receive_message_event_source); + + acd->fd = safe_close(acd->fd); + + ipv4acd_set_state(acd, IPV4ACD_STATE_INIT, true); +} + +sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *acd) { + if (!acd) + return NULL; + + assert_se(acd->n_ref >= 1); + acd->n_ref++; + + return acd; +} + +sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *acd) { + if (!acd) + return NULL; + + assert_se(acd->n_ref >= 1); + acd->n_ref--; + + if (acd->n_ref > 0) + return NULL; + + ipv4acd_reset(acd); + sd_ipv4acd_detach_event(acd); + + return mfree(acd); +} + +int sd_ipv4acd_new(sd_ipv4acd **ret) { + _cleanup_(sd_ipv4acd_unrefp) sd_ipv4acd *acd = NULL; + + assert_return(ret, -EINVAL); + + acd = new0(sd_ipv4acd, 1); + if (!acd) + return -ENOMEM; + + acd->n_ref = 1; + acd->state = IPV4ACD_STATE_INIT; + acd->ifindex = -1; + acd->fd = -1; + + *ret = acd; + acd = NULL; + + return 0; +} + +static void ipv4acd_client_notify(sd_ipv4acd *acd, int event) { + assert(acd); + + if (!acd->callback) + return; + + acd->callback(acd, event, acd->userdata); +} + +int sd_ipv4acd_stop(sd_ipv4acd *acd) { + assert_return(acd, -EINVAL); + + ipv4acd_reset(acd); + + log_ipv4acd(acd, "STOPPED"); + + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_STOP); + + return 0; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata); + +static int ipv4acd_set_next_wakeup(sd_ipv4acd *acd, usec_t usec, usec_t random_usec) { + _cleanup_(sd_event_source_unrefp) sd_event_source *timer = NULL; + usec_t next_timeout, time_now; + int r; + + assert(acd); + + next_timeout = usec; + + if (random_usec > 0) + next_timeout += (usec_t) random_u64() % random_usec; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &time_now) >= 0); + + r = sd_event_add_time(acd->event, &timer, clock_boottime_or_monotonic(), time_now + next_timeout, 0, ipv4acd_on_timeout, acd); + if (r < 0) + return r; + + r = sd_event_source_set_priority(timer, acd->event_priority); + if (r < 0) + return r; + + (void) sd_event_source_set_description(timer, "ipv4acd-timer"); + + sd_event_source_unref(acd->timer_event_source); + acd->timer_event_source = timer; + timer = NULL; + + return 0; +} + +static bool ipv4acd_arp_conflict(sd_ipv4acd *acd, struct ether_arp *arp) { + assert(acd); + assert(arp); + + /* see the BPF */ + if (memcmp(arp->arp_spa, &acd->address, sizeof(acd->address)) == 0) + return true; + + /* the TPA matched instead of the SPA, this is not a conflict */ + return false; +} + +static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ipv4acd *acd = userdata; + int r = 0; + + assert(acd); + + switch (acd->state) { + + case IPV4ACD_STATE_STARTED: + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_PROBE, true); + + if (acd->n_conflict >= MAX_CONFLICTS) { + char ts[FORMAT_TIMESPAN_MAX]; + log_ipv4acd(acd, "Max conflicts reached, delaying by %s", format_timespan(ts, sizeof(ts), RATE_LIMIT_INTERVAL_USEC, 0)); + + r = ipv4acd_set_next_wakeup(acd, RATE_LIMIT_INTERVAL_USEC, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + + acd->n_conflict = 0; + } else { + r = ipv4acd_set_next_wakeup(acd, 0, PROBE_WAIT_USEC); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + /* Send a probe */ + r = arp_send_probe(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP probe: %m"); + goto fail; + } else { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Probing %s", strna(address)); + } + + if (acd->n_iteration < PROBE_NUM - 2) { + ipv4acd_set_state(acd, IPV4ACD_STATE_PROBING, false); + + r = ipv4acd_set_next_wakeup(acd, PROBE_MIN_USEC, (PROBE_MAX_USEC-PROBE_MIN_USEC)); + if (r < 0) + goto fail; + } else { + ipv4acd_set_state(acd, IPV4ACD_STATE_WAITING_ANNOUNCE, true); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_WAIT_USEC, 0); + if (r < 0) + goto fail; + } + + break; + + case IPV4ACD_STATE_ANNOUNCING: + if (acd->n_iteration >= ANNOUNCE_NUM - 1) { + ipv4acd_set_state(acd, IPV4ACD_STATE_RUNNING, false); + break; + } + + /* fall through */ + + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* Send announcement packet */ + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "ANNOUNCE"); + + ipv4acd_set_state(acd, IPV4ACD_STATE_ANNOUNCING, false); + + r = ipv4acd_set_next_wakeup(acd, ANNOUNCE_INTERVAL_USEC, 0); + if (r < 0) + goto fail; + + if (acd->n_iteration == 0) { + acd->n_conflict = 0; + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_BIND); + } + + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +static void ipv4acd_on_conflict(sd_ipv4acd *acd) { + _cleanup_free_ char *address = NULL; + union in_addr_union addr = { .in.s_addr = acd->address }; + + assert(acd); + + acd->n_conflict++; + + (void) in_addr_to_string(AF_INET, &addr, &address); + log_ipv4acd(acd, "Conflict on %s (%u)", strna(address), acd->n_conflict); + + ipv4acd_reset(acd); + ipv4acd_client_notify(acd, SD_IPV4ACD_EVENT_CONFLICT); +} + +static int ipv4acd_on_packet( + sd_event_source *s, + int fd, + uint32_t revents, + void *userdata) { + + sd_ipv4acd *acd = userdata; + struct ether_arp packet; + ssize_t n; + int r; + + assert(s); + assert(acd); + assert(fd >= 0); + + n = recv(fd, &packet, sizeof(struct ether_arp), 0); + if (n < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_ipv4acd_errno(acd, errno, "Failed to read ARP packet: %m"); + goto fail; + } + if ((size_t) n != sizeof(struct ether_arp)) { + log_ipv4acd(acd, "Ignoring too short ARP packet."); + return 0; + } + + switch (acd->state) { + + case IPV4ACD_STATE_ANNOUNCING: + case IPV4ACD_STATE_RUNNING: + + if (ipv4acd_arp_conflict(acd, &packet)) { + usec_t ts; + + assert_se(sd_event_now(acd->event, clock_boottime_or_monotonic(), &ts) >= 0); + + /* Defend address */ + if (ts > acd->defend_window) { + acd->defend_window = ts + DEFEND_INTERVAL_USEC; + r = arp_send_announcement(acd->fd, acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) { + log_ipv4acd_errno(acd, r, "Failed to send ARP announcement: %m"); + goto fail; + } else + log_ipv4acd(acd, "DEFEND"); + + } else + ipv4acd_on_conflict(acd); + } + break; + + case IPV4ACD_STATE_WAITING_PROBE: + case IPV4ACD_STATE_PROBING: + case IPV4ACD_STATE_WAITING_ANNOUNCE: + /* BPF ensures this packet indicates a conflict */ + ipv4acd_on_conflict(acd); + break; + + default: + assert_not_reached("Invalid state."); + } + + return 0; + +fail: + sd_ipv4acd_stop(acd); + return 0; +} + +int sd_ipv4acd_set_ifindex(sd_ipv4acd *acd, int ifindex) { + assert_return(acd, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->ifindex = ifindex; + + return 0; +} + +int sd_ipv4acd_set_mac(sd_ipv4acd *acd, const struct ether_addr *addr) { + assert_return(acd, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->mac_addr = *addr; + + return 0; +} + +int sd_ipv4acd_detach_event(sd_ipv4acd *acd) { + assert_return(acd, -EINVAL); + + acd->event = sd_event_unref(acd->event); + + return 0; +} + +int sd_ipv4acd_attach_event(sd_ipv4acd *acd, sd_event *event, int64_t priority) { + int r; + + assert_return(acd, -EINVAL); + assert_return(!acd->event, -EBUSY); + + if (event) + acd->event = sd_event_ref(event); + else { + r = sd_event_default(&acd->event); + if (r < 0) + return r; + } + + acd->event_priority = priority; + + return 0; +} + +int sd_ipv4acd_set_callback(sd_ipv4acd *acd, sd_ipv4acd_callback_t cb, void *userdata) { + assert_return(acd, -EINVAL); + + acd->callback = cb; + acd->userdata = userdata; + + return 0; +} + +int sd_ipv4acd_set_address(sd_ipv4acd *acd, const struct in_addr *address) { + assert_return(acd, -EINVAL); + assert_return(address, -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + acd->address = address->s_addr; + + return 0; +} + +int sd_ipv4acd_is_running(sd_ipv4acd *acd) { + assert_return(acd, false); + + return acd->state != IPV4ACD_STATE_INIT; +} + +int sd_ipv4acd_start(sd_ipv4acd *acd) { + int r; + + assert_return(acd, -EINVAL); + assert_return(acd->event, -EINVAL); + assert_return(acd->ifindex > 0, -EINVAL); + assert_return(acd->address != 0, -EINVAL); + assert_return(!ether_addr_is_null(&acd->mac_addr), -EINVAL); + assert_return(acd->state == IPV4ACD_STATE_INIT, -EBUSY); + + r = arp_network_bind_raw_socket(acd->ifindex, acd->address, &acd->mac_addr); + if (r < 0) + return r; + + safe_close(acd->fd); + acd->fd = r; + acd->defend_window = 0; + acd->n_conflict = 0; + + r = sd_event_add_io(acd->event, &acd->receive_message_event_source, acd->fd, EPOLLIN, ipv4acd_on_packet, acd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(acd->receive_message_event_source, acd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(acd->receive_message_event_source, "ipv4acd-receive-message"); + + r = ipv4acd_set_next_wakeup(acd, 0, 0); + if (r < 0) + goto fail; + + ipv4acd_set_state(acd, IPV4ACD_STATE_STARTED, true); + return 0; + +fail: + ipv4acd_reset(acd); + return r; +} diff --git a/src/libsystemd-network/src/sd-ipv4ll.c b/src/libsystemd-network/src/sd-ipv4ll.c new file mode 100644 index 0000000000..35d3a972b2 --- /dev/null +++ b/src/libsystemd-network/src/sd-ipv4ll.c @@ -0,0 +1,343 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/ether-addr-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/list.h" +#include "systemd-basic/random-util.h" +#include "systemd-basic/siphash24.h" +#include "systemd-basic/sparse-endian.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/sd-ipv4acd.h" +#include "systemd-network/sd-ipv4ll.h" + +#define IPV4LL_NETWORK UINT32_C(0xA9FE0000) +#define IPV4LL_NETMASK UINT32_C(0xFFFF0000) + +#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 ether_addr mac; + + struct { + le64_t value; + le64_t generation; + } seed; + bool seed_set; + + /* External */ + be32_t claimed_address; + + sd_ipv4ll_callback_t callback; + void* userdata; +}; + +#define log_ipv4ll_errno(ll, error, fmt, ...) log_internal(LOG_DEBUG, error, __FILE__, __LINE__, __func__, "IPV4LL: " fmt, ##__VA_ARGS__) +#define log_ipv4ll(ll, fmt, ...) log_ipv4ll_errno(ll, 0, fmt, ##__VA_ARGS__) + +static void ipv4ll_on_acd(sd_ipv4acd *ll, int event, 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); + return mfree(ll); +} + +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) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_stop(ll->acd); +} + +int sd_ipv4ll_set_ifindex(sd_ipv4ll *ll, int ifindex) { + assert_return(ll, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + return sd_ipv4acd_set_ifindex(ll->acd, ifindex); +} + +int sd_ipv4ll_set_mac(sd_ipv4ll *ll, const struct ether_addr *addr) { + int r; + + assert_return(ll, -EINVAL); + assert_return(addr, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + r = sd_ipv4acd_set_mac(ll->acd, addr); + if (r < 0) + return r; + + ll->mac = *addr; + return 0; +} + +int sd_ipv4ll_detach_event(sd_ipv4ll *ll) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_detach_event(ll->acd); +} + +int sd_ipv4ll_attach_event(sd_ipv4ll *ll, sd_event *event, int64_t priority) { + assert_return(ll, -EINVAL); + + return sd_ipv4acd_attach_event(ll->acd, event, priority); +} + +int sd_ipv4ll_set_callback(sd_ipv4ll *ll, sd_ipv4ll_callback_t cb, void *userdata) { + assert_return(ll, -EINVAL); + + ll->callback = 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, uint64_t seed) { + assert_return(ll, -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + ll->seed.value = htole64(seed); + ll->seed_set = true; + + 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) { + assert(address); + + if (!in_addr_is_link_local(AF_INET, (const union in_addr_union *) address)) + return false; + + return !IN_SET(be32toh(address->s_addr) & 0x0000FF00U, 0x0000U, 0xFF00U); +} + +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; +} + +#define PICK_HASH_KEY SD_ID128_MAKE(15,ac,82,a6,d6,3f,49,78,98,77,5d,0c,69,02,94,0b) + +static int ipv4ll_pick_address(sd_ipv4ll *ll) { + _cleanup_free_ char *address = NULL; + be32_t addr; + + assert(ll); + + do { + uint64_t h; + + h = siphash24(&ll->seed, sizeof(ll->seed), PICK_HASH_KEY.bytes); + + /* Increase the generation counter by one */ + ll->seed.generation = htole64(le64toh(ll->seed.generation) + 1); + + addr = htobe32((h & UINT32_C(0x0000FFFF)) | IPV4LL_NETWORK); + } while (addr == ll->address || + IN_SET(be32toh(addr) & 0x0000FF00U, 0x0000U, 0xFF00U)); + + (void) in_addr_to_string(AF_INET, &(union in_addr_union) { .in.s_addr = addr }, &address); + log_ipv4ll(ll, "Picked new IP address %s.", strna(address)); + + return sd_ipv4ll_set_address(ll, &(struct in_addr) { addr }); +} + +#define MAC_HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2) + +int sd_ipv4ll_start(sd_ipv4ll *ll) { + int r; + bool picked_address = false; + + assert_return(ll, -EINVAL); + assert_return(!ether_addr_is_null(&ll->mac), -EINVAL); + assert_return(sd_ipv4ll_is_running(ll) == 0, -EBUSY); + + /* If no random seed is set, generate some from the MAC address */ + if (!ll->seed_set) + ll->seed.value = htole64(siphash24(ll->mac.ether_addr_octet, ETH_ALEN, MAC_HASH_KEY.bytes)); + + /* Restart the generation counter. */ + ll->seed.generation = 0; + + if (ll->address == 0) { + r = ipv4ll_pick_address(ll); + if (r < 0) + return r; + + picked_address = true; + } + + r = sd_ipv4acd_start(ll->acd); + if (r < 0) { + + /* We couldn't start? If so, let's forget the picked address again, the user might make a change and + * retry, and we want the new data to take effect when picking an address. */ + if (picked_address) + ll->address = 0; + + return r; + } + + return 0; +} + +static void ipv4ll_client_notify(sd_ipv4ll *ll, int event) { + assert(ll); + + if (ll->callback) + ll->callback(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/src/sd-lldp.c b/src/libsystemd-network/src/sd-lldp.c new file mode 100644 index 0000000000..b271c88786 --- /dev/null +++ b/src/libsystemd-network/src/sd-lldp.c @@ -0,0 +1,536 @@ +/*** + 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 . +***/ + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/ether-addr-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-network/lldp-internal.h" +#include "systemd-network/lldp-neighbor.h" +#include "systemd-network/lldp-network.h" +#include "systemd-network/sd-lldp.h" + +#define LLDP_DEFAULT_NEIGHBORS_MAX 128U + +static void lldp_flush_neighbors(sd_lldp *lldp) { + sd_lldp_neighbor *n; + + assert(lldp); + + while ((n = hashmap_first(lldp->neighbor_by_id))) + lldp_neighbor_unlink(n); +} + +static void lldp_callback(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n) { + assert(lldp); + + log_lldp("Invoking callback for '%c'.", event); + + if (!lldp->callback) + return; + + lldp->callback(lldp, event, n, lldp->userdata); +} + +static int lldp_make_space(sd_lldp *lldp, size_t extra) { + usec_t t = USEC_INFINITY; + bool changed = false; + + assert(lldp); + + /* Remove all entries that are past their TTL, and more until at least the specified number of extra entries + * are free. */ + + for (;;) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) + break; + + sd_lldp_neighbor_ref(n); + + if (hashmap_size(lldp->neighbor_by_id) > LESS_BY(lldp->neighbors_max, extra)) + goto remove_one; + + if (t == USEC_INFINITY) + t = now(clock_boottime_or_monotonic()); + + if (n->until > t) + break; + + remove_one: + lldp_neighbor_unlink(n); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, n); + changed = true; + } + + return changed; +} + +static bool lldp_keep_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + assert(lldp); + assert(n); + + /* Don't keep data with a zero TTL */ + if (n->ttl <= 0) + return false; + + /* Filter out data from the filter address */ + if (!ether_addr_is_null(&lldp->filter_address) && + ether_addr_equal(&lldp->filter_address, &n->source_address)) + return false; + + /* Only add if the neighbor has a capability we are interested in. Note that we also store all neighbors with + * no caps field set. */ + if (n->has_capabilities && + (n->enabled_capabilities & lldp->capability_mask) == 0) + return false; + + /* Keep everything else */ + return true; +} + +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor); + +static int lldp_add_neighbor(sd_lldp *lldp, sd_lldp_neighbor *n) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *old = NULL; + bool keep; + int r; + + assert(lldp); + assert(n); + assert(!n->lldp); + + keep = lldp_keep_neighbor(lldp, n); + + /* First retrieve the old entry for this MSAP */ + old = hashmap_get(lldp->neighbor_by_id, &n->id); + if (old) { + sd_lldp_neighbor_ref(old); + + if (!keep) { + lldp_neighbor_unlink(old); + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + return 0; + } + + if (lldp_neighbor_equal(n, old)) { + /* Is this equal, then restart the TTL counter, but don't do anyting else. */ + old->timestamp = n->timestamp; + lldp_start_timer(lldp, old); + lldp_callback(lldp, SD_LLDP_EVENT_REFRESHED, old); + return 0; + } + + /* Data changed, remove the old entry, and add a new one */ + lldp_neighbor_unlink(old); + + } else if (!keep) + return 0; + + /* Then, make room for at least one new neighbor */ + lldp_make_space(lldp, 1); + + r = hashmap_put(lldp->neighbor_by_id, &n->id, n); + if (r < 0) + goto finish; + + r = prioq_put(lldp->neighbor_by_expiry, n, &n->prioq_idx); + if (r < 0) { + assert_se(hashmap_remove(lldp->neighbor_by_id, &n->id) == n); + goto finish; + } + + n->lldp = lldp; + + lldp_start_timer(lldp, n); + lldp_callback(lldp, old ? SD_LLDP_EVENT_UPDATED : SD_LLDP_EVENT_ADDED, n); + + return 1; + +finish: + if (old) + lldp_callback(lldp, SD_LLDP_EVENT_REMOVED, old); + + return r; +} + +static int lldp_handle_datagram(sd_lldp *lldp, sd_lldp_neighbor *n) { + int r; + + assert(lldp); + assert(n); + + r = lldp_neighbor_parse(n); + if (r == -EBADMSG) /* Ignore bad messages */ + return 0; + if (r < 0) + return r; + + r = lldp_add_neighbor(lldp, n); + if (r < 0) { + log_lldp_errno(r, "Failed to add datagram. Ignoring."); + return 0; + } + + log_lldp("Successfully processed LLDP datagram."); + return 0; +} + +static int lldp_receive_datagram(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_lldp_neighbor_unrefp) sd_lldp_neighbor *n = NULL; + ssize_t space, length; + sd_lldp *lldp = userdata; + struct timespec ts; + + assert(fd >= 0); + assert(lldp); + + space = next_datagram_size_fd(fd); + if (space < 0) + return log_lldp_errno(space, "Failed to determine datagram size to read: %m"); + + n = lldp_neighbor_new(space); + if (!n) + return -ENOMEM; + + length = recv(fd, LLDP_NEIGHBOR_RAW(n), n->raw_size, MSG_DONTWAIT); + if (length < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_lldp_errno(errno, "Failed to read LLDP datagram: %m"); + } + + if ((size_t) length != n->raw_size) { + log_lldp("Packet size mismatch."); + return -EINVAL; + } + + /* Try to get the timestamp of this packet if it is known */ + if (ioctl(fd, SIOCGSTAMPNS, &ts) >= 0) + triple_timestamp_from_realtime(&n->timestamp, timespec_load(&ts)); + else + triple_timestamp_get(&n->timestamp); + + return lldp_handle_datagram(lldp, n); +} + +static void lldp_reset(sd_lldp *lldp) { + assert(lldp); + + lldp->timer_event_source = sd_event_source_unref(lldp->timer_event_source); + lldp->io_event_source = sd_event_source_unref(lldp->io_event_source); + lldp->fd = safe_close(lldp->fd); +} + +_public_ int sd_lldp_start(sd_lldp *lldp) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->event, -EINVAL); + assert_return(lldp->ifindex > 0, -EINVAL); + + if (lldp->fd >= 0) + return 0; + + assert(!lldp->io_event_source); + + lldp->fd = lldp_network_bind_raw_socket(lldp->ifindex); + if (lldp->fd < 0) + return lldp->fd; + + r = sd_event_add_io(lldp->event, &lldp->io_event_source, lldp->fd, EPOLLIN, lldp_receive_datagram, lldp); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(lldp->io_event_source, lldp->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(lldp->io_event_source, "lldp-io"); + + log_lldp("Started LLDP client"); + return 1; + +fail: + lldp_reset(lldp); + return r; +} + +_public_ int sd_lldp_stop(sd_lldp *lldp) { + assert_return(lldp, -EINVAL); + + if (lldp->fd < 0) + return 0; + + log_lldp("Stopping LLDP client"); + + lldp_reset(lldp); + lldp_flush_neighbors(lldp); + + return 1; +} + +_public_ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int64_t priority) { + int r; + + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + assert_return(!lldp->event, -EBUSY); + + if (event) + lldp->event = sd_event_ref(event); + else { + r = sd_event_default(&lldp->event); + if (r < 0) + return r; + } + + lldp->event_priority = priority; + + return 0; +} + +_public_ int sd_lldp_detach_event(sd_lldp *lldp) { + + assert_return(lldp, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->event = sd_event_unref(lldp->event); + return 0; +} + +_public_ sd_event* sd_lldp_get_event(sd_lldp *lldp) { + assert_return(lldp, NULL); + + return lldp->event; +} + +_public_ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_callback_t cb, void *userdata) { + assert_return(lldp, -EINVAL); + + lldp->callback = cb; + lldp->userdata = userdata; + + return 0; +} + +_public_ int sd_lldp_set_ifindex(sd_lldp *lldp, int ifindex) { + assert_return(lldp, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(lldp->fd < 0, -EBUSY); + + lldp->ifindex = ifindex; + return 0; +} + +_public_ sd_lldp* sd_lldp_ref(sd_lldp *lldp) { + + if (!lldp) + return NULL; + + assert(lldp->n_ref > 0); + lldp->n_ref++; + + return lldp; +} + +_public_ sd_lldp* sd_lldp_unref(sd_lldp *lldp) { + + if (!lldp) + return NULL; + + assert(lldp->n_ref > 0); + lldp->n_ref --; + + if (lldp->n_ref > 0) + return NULL; + + lldp_reset(lldp); + sd_lldp_detach_event(lldp); + lldp_flush_neighbors(lldp); + + hashmap_free(lldp->neighbor_by_id); + prioq_free(lldp->neighbor_by_expiry); + return mfree(lldp); +} + +_public_ int sd_lldp_new(sd_lldp **ret) { + _cleanup_(sd_lldp_unrefp) sd_lldp *lldp = NULL; + int r; + + assert_return(ret, -EINVAL); + + lldp = new0(sd_lldp, 1); + if (!lldp) + return -ENOMEM; + + lldp->n_ref = 1; + lldp->fd = -1; + lldp->neighbors_max = LLDP_DEFAULT_NEIGHBORS_MAX; + lldp->capability_mask = (uint16_t) -1; + + lldp->neighbor_by_id = hashmap_new(&lldp_neighbor_id_hash_ops); + if (!lldp->neighbor_by_id) + return -ENOMEM; + + r = prioq_ensure_allocated(&lldp->neighbor_by_expiry, lldp_neighbor_prioq_compare_func); + if (r < 0) + return r; + + *ret = lldp; + lldp = NULL; + + return 0; +} + +static int neighbor_compare_func(const void *a, const void *b) { + const sd_lldp_neighbor * const*x = a, * const *y = b; + + return lldp_neighbor_id_hash_ops.compare(&(*x)->id, &(*y)->id); +} + +static int on_timer_event(sd_event_source *s, uint64_t usec, void *userdata) { + sd_lldp *lldp = userdata; + int r, q; + + r = lldp_make_space(lldp, 0); + if (r < 0) + return log_lldp_errno(r, "Failed to make space: %m"); + + q = lldp_start_timer(lldp, NULL); + if (q < 0) + return log_lldp_errno(q, "Failed to restart timer: %m"); + + return 0; +} + +static int lldp_start_timer(sd_lldp *lldp, sd_lldp_neighbor *neighbor) { + sd_lldp_neighbor *n; + int r; + + assert(lldp); + + if (neighbor) + lldp_neighbor_start_ttl(neighbor); + + n = prioq_peek(lldp->neighbor_by_expiry); + if (!n) { + + if (lldp->timer_event_source) + return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_OFF); + + return 0; + } + + if (lldp->timer_event_source) { + r = sd_event_source_set_time(lldp->timer_event_source, n->until); + if (r < 0) + return r; + + return sd_event_source_set_enabled(lldp->timer_event_source, SD_EVENT_ONESHOT); + } + + if (!lldp->event) + return 0; + + r = sd_event_add_time(lldp->event, &lldp->timer_event_source, clock_boottime_or_monotonic(), n->until, 0, on_timer_event, lldp); + if (r < 0) + return r; + + r = sd_event_source_set_priority(lldp->timer_event_source, lldp->event_priority); + if (r < 0) + return r; + + (void) sd_event_source_set_description(lldp->timer_event_source, "lldp-timer"); + return 0; +} + +_public_ int sd_lldp_get_neighbors(sd_lldp *lldp, sd_lldp_neighbor ***ret) { + sd_lldp_neighbor **l = NULL, *n; + Iterator i; + int k = 0, r; + + assert_return(lldp, -EINVAL); + assert_return(ret, -EINVAL); + + if (hashmap_isempty(lldp->neighbor_by_id)) { /* Special shortcut */ + *ret = NULL; + return 0; + } + + l = new0(sd_lldp_neighbor*, hashmap_size(lldp->neighbor_by_id)); + if (!l) + return -ENOMEM; + + r = lldp_start_timer(lldp, NULL); + if (r < 0) { + free(l); + return r; + } + + HASHMAP_FOREACH(n, lldp->neighbor_by_id, i) + l[k++] = sd_lldp_neighbor_ref(n); + + assert((size_t) k == hashmap_size(lldp->neighbor_by_id)); + + /* Return things in a stable order */ + qsort(l, k, sizeof(sd_lldp_neighbor*), neighbor_compare_func); + *ret = l; + + return k; +} + +_public_ int sd_lldp_set_neighbors_max(sd_lldp *lldp, uint64_t m) { + assert_return(lldp, -EINVAL); + assert_return(m <= 0, -EINVAL); + + lldp->neighbors_max = m; + lldp_make_space(lldp, 0); + + return 0; +} + +_public_ int sd_lldp_match_capabilities(sd_lldp *lldp, uint16_t mask) { + assert_return(lldp, -EINVAL); + assert_return(mask != 0, -EINVAL); + + lldp->capability_mask = mask; + + return 0; +} + +_public_ int sd_lldp_set_filter_address(sd_lldp *lldp, const struct ether_addr *addr) { + assert_return(lldp, -EINVAL); + + /* In order to deal nicely with bridges that send back our own packets, allow one address to be filtered, so + * that our own can be filtered out here. */ + + if (addr) + lldp->filter_address = *addr; + else + zero(lldp->filter_address); + + return 0; +} diff --git a/src/libsystemd-network/src/sd-ndisc.c b/src/libsystemd-network/src/sd-ndisc.c new file mode 100644 index 0000000000..7f3bcbd7e2 --- /dev/null +++ b/src/libsystemd-network/src/sd-ndisc.c @@ -0,0 +1,419 @@ +/*** + 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 . +***/ + +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/icmp6-util.h" +#include "systemd-network/ndisc-internal.h" +#include "systemd-network/ndisc-router.h" +#include "systemd-network/sd-ndisc.h" + +#define NDISC_ROUTER_SOLICITATION_INTERVAL (4U * USEC_PER_SEC) +#define NDISC_MAX_ROUTER_SOLICITATIONS 3U + +static void ndisc_callback(sd_ndisc *ndisc, sd_ndisc_event event, sd_ndisc_router *rt) { + assert(ndisc); + + log_ndisc("Invoking callback for '%c'.", event); + + if (!ndisc->callback) + return; + + ndisc->callback(ndisc, event, rt, ndisc->userdata); +} + +_public_ int sd_ndisc_set_callback( + sd_ndisc *nd, + sd_ndisc_callback_t callback, + void *userdata) { + + assert_return(nd, -EINVAL); + + nd->callback = callback; + nd->userdata = userdata; + + return 0; +} + +_public_ int sd_ndisc_set_ifindex(sd_ndisc *nd, int ifindex) { + assert_return(nd, -EINVAL); + assert_return(ifindex > 0, -EINVAL); + assert_return(nd->fd < 0, -EBUSY); + + nd->ifindex = ifindex; + return 0; +} + +_public_ int sd_ndisc_set_mac(sd_ndisc *nd, const struct ether_addr *mac_addr) { + assert_return(nd, -EINVAL); + + if (mac_addr) + nd->mac_addr = *mac_addr; + else + zero(nd->mac_addr); + + return 0; +} + +_public_ int sd_ndisc_attach_event(sd_ndisc *nd, sd_event *event, int64_t priority) { + int r; + + assert_return(nd, -EINVAL); + assert_return(nd->fd < 0, -EBUSY); + assert_return(!nd->event, -EBUSY); + + if (event) + nd->event = sd_event_ref(event); + else { + r = sd_event_default(&nd->event); + if (r < 0) + return 0; + } + + nd->event_priority = priority; + + return 0; +} + +_public_ int sd_ndisc_detach_event(sd_ndisc *nd) { + + assert_return(nd, -EINVAL); + assert_return(nd->fd < 0, -EBUSY); + + nd->event = sd_event_unref(nd->event); + return 0; +} + +_public_ sd_event *sd_ndisc_get_event(sd_ndisc *nd) { + assert_return(nd, NULL); + + return nd->event; +} + +_public_ sd_ndisc *sd_ndisc_ref(sd_ndisc *nd) { + + if (!nd) + return NULL; + + assert(nd->n_ref > 0); + nd->n_ref++; + + return nd; +} + +static int ndisc_reset(sd_ndisc *nd) { + assert(nd); + + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + nd->recv_event_source = sd_event_source_unref(nd->recv_event_source); + nd->fd = safe_close(nd->fd); + + return 0; +} + +_public_ sd_ndisc *sd_ndisc_unref(sd_ndisc *nd) { + + if (!nd) + return NULL; + + assert(nd->n_ref > 0); + nd->n_ref--; + + if (nd->n_ref > 0) + return NULL; + + ndisc_reset(nd); + sd_ndisc_detach_event(nd); + return mfree(nd); +} + +_public_ int sd_ndisc_new(sd_ndisc **ret) { + _cleanup_(sd_ndisc_unrefp) sd_ndisc *nd = NULL; + + assert_return(ret, -EINVAL); + + nd = new0(sd_ndisc, 1); + if (!nd) + return -ENOMEM; + + nd->n_ref = 1; + nd->fd = -1; + + *ret = nd; + nd = NULL; + + return 0; +} + +_public_ int sd_ndisc_get_mtu(sd_ndisc *nd, uint32_t *mtu) { + assert_return(nd, -EINVAL); + assert_return(mtu, -EINVAL); + + if (nd->mtu == 0) + return -ENODATA; + + *mtu = nd->mtu; + return 0; +} + +_public_ int sd_ndisc_get_hop_limit(sd_ndisc *nd, uint8_t *ret) { + assert_return(nd, -EINVAL); + assert_return(ret, -EINVAL); + + if (nd->hop_limit == 0) + return -ENODATA; + + *ret = nd->hop_limit; + return 0; +} + +static int ndisc_handle_datagram(sd_ndisc *nd, sd_ndisc_router *rt) { + int r; + + assert(nd); + assert(rt); + + r = ndisc_router_parse(rt); + if (r == -EBADMSG) /* Bad packet */ + return 0; + if (r < 0) + return 0; + + /* Update global variables we keep */ + if (rt->mtu > 0) + nd->mtu = rt->mtu; + if (rt->hop_limit > 0) + nd->hop_limit = rt->hop_limit; + + log_ndisc("Received Router Advertisement: flags %s preference %s lifetime %" PRIu16 " sec", + rt->flags & ND_RA_FLAG_MANAGED ? "MANAGED" : rt->flags & ND_RA_FLAG_OTHER ? "OTHER" : "none", + rt->preference == SD_NDISC_PREFERENCE_HIGH ? "high" : rt->preference == SD_NDISC_PREFERENCE_LOW ? "low" : "medium", + rt->lifetime); + + ndisc_callback(nd, SD_NDISC_EVENT_ROUTER, rt); + return 0; +} + +static int ndisc_recv(sd_event_source *s, int fd, uint32_t revents, void *userdata) { + _cleanup_(sd_ndisc_router_unrefp) sd_ndisc_router *rt = NULL; + sd_ndisc *nd = userdata; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(int)) + /* ttl */ + CMSG_SPACE(sizeof(struct timeval))]; + } control = {}; + struct iovec iov = {}; + union sockaddr_union sa = {}; + struct msghdr msg = { + .msg_name = &sa.sa, + .msg_namelen = sizeof(sa), + .msg_iov = &iov, + .msg_iovlen = 1, + .msg_control = &control, + .msg_controllen = sizeof(control), + }; + struct cmsghdr *cmsg; + ssize_t len, buflen; + + assert(s); + assert(nd); + assert(nd->event); + + buflen = next_datagram_size_fd(fd); + if (buflen < 0) + return log_ndisc_errno(buflen, "Failed to determine datagram size to read: %m"); + + rt = ndisc_router_new(buflen); + if (!rt) + return -ENOMEM; + + iov.iov_base = NDISC_ROUTER_RAW(rt); + iov.iov_len = rt->raw_size; + + len = recvmsg(fd, &msg, MSG_DONTWAIT); + if (len < 0) { + if (errno == EAGAIN || errno == EINTR) + return 0; + + return log_ndisc_errno(errno, "Could not receive message from ICMPv6 socket: %m"); + } + + if ((size_t) len != rt->raw_size) { + log_ndisc("Packet size mismatch."); + return -EINVAL; + } + + if (msg.msg_namelen == sizeof(struct sockaddr_in6) && + sa.in6.sin6_family == AF_INET6) { + + if (in_addr_is_link_local(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr) <= 0) { + _cleanup_free_ char *addr = NULL; + + (void) in_addr_to_string(AF_INET6, (union in_addr_union*) &sa.in6.sin6_addr, &addr); + log_ndisc("Received RA from non-link-local address %s. Ignoring.", strna(addr)); + return 0; + } + + rt->address = sa.in6.sin6_addr; + + } else if (msg.msg_namelen > 0) { + log_ndisc("Received invalid source address size from ICMPv6 socket: %zu bytes", (size_t) msg.msg_namelen); + return -EINVAL; + } + + /* namelen == 0 only happens when running the test-suite over a socketpair */ + + assert(!(msg.msg_flags & MSG_CTRUNC)); + assert(!(msg.msg_flags & MSG_TRUNC)); + + CMSG_FOREACH(cmsg, &msg) { + if (cmsg->cmsg_level == SOL_IPV6 && + cmsg->cmsg_type == IPV6_HOPLIMIT && + cmsg->cmsg_len == CMSG_LEN(sizeof(int))) { + int hops = *(int*) CMSG_DATA(cmsg); + + if (hops != 255) { + log_ndisc("Received RA with invalid hop limit %d. Ignoring.", hops); + return 0; + } + } + + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SO_TIMESTAMP && + cmsg->cmsg_len == CMSG_LEN(sizeof(struct timeval))) + triple_timestamp_from_realtime(&rt->timestamp, timeval_load((struct timeval*) CMSG_DATA(cmsg))); + } + + if (!triple_timestamp_is_set(&rt->timestamp)) + triple_timestamp_get(&rt->timestamp); + + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + + return ndisc_handle_datagram(nd, rt); +} + +static int ndisc_timeout(sd_event_source *s, uint64_t usec, void *userdata) { + sd_ndisc *nd = userdata; + usec_t time_now, next_timeout; + int r; + + assert(s); + assert(nd); + assert(nd->event); + + if (nd->nd_sent >= NDISC_MAX_ROUTER_SOLICITATIONS) { + nd->timeout_event_source = sd_event_source_unref(nd->timeout_event_source); + ndisc_callback(nd, SD_NDISC_EVENT_TIMEOUT, NULL); + return 0; + } + + r = icmp6_send_router_solicitation(nd->fd, &nd->mac_addr); + if (r < 0) { + log_ndisc_errno(r, "Error sending Router Solicitation: %m"); + goto fail; + } + + log_ndisc("Sent Router Solicitation"); + nd->nd_sent++; + + assert_se(sd_event_now(nd->event, clock_boottime_or_monotonic(), &time_now) >= 0); + next_timeout = time_now + NDISC_ROUTER_SOLICITATION_INTERVAL; + + r = sd_event_source_set_time(nd->timeout_event_source, next_timeout); + if (r < 0) { + log_ndisc_errno(r, "Error updating timer: %m"); + goto fail; + } + + r = sd_event_source_set_enabled(nd->timeout_event_source, SD_EVENT_ONESHOT); + if (r < 0) { + log_ndisc_errno(r, "Error reenabling timer: %m"); + goto fail; + } + + return 0; + +fail: + sd_ndisc_stop(nd); + return 0; +} + +_public_ int sd_ndisc_stop(sd_ndisc *nd) { + assert_return(nd, -EINVAL); + + if (nd->fd < 0) + return 0; + + log_ndisc("Stopping IPv6 Router Solicitation client"); + + ndisc_reset(nd); + return 1; +} + +_public_ int sd_ndisc_start(sd_ndisc *nd) { + int r; + + assert_return(nd, -EINVAL); + assert_return(nd->event, -EINVAL); + assert_return(nd->ifindex > 0, -EINVAL); + + if (nd->fd >= 0) + return 0; + + assert(!nd->recv_event_source); + assert(!nd->timeout_event_source); + + nd->fd = icmp6_bind_router_solicitation(nd->ifindex); + if (nd->fd < 0) + return nd->fd; + + r = sd_event_add_io(nd->event, &nd->recv_event_source, nd->fd, EPOLLIN, ndisc_recv, nd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nd->recv_event_source, nd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(nd->recv_event_source, "ndisc-receive-message"); + + r = sd_event_add_time(nd->event, &nd->timeout_event_source, clock_boottime_or_monotonic(), 0, 0, ndisc_timeout, nd); + if (r < 0) + goto fail; + + r = sd_event_source_set_priority(nd->timeout_event_source, nd->event_priority); + if (r < 0) + goto fail; + + (void) sd_event_source_set_description(nd->timeout_event_source, "ndisc-timeout"); + + log_ndisc("Started IPv6 Router Solicitation client"); + return 1; + +fail: + ndisc_reset(nd); + return r; +} diff --git a/src/libsystemd-network/test-acd.c b/src/libsystemd-network/test-acd.c deleted file mode 100644 index 27fcc332a3..0000000000 --- a/src/libsystemd-network/test-acd.c +++ /dev/null @@ -1,114 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include - -#include -#include - -#include "sd-event.h" -#include "sd-ipv4acd.h" -#include "sd-netlink.h" - -#include "in-addr-util.h" -#include "netlink-util.h" -#include "util.h" - -static void acd_handler(sd_ipv4acd *acd, int event, void *userdata) { - assert_se(acd); - - switch (event) { - case SD_IPV4ACD_EVENT_BIND: - log_info("bound"); - break; - case SD_IPV4ACD_EVENT_CONFLICT: - log_info("conflict"); - break; - case SD_IPV4ACD_EVENT_STOP: - log_error("the client was stopped"); - break; - default: - assert_not_reached("invalid ACD event"); - } -} - -static int client_run(int ifindex, const struct in_addr *pa, const struct ether_addr *ha, sd_event *e) { - sd_ipv4acd *acd; - - assert_se(sd_ipv4acd_new(&acd) >= 0); - assert_se(sd_ipv4acd_attach_event(acd, e, 0) >= 0); - - assert_se(sd_ipv4acd_set_ifindex(acd, ifindex) >= 0); - assert_se(sd_ipv4acd_set_mac(acd, ha) >= 0); - assert_se(sd_ipv4acd_set_address(acd, pa) >= 0); - assert_se(sd_ipv4acd_set_callback(acd, acd_handler, NULL) >= 0); - - log_info("starting IPv4ACD client"); - - assert_se(sd_ipv4acd_start(acd) >= 0); - - assert_se(sd_event_loop(e) >= 0); - - assert_se(!sd_ipv4acd_unref(acd)); - - return EXIT_SUCCESS; -} - -static int test_acd(const char *ifname, const char *address) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; - union in_addr_union pa; - struct ether_addr ha; - int ifindex; - - assert_se(in_addr_from_string(AF_INET, address, &pa) >= 0); - - assert_se(sd_event_new(&e) >= 0); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); - assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); - assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); - - assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); - assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); - - client_run(ifindex, &pa.in, &ha, e); - - return EXIT_SUCCESS; -} - -int main(int argc, char *argv[]) { - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - log_open(); - - if (argc == 3) - return test_acd(argv[1], argv[2]); - else { - log_error("This program takes two arguments.\n" - "\t %s ", 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 deleted file mode 100644 index 2a101cb1fe..0000000000 --- a/src/libsystemd-network/test-dhcp-client.c +++ /dev/null @@ -1,513 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include - -#include "sd-dhcp-client.h" -#include "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_ifindex(NULL, 0) == -EINVAL); - - assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); - assert_se(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); - assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0); - - assert_se(sd_dhcp_client_set_request_option(client, - 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_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); - - assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0); - - 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_ifindex(client, 42) >= 0); - assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); - - assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0); - - 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 deleted file mode 100644 index d84859c053..0000000000 --- a/src/libsystemd-network/test-dhcp-option.c +++ /dev/null @@ -1,367 +0,0 @@ -#include -#include -#include -#include - -#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); - - memcpy_safe(&message->options, options, optlen); - memcpy_safe(&message->file, file, filelen); - memcpy_safe(&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 deleted file mode 100644 index e81c508c7f..0000000000 --- a/src/libsystemd-network/test-dhcp-server.c +++ /dev/null @@ -1,261 +0,0 @@ -/*** - 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 . -***/ - -#include - -#include "sd-dhcp-server.h" -#include "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 deleted file mode 100644 index bd289fa802..0000000000 --- a/src/libsystemd-network/test-dhcp6-client.c +++ /dev/null @@ -1,763 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include - -#include "sd-dhcp6-client.h" -#include "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_ifindex(client, 15) == 0); - assert_se(sd_dhcp6_client_set_ifindex(client, -42) == -EINVAL); - assert_se(sd_dhcp6_client_set_ifindex(client, -1) == 0); - assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0); - - assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, - sizeof (mac_addr), - ARPHRD_ETHER) >= 0); - - assert_se(sd_dhcp6_client_set_request_option(client, 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_ifindex(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 deleted file mode 100644 index 2b1387fa91..0000000000 --- a/src/libsystemd-network/test-ipv4ll-manual.c +++ /dev/null @@ -1,128 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Tom Gundersen - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include - -#include "sd-event.h" -#include "sd-ipv4ll.h" -#include "sd-netlink.h" - -#include "alloc-util.h" -#include "in-addr-util.h" -#include "netlink-util.h" -#include "parse-util.h" -#include "string-util.h" -#include "util.h" - -static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) { - _cleanup_free_ char *address = NULL; - struct in_addr addr = {}; - - assert_se(ll); - - if (sd_ipv4ll_get_address(ll, &addr) >= 0) - assert_se(in_addr_to_string(AF_INET, (const union in_addr_union*) &addr, &address) >= 0); - - switch (event) { - case SD_IPV4LL_EVENT_BIND: - log_info("bound %s", strna(address)); - break; - case SD_IPV4LL_EVENT_CONFLICT: - log_info("conflict on %s", strna(address)); - break; - case SD_IPV4LL_EVENT_STOP: - log_error("the client was stopped with address %s", strna(address)); - break; - default: - assert_not_reached("invalid LL event"); - } -} - -static int client_run(int ifindex, const char *seed_str, const struct ether_addr *ha, sd_event *e) { - sd_ipv4ll *ll; - - assert_se(sd_ipv4ll_new(&ll) >= 0); - assert_se(sd_ipv4ll_attach_event(ll, e, 0) >= 0); - - assert_se(sd_ipv4ll_set_ifindex(ll, ifindex) >= 0); - assert_se(sd_ipv4ll_set_mac(ll, ha) >= 0); - assert_se(sd_ipv4ll_set_callback(ll, ll_handler, NULL) >= 0); - - if (seed_str) { - unsigned seed; - - assert_se(safe_atou(seed_str, &seed) >= 0); - - assert_se(sd_ipv4ll_set_address_seed(ll, seed) >= 0); - } - - log_info("starting IPv4LL client"); - - assert_se(sd_ipv4ll_start(ll) >= 0); - - assert_se(sd_event_loop(e) >= 0); - - assert_se(!sd_ipv4ll_unref(ll)); - - return EXIT_SUCCESS; -} - -static int test_ll(const char *ifname, const char *seed) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; - _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; - struct ether_addr ha; - int ifindex; - - assert_se(sd_event_new(&e) >= 0); - - assert_se(sd_netlink_open(&rtnl) >= 0); - assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); - - assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); - assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); - assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); - - assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); - assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); - - client_run(ifindex, seed, &ha, e); - - return EXIT_SUCCESS; -} - -int main(int argc, char *argv[]) { - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - log_open(); - - if (argc == 2) - return test_ll(argv[1], NULL); - else if (argc == 3) - return test_ll(argv[1], argv[2]); - else { - log_error("This program takes one or two arguments.\n" - "\t %s []", program_invocation_short_name); - return EXIT_FAILURE; - } -} diff --git a/src/libsystemd-network/test-ipv4ll.c b/src/libsystemd-network/test-ipv4ll.c deleted file mode 100644 index fe70697075..0000000000 --- a/src/libsystemd-network/test-ipv4ll.c +++ /dev/null @@ -1,221 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Axis Communications AB. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include -#include -#include -#include -#include -#include - -#include "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 = {}; - uint64_t 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_ifindex(NULL, -1) == -EINVAL); - assert_se(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL); - assert_se(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL); - assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0); - assert_se(sd_ipv4ll_set_ifindex(ll, 99) == 0); - - assert_se(sd_ipv4ll_ref(ll) == ll); - assert_se(sd_ipv4ll_unref(ll) == 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_ifindex(ll, 1) == 0); - assert_se(sd_ipv4ll_start(ll) == 0); - - sd_event_run(e, (uint64_t) -1); - assert_se(sd_ipv4ll_start(ll) == -EBUSY); - - assert_se(sd_ipv4ll_is_running(ll)); - - /* PROBE */ - sd_event_run(e, (uint64_t) -1); - assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); - - if (extended) { - /* PROBE */ - sd_event_run(e, (uint64_t) -1); - assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); - - /* PROBE */ - sd_event_run(e, (uint64_t) -1); - assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); - - sd_event_run(e, (uint64_t) -1); - assert_se(basic_request_handler_bind == 1); - } - - 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 deleted file mode 100644 index 6bcd65de0a..0000000000 --- a/src/libsystemd-network/test-lldp.c +++ /dev/null @@ -1,261 +0,0 @@ -/*** - 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 . -***/ - -#include -#include -#include -#include -#include - -#include "sd-event.h" -#include "sd-lldp.h" - -#include "alloc-util.h" -#include "fd-util.h" -#include "lldp-network.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] = { -1, -1 }; -static int lldp_handler_calls; - -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 void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) { - lldp_handler_calls++; -} - -static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) { - int r; - - r = sd_lldp_new(lldp); - if (r < 0) - return r; - - r = sd_lldp_set_ifindex(*lldp, 42); - if (r < 0) - return r; - - r = sd_lldp_set_callback(*lldp, cb, cb_data); - if (r < 0) - return r; - - r = sd_lldp_attach_event(*lldp, e, 0); - if (r < 0) - return r; - - r = sd_lldp_start(*lldp); - if (r < 0) - return r; - - return 0; -} - -static int stop_lldp(sd_lldp *lldp) { - int r; - - r = sd_lldp_stop(lldp); - if (r < 0) - return r; - - r = sd_lldp_detach_event(lldp); - if (r < 0) - return r; - - sd_lldp_unref(lldp); - safe_close(test_fd[1]); - - return 0; -} - -static void test_receive_basic_packet(sd_event *e) { - - static const uint8_t frame[] = { - /* Ethernet header */ - 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ - 0x88, 0xcc, /* Ethertype */ - /* LLDP mandatory TLVs */ - 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ - 0x03, 0x04, 0x05, - 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ - 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds*/ - /* LLDP optional TLVs */ - 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, /* Port Description: "Port" */ - 0x0a, 0x03, 0x53, 0x59, 0x53, /* System Name: "SYS" */ - 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, /* System Description: "foo" (NULL-terminated) */ - 0x00, 0x00 /* End Of LLDPDU */ - }; - - sd_lldp *lldp; - sd_lldp_neighbor **neighbors; - uint8_t type; - const void *data; - uint16_t ttl; - size_t length; - const char *str; - - lldp_handler_calls = 0; - assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); - - assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); - sd_event_run(e, 0); - assert_se(lldp_handler_calls == 1); - assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1); - - assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0); - assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS); - assert_se(length == ETH_ALEN); - assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN)); - - assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0); - assert_se(type == SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME); - assert_se(length == 3); - assert_se(strneq((char *) data, "1/3", 3)); - - assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0); - assert_se(streq(str, "Port")); - - assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0); - assert_se(streq(str, "SYS")); - - assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0); - assert_se(streq(str, "foo")); - - assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0); - assert_se(ttl == 120); - - sd_lldp_neighbor_unref(neighbors[0]); - free(neighbors); - - assert_se(stop_lldp(lldp) == 0); -} - -static void test_receive_incomplete_packet(sd_event *e) { - sd_lldp *lldp; - sd_lldp_neighbor **neighbors; - uint8_t frame[] = { - /* Ethernet header */ - 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ - 0x88, 0xcc, /* Ethertype */ - /* LLDP mandatory TLVs */ - 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ - 0x03, 0x04, 0x05, - 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ - /* Missing TTL */ - 0x00, 0x00 /* End Of LLDPDU */ - }; - - 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_neighbors(lldp, &neighbors) == 0); - - assert_se(stop_lldp(lldp) == 0); -} - -static void test_receive_oui_packet(sd_event *e) { - sd_lldp *lldp; - sd_lldp_neighbor **neighbors; - uint8_t frame[] = { - /* Ethernet header */ - 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ - 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ - 0x88, 0xcc, /* Ethertype */ - /* LLDP mandatory TLVs */ - 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ - 0x03, 0x04, 0x05, - 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port TLV: interface name, "1/3" */ - 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds*/ - /* LLDP optional TLVs */ - 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, /* Port VLAN ID: 0x1234 */ - 0x12, 0x34, - 0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* Port and protocol: flag 1, PPVID 0x7788 */ - 0x01, 0x77, 0x88, - 0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03, /* VLAN Name: ID 0x1234, name "Vlan51" */ - 0x12, 0x34, 0x06, 0x56, 0x6c, 0x61, - 0x6e, 0x35, 0x31, - 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06, /* Management VID: 0x0102 */ - 0x01, 0x02, - 0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07, /* Link aggregation: status 1, ID 0x00140012 */ - 0x01, 0x00, 0x14, 0x00, 0x12, - 0x00, 0x00 /* End of LLDPDU */ - }; - - lldp_handler_calls = 0; - assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); - - assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); - sd_event_run(e, 0); - assert_se(lldp_handler_calls == 1); - assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1); - - assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0); - assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_CHASSIS_ID) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_PORT_ID) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_TTL) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); - assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_END) > 0); - assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0); - - sd_lldp_neighbor_unref(neighbors[0]); - free(neighbors); - - assert_se(stop_lldp(lldp) == 0); -} - -int main(int argc, char *argv[]) { - _cleanup_(sd_event_unrefp) sd_event *e = NULL; - - log_set_max_level(LOG_DEBUG); - - /* 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 deleted file mode 100644 index d9669488be..0000000000 --- a/src/libsystemd-network/test-ndisc-rs.c +++ /dev/null @@ -1,317 +0,0 @@ -/*** - This file is part of systemd. - - Copyright (C) 2014 Intel Corporation. All rights reserved. - - systemd is free software; you can redistribute it and/or modify it - under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation; either version 2.1 of the License, or - (at your option) any later version. - - systemd is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with systemd; If not, see . -***/ - -#include -#include - -#include "sd-ndisc.h" - -#include "alloc-util.h" -#include "hexdecoct.h" -#include "icmp6-util.h" -#include "socket-util.h" -#include "strv.h" - -static struct ether_addr mac_addr = { - .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} -}; - -static bool verbose = false; -static sd_event_source *test_hangcheck; -static int test_fd[2]; - -typedef int (*send_ra_t)(uint8_t flags); -static send_ra_t send_ra_function; - -static void router_dump(sd_ndisc_router *rt) { - struct in6_addr addr; - char buf[FORMAT_TIMESTAMP_MAX]; - uint8_t hop_limit; - uint64_t t, flags; - uint32_t mtu; - uint16_t lifetime; - unsigned preference; - int r; - - assert_se(rt); - - log_info("--"); - assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA); - - assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0); - log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), t)); - - assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0); - log_info("Monotonic: %" PRIu64, t); - - if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0) - log_info("No hop limit set"); - else - log_info("Hop limit: %u", hop_limit); - - assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); - log_info("Flags: <%s|%s>", - flags & ND_RA_FLAG_OTHER ? "OTHER" : "", - flags & ND_RA_FLAG_MANAGED ? "MANAGED" : ""); - - assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0); - log_info("Preference: %s", - preference == SD_NDISC_PREFERENCE_LOW ? "low" : - preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium"); - - assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0); - log_info("Lifetime: %" PRIu16, lifetime); - - if (sd_ndisc_router_get_mtu(rt, &mtu) < 0) - log_info("No MTU set"); - else - log_info("MTU: %" PRIu32, mtu); - - r = sd_ndisc_router_option_rewind(rt); - for (;;) { - uint8_t type; - - assert_se(r >= 0); - - if (r == 0) - break; - - assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0); - - log_info(">> Option %u", type); - - switch (type) { - - case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: - case SD_NDISC_OPTION_TARGET_LL_ADDRESS: { - _cleanup_free_ char *c = NULL; - const void *p; - size_t n; - - assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0); - assert_se(n > 2); - assert_se(c = hexmem((uint8_t*) p + 2, n - 2)); - - log_info("Address: %s", c); - break; - } - - case SD_NDISC_OPTION_PREFIX_INFORMATION: { - uint32_t lifetime_valid, lifetime_preferred; - unsigned prefix_len; - uint8_t pfl; - struct in6_addr a; - char buff[INET6_ADDRSTRLEN]; - - assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0); - log_info("Valid Lifetime: %" PRIu32, lifetime_valid); - - assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0); - log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred); - - assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0); - log_info("Flags: <%s|%s>", - pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "", - pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : ""); - - assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0); - log_info("Prefix Length: %u", prefix_len); - - assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0); - log_info("Prefix: %s", inet_ntop(AF_INET6, &a, buff, sizeof(buff))); - - break; - } - - case SD_NDISC_OPTION_RDNSS: { - const struct in6_addr *a; - uint32_t lt; - int n, i; - - n = sd_ndisc_router_rdnss_get_addresses(rt, &a); - assert_se(n > 0); - - for (i = 0; i < n; i++) { - char buff[INET6_ADDRSTRLEN]; - log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff))); - } - - assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, <) >= 0); - log_info("Lifetime: %" PRIu32, lt); - break; - } - - case SD_NDISC_OPTION_DNSSL: { - _cleanup_strv_free_ char **l = NULL; - uint32_t lt; - int n, i; - - n = sd_ndisc_router_dnssl_get_domains(rt, &l); - assert_se(n > 0); - - for (i = 0; i < n; i++) - log_info("Domain: %s", l[i]); - - assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, <) >= 0); - log_info("Lifetime: %" PRIu32, lt); - break; - }} - - r = sd_ndisc_router_option_next(rt); - } -} - -static int test_rs_hangcheck(sd_event_source *s, uint64_t usec, - void *userdata) { - assert_se(false); - - return 0; -} - -int icmp6_bind_router_solicitation(int index) { - assert_se(index == 42); - - if (socketpair(AF_UNIX, SOCK_DGRAM, 0, test_fd) < 0) - return -errno; - - return test_fd[0]; -} - -static int send_ra(uint8_t flags) { - uint8_t advertisement[] = { - 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, - 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, - 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, - 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, - }; - - advertisement[5] = flags; - - assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == - sizeof(advertisement)); - - if (verbose) - printf(" sent RA with flag 0x%02x\n", flags); - - return 0; -} - -int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { - return send_ra_function(0); -} - -static void test_callback(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) { - sd_event *e = userdata; - static unsigned idx = 0; - uint64_t flags_array[] = { - 0, - 0, - 0, - ND_RA_FLAG_OTHER, - ND_RA_FLAG_MANAGED - }; - uint64_t flags; - uint32_t mtu; - - assert_se(nd); - - if (event != SD_NDISC_EVENT_ROUTER) - return; - - router_dump(rt); - - assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); - assert_se(flags == flags_array[idx]); - idx++; - - if (verbose) - printf(" got event 0x%02" PRIx64 "\n", flags); - - if (idx < ELEMENTSOF(flags_array)) { - send_ra(flags_array[idx]); - return; - } - - assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA); - - sd_event_exit(e, 0); -} - -static void test_rs(void) { - sd_event *e; - sd_ndisc *nd; - usec_t time_now = now(clock_boottime_or_monotonic()); - - if (verbose) - printf("* %s\n", __FUNCTION__); - - send_ra_function = send_ra; - - assert_se(sd_event_new(&e) >= 0); - - assert_se(sd_ndisc_new(&nd) >= 0); - assert_se(nd); - - assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); - - assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); - assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); - assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0); - - assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(), - time_now + 2 *USEC_PER_SEC, 0, - test_rs_hangcheck, NULL) >= 0); - - assert_se(sd_ndisc_stop(nd) >= 0); - assert_se(sd_ndisc_start(nd) >= 0); - assert_se(sd_ndisc_stop(nd) >= 0); - - assert_se(sd_ndisc_start(nd) >= 0); - - sd_event_loop(e); - - test_hangcheck = sd_event_source_unref(test_hangcheck); - - nd = sd_ndisc_unref(nd); - assert_se(!nd); - - close(test_fd[1]); - - sd_event_unref(e); -} - -int main(int argc, char *argv[]) { - - log_set_max_level(LOG_DEBUG); - log_parse_environment(); - log_open(); - - test_rs(); - - return 0; -} diff --git a/src/libsystemd-network/test/GNUmakefile b/src/libsystemd-network/test/GNUmakefile new file mode 120000 index 0000000000..95e5924740 --- /dev/null +++ b/src/libsystemd-network/test/GNUmakefile @@ -0,0 +1 @@ +../../../GNUmakefile \ No newline at end of file diff --git a/src/libsystemd-network/test/Makefile b/src/libsystemd-network/test/Makefile new file mode 100644 index 0000000000..8cc38bb547 --- /dev/null +++ b/src/libsystemd-network/test/Makefile @@ -0,0 +1,118 @@ +# -*- 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 . +include $(dir $(lastword $(MAKEFILE_LIST)))/../../../config.mk +include $(topsrcdir)/build-aux/Makefile.head.mk + +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 \ + libsystemd-shared.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 \ + libsystemd-shared.la + +test_dhcp_server_SOURCES = \ + src/libsystemd-network/test-dhcp-server.c + +test_dhcp_server_LDADD = \ + libsystemd-network.la \ + libsystemd-shared.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 \ + libsystemd-shared.la + +test_ipv4ll_manual_SOURCES = \ + src/systemd/sd-ipv4ll.h \ + src/libsystemd-network/test-ipv4ll-manual.c + +test_ipv4ll_manual_LDADD = \ + libsystemd-network.la \ + libsystemd-shared.la + +test_acd_SOURCES = \ + src/systemd/sd-ipv4acd.h \ + src/libsystemd-network/test-acd.c + +test_acd_LDADD = \ + libsystemd-network.la \ + libsystemd-shared.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 \ + libsystemd-shared.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 \ + libsystemd-shared.la + +test_lldp_SOURCES = \ + src/libsystemd-network/test-lldp.c + +test_lldp_LDADD = \ + libsystemd-network.la \ + libsystemd-shared.la + +tests += \ + test-dhcp-option \ + test-dhcp-client \ + test-dhcp-server \ + test-ipv4ll \ + test-ndisc-rs \ + test-dhcp6-client \ + test-lldp + +include $(topsrcdir)/build-aux/Makefile.tail.mk diff --git a/src/libsystemd-network/test/test-acd.c b/src/libsystemd-network/test/test-acd.c new file mode 100644 index 0000000000..26480a2012 --- /dev/null +++ b/src/libsystemd-network/test/test-acd.c @@ -0,0 +1,114 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include + +#include + +#include "sd-netlink/netlink-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/sd-ipv4acd.h" +#include "systemd-staging/sd-netlink.h" + +static void acd_handler(sd_ipv4acd *acd, int event, void *userdata) { + assert_se(acd); + + switch (event) { + case SD_IPV4ACD_EVENT_BIND: + log_info("bound"); + break; + case SD_IPV4ACD_EVENT_CONFLICT: + log_info("conflict"); + break; + case SD_IPV4ACD_EVENT_STOP: + log_error("the client was stopped"); + break; + default: + assert_not_reached("invalid ACD event"); + } +} + +static int client_run(int ifindex, const struct in_addr *pa, const struct ether_addr *ha, sd_event *e) { + sd_ipv4acd *acd; + + assert_se(sd_ipv4acd_new(&acd) >= 0); + assert_se(sd_ipv4acd_attach_event(acd, e, 0) >= 0); + + assert_se(sd_ipv4acd_set_ifindex(acd, ifindex) >= 0); + assert_se(sd_ipv4acd_set_mac(acd, ha) >= 0); + assert_se(sd_ipv4acd_set_address(acd, pa) >= 0); + assert_se(sd_ipv4acd_set_callback(acd, acd_handler, NULL) >= 0); + + log_info("starting IPv4ACD client"); + + assert_se(sd_ipv4acd_start(acd) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(!sd_ipv4acd_unref(acd)); + + return EXIT_SUCCESS; +} + +static int test_acd(const char *ifname, const char *address) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + union in_addr_union pa; + struct ether_addr ha; + int ifindex; + + assert_se(in_addr_from_string(AF_INET, address, &pa) >= 0); + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); + assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); + + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); + + client_run(ifindex, &pa.in, &ha, e); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc == 3) + return test_acd(argv[1], argv[2]); + else { + log_error("This program takes two arguments.\n" + "\t %s ", program_invocation_short_name); + return EXIT_FAILURE; + } +} diff --git a/src/libsystemd-network/test/test-dhcp-client.c b/src/libsystemd-network/test/test-dhcp-client.c new file mode 100644 index 0000000000..e58491293c --- /dev/null +++ b/src/libsystemd-network/test/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 . +***/ + +#include +#include +#include +#include + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp-identifier.h" +#include "systemd-network/dhcp-internal.h" +#include "systemd-network/dhcp-protocol.h" +#include "systemd-network/sd-dhcp-client.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_ifindex(NULL, 0) == -EINVAL); + + assert_se(sd_dhcp_client_set_ifindex(client, 15) == 0); + assert_se(sd_dhcp_client_set_ifindex(client, -42) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(client, -1) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(client, 0) == -EINVAL); + assert_se(sd_dhcp_client_set_ifindex(client, 1) == 0); + + assert_se(sd_dhcp_client_set_request_option(client, + 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_ifindex(client, 42) >= 0); + assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); + + assert_se(sd_dhcp_client_set_request_option(client, 248) >= 0); + + 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_ifindex(client, 42) >= 0); + assert_se(sd_dhcp_client_set_mac(client, mac_addr, ETH_ALEN, ARPHRD_ETHER) >= 0); + + assert_se(sd_dhcp_client_set_callback(client, test_addr_acq_acquired, e) >= 0); + + 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/test-dhcp-option.c b/src/libsystemd-network/test/test-dhcp-option.c new file mode 100644 index 0000000000..2dfbb557c0 --- /dev/null +++ b/src/libsystemd-network/test/test-dhcp-option.c @@ -0,0 +1,367 @@ +#include +#include +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/macro.h" +#include "systemd-basic/util.h" +#include "systemd-network/dhcp-internal.h" +#include "systemd-network/dhcp-protocol.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); + + memcpy_safe(&message->options, options, optlen); + memcpy_safe(&message->file, file, filelen); + memcpy_safe(&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/test-dhcp-server.c b/src/libsystemd-network/test/test-dhcp-server.c new file mode 100644 index 0000000000..0796a98707 --- /dev/null +++ b/src/libsystemd-network/test/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 . +***/ + +#include + +#include + +#include "systemd-network/dhcp-server-internal.h" +#include "systemd-network/sd-dhcp-server.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/test-dhcp6-client.c b/src/libsystemd-network/test/test-dhcp6-client.c new file mode 100644 index 0000000000..84bb9f9013 --- /dev/null +++ b/src/libsystemd-network/test/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 . +***/ + +#include +#include +#include +#include +#include +#include + +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/macro.h" +#include "systemd-basic/socket-util.h" +#include "systemd-basic/virt.h" +#include "systemd-network/dhcp6-internal.h" +#include "systemd-network/dhcp6-lease-internal.h" +#include "systemd-network/dhcp6-protocol.h" +#include "systemd-network/sd-dhcp6-client.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_ifindex(client, 15) == 0); + assert_se(sd_dhcp6_client_set_ifindex(client, -42) == -EINVAL); + assert_se(sd_dhcp6_client_set_ifindex(client, -1) == 0); + assert_se(sd_dhcp6_client_set_ifindex(client, 42) >= 0); + + assert_se(sd_dhcp6_client_set_mac(client, (const uint8_t *) &mac_addr, + sizeof (mac_addr), + ARPHRD_ETHER) >= 0); + + assert_se(sd_dhcp6_client_set_request_option(client, 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_ifindex(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/test-ipv4ll-manual.c b/src/libsystemd-network/test/test-ipv4ll-manual.c new file mode 100644 index 0000000000..f67a8fe16c --- /dev/null +++ b/src/libsystemd-network/test/test-ipv4ll-manual.c @@ -0,0 +1,129 @@ +/*** + This file is part of systemd. + + Copyright (C) 2014 Tom Gundersen + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation; either version 2.1 of the License, or + (at your option) any later version. + + systemd is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with systemd; If not, see . +***/ + +#include +#include +#include +#include + +#include + +#include + +#include "sd-netlink/netlink-util.h" +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/in-addr-util.h" +#include "systemd-basic/parse-util.h" +#include "systemd-basic/string-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/sd-ipv4ll.h" +#include "systemd-staging/sd-netlink.h" + +static void ll_handler(sd_ipv4ll *ll, int event, void *userdata) { + _cleanup_free_ char *address = NULL; + struct in_addr addr = {}; + + assert_se(ll); + + if (sd_ipv4ll_get_address(ll, &addr) >= 0) + assert_se(in_addr_to_string(AF_INET, (const union in_addr_union*) &addr, &address) >= 0); + + switch (event) { + case SD_IPV4LL_EVENT_BIND: + log_info("bound %s", strna(address)); + break; + case SD_IPV4LL_EVENT_CONFLICT: + log_info("conflict on %s", strna(address)); + break; + case SD_IPV4LL_EVENT_STOP: + log_error("the client was stopped with address %s", strna(address)); + break; + default: + assert_not_reached("invalid LL event"); + } +} + +static int client_run(int ifindex, const char *seed_str, const struct ether_addr *ha, sd_event *e) { + sd_ipv4ll *ll; + + assert_se(sd_ipv4ll_new(&ll) >= 0); + assert_se(sd_ipv4ll_attach_event(ll, e, 0) >= 0); + + assert_se(sd_ipv4ll_set_ifindex(ll, ifindex) >= 0); + assert_se(sd_ipv4ll_set_mac(ll, ha) >= 0); + assert_se(sd_ipv4ll_set_callback(ll, ll_handler, NULL) >= 0); + + if (seed_str) { + unsigned seed; + + assert_se(safe_atou(seed_str, &seed) >= 0); + + assert_se(sd_ipv4ll_set_address_seed(ll, seed) >= 0); + } + + log_info("starting IPv4LL client"); + + assert_se(sd_ipv4ll_start(ll) >= 0); + + assert_se(sd_event_loop(e) >= 0); + + assert_se(!sd_ipv4ll_unref(ll)); + + return EXIT_SUCCESS; +} + +static int test_ll(const char *ifname, const char *seed) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + struct ether_addr ha; + int ifindex; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_netlink_open(&rtnl) >= 0); + assert_se(sd_netlink_attach_event(rtnl, e, 0) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, 0) >= 0); + assert_se(sd_netlink_message_append_string(m, IFLA_IFNAME, ifname) >= 0); + assert_se(sd_netlink_call(rtnl, m, 0, &reply) >= 0); + + assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); + assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &ha) >= 0); + + client_run(ifindex, seed, &ha, e); + + return EXIT_SUCCESS; +} + +int main(int argc, char *argv[]) { + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + if (argc == 2) + return test_ll(argv[1], NULL); + else if (argc == 3) + return test_ll(argv[1], argv[2]); + else { + log_error("This program takes one or two arguments.\n" + "\t %s []", program_invocation_short_name); + return EXIT_FAILURE; + } +} diff --git a/src/libsystemd-network/test/test-ipv4ll.c b/src/libsystemd-network/test/test-ipv4ll.c new file mode 100644 index 0000000000..58912a1bf5 --- /dev/null +++ b/src/libsystemd-network/test/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 . +***/ + +#include +#include +#include +#include +#include +#include +#include + +#include "systemd-basic/fd-util.h" +#include "systemd-basic/socket-util.h" +#include "systemd-basic/util.h" +#include "systemd-network/arp-util.h" +#include "systemd-network/sd-ipv4ll.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 = {}; + uint64_t 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_ifindex(NULL, -1) == -EINVAL); + assert_se(sd_ipv4ll_set_ifindex(ll, -1) == -EINVAL); + assert_se(sd_ipv4ll_set_ifindex(ll, -99) == -EINVAL); + assert_se(sd_ipv4ll_set_ifindex(ll, 1) == 0); + assert_se(sd_ipv4ll_set_ifindex(ll, 99) == 0); + + assert_se(sd_ipv4ll_ref(ll) == ll); + assert_se(sd_ipv4ll_unref(ll) == 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_ifindex(ll, 1) == 0); + assert_se(sd_ipv4ll_start(ll) == 0); + + sd_event_run(e, (uint64_t) -1); + assert_se(sd_ipv4ll_start(ll) == -EBUSY); + + assert_se(sd_ipv4ll_is_running(ll)); + + /* PROBE */ + sd_event_run(e, (uint64_t) -1); + assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); + + if (extended) { + /* PROBE */ + sd_event_run(e, (uint64_t) -1); + assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); + + /* PROBE */ + sd_event_run(e, (uint64_t) -1); + assert_se(recv(test_fd[1], &arp, sizeof(struct ether_arp), 0) == sizeof(struct ether_arp)); + + sd_event_run(e, (uint64_t) -1); + assert_se(basic_request_handler_bind == 1); + } + + 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/test-lldp.c b/src/libsystemd-network/test/test-lldp.c new file mode 100644 index 0000000000..27b6711916 --- /dev/null +++ b/src/libsystemd-network/test/test-lldp.c @@ -0,0 +1,261 @@ +/*** + 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 . +***/ + +#include +#include +#include +#include +#include + +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/fd-util.h" +#include "systemd-basic/macro.h" +#include "systemd-basic/string-util.h" +#include "systemd-network/lldp-network.h" +#include "systemd-network/sd-lldp.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] = { -1, -1 }; +static int lldp_handler_calls; + +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 void lldp_handler(sd_lldp *lldp, sd_lldp_event event, sd_lldp_neighbor *n, void *userdata) { + lldp_handler_calls++; +} + +static int start_lldp(sd_lldp **lldp, sd_event *e, sd_lldp_callback_t cb, void *cb_data) { + int r; + + r = sd_lldp_new(lldp); + if (r < 0) + return r; + + r = sd_lldp_set_ifindex(*lldp, 42); + if (r < 0) + return r; + + r = sd_lldp_set_callback(*lldp, cb, cb_data); + if (r < 0) + return r; + + r = sd_lldp_attach_event(*lldp, e, 0); + if (r < 0) + return r; + + r = sd_lldp_start(*lldp); + if (r < 0) + return r; + + return 0; +} + +static int stop_lldp(sd_lldp *lldp) { + int r; + + r = sd_lldp_stop(lldp); + if (r < 0) + return r; + + r = sd_lldp_detach_event(lldp); + if (r < 0) + return r; + + sd_lldp_unref(lldp); + safe_close(test_fd[1]); + + return 0; +} + +static void test_receive_basic_packet(sd_event *e) { + + static const uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ + 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds*/ + /* LLDP optional TLVs */ + 0x08, 0x04, 0x50, 0x6f, 0x72, 0x74, /* Port Description: "Port" */ + 0x0a, 0x03, 0x53, 0x59, 0x53, /* System Name: "SYS" */ + 0x0c, 0x04, 0x66, 0x6f, 0x6f, 0x00, /* System Description: "foo" (NULL-terminated) */ + 0x00, 0x00 /* End Of LLDPDU */ + }; + + sd_lldp *lldp; + sd_lldp_neighbor **neighbors; + uint8_t type; + const void *data; + uint16_t ttl; + size_t length; + const char *str; + + lldp_handler_calls = 0; + assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); + + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(lldp_handler_calls == 1); + assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1); + + assert_se(sd_lldp_neighbor_get_chassis_id(neighbors[0], &type, &data, &length) == 0); + assert_se(type == SD_LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS); + assert_se(length == ETH_ALEN); + assert_se(!memcmp(data, "\x00\x01\x02\x03\x04\x05", ETH_ALEN)); + + assert_se(sd_lldp_neighbor_get_port_id(neighbors[0], &type, &data, &length) == 0); + assert_se(type == SD_LLDP_PORT_SUBTYPE_INTERFACE_NAME); + assert_se(length == 3); + assert_se(strneq((char *) data, "1/3", 3)); + + assert_se(sd_lldp_neighbor_get_port_description(neighbors[0], &str) == 0); + assert_se(streq(str, "Port")); + + assert_se(sd_lldp_neighbor_get_system_name(neighbors[0], &str) == 0); + assert_se(streq(str, "SYS")); + + assert_se(sd_lldp_neighbor_get_system_description(neighbors[0], &str) == 0); + assert_se(streq(str, "foo")); + + assert_se(sd_lldp_neighbor_get_ttl(neighbors[0], &ttl) == 0); + assert_se(ttl == 120); + + sd_lldp_neighbor_unref(neighbors[0]); + free(neighbors); + + assert_se(stop_lldp(lldp) == 0); +} + +static void test_receive_incomplete_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_neighbor **neighbors; + uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port: interface name, "1/3" */ + /* Missing TTL */ + 0x00, 0x00 /* End Of LLDPDU */ + }; + + 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_neighbors(lldp, &neighbors) == 0); + + assert_se(stop_lldp(lldp) == 0); +} + +static void test_receive_oui_packet(sd_event *e) { + sd_lldp *lldp; + sd_lldp_neighbor **neighbors; + uint8_t frame[] = { + /* Ethernet header */ + 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03, /* Destination MAC*/ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, /* Source MAC */ + 0x88, 0xcc, /* Ethertype */ + /* LLDP mandatory TLVs */ + 0x02, 0x07, 0x04, 0x00, 0x01, 0x02, /* Chassis: MAC, 00:01:02:03:04:05 */ + 0x03, 0x04, 0x05, + 0x04, 0x04, 0x05, 0x31, 0x2f, 0x33, /* Port TLV: interface name, "1/3" */ + 0x06, 0x02, 0x00, 0x78, /* TTL: 120 seconds*/ + /* LLDP optional TLVs */ + 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x01, /* Port VLAN ID: 0x1234 */ + 0x12, 0x34, + 0xfe, 0x07, 0x00, 0x80, 0xc2, 0x02, /* Port and protocol: flag 1, PPVID 0x7788 */ + 0x01, 0x77, 0x88, + 0xfe, 0x0d, 0x00, 0x80, 0xc2, 0x03, /* VLAN Name: ID 0x1234, name "Vlan51" */ + 0x12, 0x34, 0x06, 0x56, 0x6c, 0x61, + 0x6e, 0x35, 0x31, + 0xfe, 0x06, 0x00, 0x80, 0xc2, 0x06, /* Management VID: 0x0102 */ + 0x01, 0x02, + 0xfe, 0x09, 0x00, 0x80, 0xc2, 0x07, /* Link aggregation: status 1, ID 0x00140012 */ + 0x01, 0x00, 0x14, 0x00, 0x12, + 0x00, 0x00 /* End of LLDPDU */ + }; + + lldp_handler_calls = 0; + assert_se(start_lldp(&lldp, e, lldp_handler, NULL) == 0); + + assert_se(write(test_fd[1], frame, sizeof(frame)) == sizeof(frame)); + sd_event_run(e, 0); + assert_se(lldp_handler_calls == 1); + assert_se(sd_lldp_get_neighbors(lldp, &neighbors) == 1); + + assert_se(sd_lldp_neighbor_tlv_rewind(neighbors[0]) >= 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_CHASSIS_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_PORT_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_TTL) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_VLAN_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_PORT_PROTOCOL_VLAN_ID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_VLAN_NAME) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_MANAGEMENT_VID) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_oui(neighbors[0], SD_LLDP_OUI_802_1, SD_LLDP_OUI_802_1_SUBTYPE_LINK_AGGREGATION) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) > 0); + assert_se(sd_lldp_neighbor_tlv_is_type(neighbors[0], SD_LLDP_TYPE_END) > 0); + assert_se(sd_lldp_neighbor_tlv_next(neighbors[0]) == 0); + + sd_lldp_neighbor_unref(neighbors[0]); + free(neighbors); + + assert_se(stop_lldp(lldp) == 0); +} + +int main(int argc, char *argv[]) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + + log_set_max_level(LOG_DEBUG); + + /* 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/test-ndisc-rs.c b/src/libsystemd-network/test/test-ndisc-rs.c new file mode 100644 index 0000000000..eb2dad5ab8 --- /dev/null +++ b/src/libsystemd-network/test/test-ndisc-rs.c @@ -0,0 +1,316 @@ +/*** + 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 . +***/ + +#include +#include + +#include "systemd-basic/alloc-util.h" +#include "systemd-basic/hexdecoct.h" +#include "systemd-basic/socket-util.h" +#include "systemd-basic/strv.h" +#include "systemd-network/icmp6-util.h" +#include "systemd-network/sd-ndisc.h" + +static struct ether_addr mac_addr = { + .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'} +}; + +static bool verbose = false; +static sd_event_source *test_hangcheck; +static int test_fd[2]; + +typedef int (*send_ra_t)(uint8_t flags); +static send_ra_t send_ra_function; + +static void router_dump(sd_ndisc_router *rt) { + struct in6_addr addr; + char buf[FORMAT_TIMESTAMP_MAX]; + uint8_t hop_limit; + uint64_t t, flags; + uint32_t mtu; + uint16_t lifetime; + unsigned preference; + int r; + + assert_se(rt); + + log_info("--"); + assert_se(sd_ndisc_router_get_address(rt, &addr) == -ENODATA); + + assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_REALTIME, &t) >= 0); + log_info("Timestamp: %s", format_timestamp(buf, sizeof(buf), t)); + + assert_se(sd_ndisc_router_get_timestamp(rt, CLOCK_MONOTONIC, &t) >= 0); + log_info("Monotonic: %" PRIu64, t); + + if (sd_ndisc_router_get_hop_limit(rt, &hop_limit) < 0) + log_info("No hop limit set"); + else + log_info("Hop limit: %u", hop_limit); + + assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); + log_info("Flags: <%s|%s>", + flags & ND_RA_FLAG_OTHER ? "OTHER" : "", + flags & ND_RA_FLAG_MANAGED ? "MANAGED" : ""); + + assert_se(sd_ndisc_router_get_preference(rt, &preference) >= 0); + log_info("Preference: %s", + preference == SD_NDISC_PREFERENCE_LOW ? "low" : + preference == SD_NDISC_PREFERENCE_HIGH ? "high" : "medium"); + + assert_se(sd_ndisc_router_get_lifetime(rt, &lifetime) >= 0); + log_info("Lifetime: %" PRIu16, lifetime); + + if (sd_ndisc_router_get_mtu(rt, &mtu) < 0) + log_info("No MTU set"); + else + log_info("MTU: %" PRIu32, mtu); + + r = sd_ndisc_router_option_rewind(rt); + for (;;) { + uint8_t type; + + assert_se(r >= 0); + + if (r == 0) + break; + + assert_se(sd_ndisc_router_option_get_type(rt, &type) >= 0); + + log_info(">> Option %u", type); + + switch (type) { + + case SD_NDISC_OPTION_SOURCE_LL_ADDRESS: + case SD_NDISC_OPTION_TARGET_LL_ADDRESS: { + _cleanup_free_ char *c = NULL; + const void *p; + size_t n; + + assert_se(sd_ndisc_router_option_get_raw(rt, &p, &n) >= 0); + assert_se(n > 2); + assert_se(c = hexmem((uint8_t*) p + 2, n - 2)); + + log_info("Address: %s", c); + break; + } + + case SD_NDISC_OPTION_PREFIX_INFORMATION: { + uint32_t lifetime_valid, lifetime_preferred; + unsigned prefix_len; + uint8_t pfl; + struct in6_addr a; + char buff[INET6_ADDRSTRLEN]; + + assert_se(sd_ndisc_router_prefix_get_valid_lifetime(rt, &lifetime_valid) >= 0); + log_info("Valid Lifetime: %" PRIu32, lifetime_valid); + + assert_se(sd_ndisc_router_prefix_get_preferred_lifetime(rt, &lifetime_preferred) >= 0); + log_info("Preferred Lifetime: %" PRIu32, lifetime_preferred); + + assert_se(sd_ndisc_router_prefix_get_flags(rt, &pfl) >= 0); + log_info("Flags: <%s|%s>", + pfl & ND_OPT_PI_FLAG_ONLINK ? "ONLINK" : "", + pfl & ND_OPT_PI_FLAG_AUTO ? "AUTO" : ""); + + assert_se(sd_ndisc_router_prefix_get_prefixlen(rt, &prefix_len) >= 0); + log_info("Prefix Length: %u", prefix_len); + + assert_se(sd_ndisc_router_prefix_get_address(rt, &a) >= 0); + log_info("Prefix: %s", inet_ntop(AF_INET6, &a, buff, sizeof(buff))); + + break; + } + + case SD_NDISC_OPTION_RDNSS: { + const struct in6_addr *a; + uint32_t lt; + int n, i; + + n = sd_ndisc_router_rdnss_get_addresses(rt, &a); + assert_se(n > 0); + + for (i = 0; i < n; i++) { + char buff[INET6_ADDRSTRLEN]; + log_info("DNS: %s", inet_ntop(AF_INET6, a + i, buff, sizeof(buff))); + } + + assert_se(sd_ndisc_router_rdnss_get_lifetime(rt, <) >= 0); + log_info("Lifetime: %" PRIu32, lt); + break; + } + + case SD_NDISC_OPTION_DNSSL: { + _cleanup_strv_free_ char **l = NULL; + uint32_t lt; + int n, i; + + n = sd_ndisc_router_dnssl_get_domains(rt, &l); + assert_se(n > 0); + + for (i = 0; i < n; i++) + log_info("Domain: %s", l[i]); + + assert_se(sd_ndisc_router_dnssl_get_lifetime(rt, <) >= 0); + log_info("Lifetime: %" PRIu32, lt); + break; + }} + + r = sd_ndisc_router_option_next(rt); + } +} + +static int test_rs_hangcheck(sd_event_source *s, uint64_t usec, + void *userdata) { + assert_se(false); + + return 0; +} + +int icmp6_bind_router_solicitation(int index) { + assert_se(index == 42); + + if (socketpair(AF_UNIX, SOCK_DGRAM, 0, test_fd) < 0) + return -errno; + + return test_fd[0]; +} + +static int send_ra(uint8_t flags) { + uint8_t advertisement[] = { + 0x86, 0x00, 0xde, 0x83, 0x40, 0xc0, 0x00, 0xb4, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x03, 0x04, 0x40, 0xc0, 0x00, 0x00, 0x01, 0xf4, + 0x00, 0x00, 0x01, 0xb8, 0x00, 0x00, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x19, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x20, 0x01, 0x0d, 0xb8, 0xde, 0xad, 0xbe, 0xef, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, + 0x1f, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, + 0x03, 0x6c, 0x61, 0x62, 0x05, 0x69, 0x6e, 0x74, + 0x72, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x01, 0x78, 0x2b, 0xcb, 0xb3, 0x6d, 0x53, + }; + + advertisement[5] = flags; + + assert_se(write(test_fd[1], advertisement, sizeof(advertisement)) == + sizeof(advertisement)); + + if (verbose) + printf(" sent RA with flag 0x%02x\n", flags); + + return 0; +} + +int icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr) { + return send_ra_function(0); +} + +static void test_callback(sd_ndisc *nd, sd_ndisc_event event, sd_ndisc_router *rt, void *userdata) { + sd_event *e = userdata; + static unsigned idx = 0; + uint64_t flags_array[] = { + 0, + 0, + 0, + ND_RA_FLAG_OTHER, + ND_RA_FLAG_MANAGED + }; + uint64_t flags; + uint32_t mtu; + + assert_se(nd); + + if (event != SD_NDISC_EVENT_ROUTER) + return; + + router_dump(rt); + + assert_se(sd_ndisc_router_get_flags(rt, &flags) >= 0); + assert_se(flags == flags_array[idx]); + idx++; + + if (verbose) + printf(" got event 0x%02" PRIx64 "\n", flags); + + if (idx < ELEMENTSOF(flags_array)) { + send_ra(flags_array[idx]); + return; + } + + assert_se(sd_ndisc_get_mtu(nd, &mtu) == -ENODATA); + + sd_event_exit(e, 0); +} + +static void test_rs(void) { + sd_event *e; + sd_ndisc *nd; + usec_t time_now = now(clock_boottime_or_monotonic()); + + if (verbose) + printf("* %s\n", __FUNCTION__); + + send_ra_function = send_ra; + + assert_se(sd_event_new(&e) >= 0); + + assert_se(sd_ndisc_new(&nd) >= 0); + assert_se(nd); + + assert_se(sd_ndisc_attach_event(nd, e, 0) >= 0); + + assert_se(sd_ndisc_set_ifindex(nd, 42) >= 0); + assert_se(sd_ndisc_set_mac(nd, &mac_addr) >= 0); + assert_se(sd_ndisc_set_callback(nd, test_callback, e) >= 0); + + assert_se(sd_event_add_time(e, &test_hangcheck, clock_boottime_or_monotonic(), + time_now + 2 *USEC_PER_SEC, 0, + test_rs_hangcheck, NULL) >= 0); + + assert_se(sd_ndisc_stop(nd) >= 0); + assert_se(sd_ndisc_start(nd) >= 0); + assert_se(sd_ndisc_stop(nd) >= 0); + + assert_se(sd_ndisc_start(nd) >= 0); + + sd_event_loop(e); + + test_hangcheck = sd_event_source_unref(test_hangcheck); + + nd = sd_ndisc_unref(nd); + assert_se(!nd); + + close(test_fd[1]); + + sd_event_unref(e); +} + +int main(int argc, char *argv[]) { + + log_set_max_level(LOG_DEBUG); + log_parse_environment(); + log_open(); + + test_rs(); + + return 0; +} -- cgit v1.2.3