diff options
Diffstat (limited to '')
-rw-r--r-- | src/libsystemd-network/dhcp-network.c | 269 |
1 files changed, 269 insertions, 0 deletions
diff --git a/src/libsystemd-network/dhcp-network.c b/src/libsystemd-network/dhcp-network.c new file mode 100644 index 0000000..0ee977a --- /dev/null +++ b/src/libsystemd-network/dhcp-network.c @@ -0,0 +1,269 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +/*** + Copyright © 2013 Intel Corporation. All rights reserved. +***/ + +#include <errno.h> +#include <net/ethernet.h> +#include <net/if.h> +#include <net/if_arp.h> +#include <stdio.h> +#include <string.h> +#include <linux/filter.h> +#include <linux/if_infiniband.h> +#include <linux/if_packet.h> + +#include "dhcp-internal.h" +#include "fd-util.h" +#include "socket-util.h" +#include "unaligned.h" + +static int _bind_raw_socket( + int ifindex, + union sockaddr_union *link, + uint32_t xid, + const struct hw_addr_data *hw_addr, + const struct hw_addr_data *bcast_addr, + uint16_t arp_type, + uint16_t port) { + + assert(ifindex > 0); + assert(link); + assert(hw_addr); + assert(bcast_addr); + assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); + + switch (arp_type) { + case ARPHRD_ETHER: + assert(hw_addr->length == ETH_ALEN); + assert(bcast_addr->length == ETH_ALEN); + break; + case ARPHRD_INFINIBAND: + assert(hw_addr->length == 0); + assert(bcast_addr->length == INFINIBAND_ALEN); + break; + default: + assert_not_reached(); + } + + 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, port, 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_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_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (uint8_t) hw_addr->length, 1, 0), /* address length == hw_addr->length ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + + /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally + * compare chaddr for ETH_ALEN bytes. */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 8), /* A (the MAC address length) == ETH_ALEN ? */ + BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be32(hw_addr->bytes)), /* X <- 4 bytes of client's MAC */ + BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ + BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ + BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be16(hw_addr->bytes + 4)), /* X <- remainder of client's MAC */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ + BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ + 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, UINT32_MAX), /* accept */ + }; + struct sock_fprog fprog = { + .len = ELEMENTSOF(filter), + .filter = filter + }; + _cleanup_close_ int s = -1; + int r; + + s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true); + if (r < 0) + return r; + + r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); + if (r < 0) + return -errno; + + link->ll = (struct sockaddr_ll) { + .sll_family = AF_PACKET, + .sll_protocol = htobe16(ETH_P_IP), + .sll_ifindex = ifindex, + .sll_hatype = htobe16(arp_type), + .sll_halen = bcast_addr->length, + }; + /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */ + memcpy(link->ll.sll_addr, bcast_addr->bytes, bcast_addr->length); + + r = bind(s, &link->sa, SOCKADDR_LL_LEN(link->ll)); + if (r < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp_network_bind_raw_socket( + int ifindex, + union sockaddr_union *link, + uint32_t xid, + const struct hw_addr_data *hw_addr, + const struct hw_addr_data *bcast_addr, + uint16_t arp_type, + uint16_t port) { + + static struct hw_addr_data default_eth_bcast = { + .length = ETH_ALEN, + .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}, + }, default_ib_bcast = { + .length = INFINIBAND_ALEN, + .infiniband = { + 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff + }, + }; + + assert(ifindex > 0); + assert(link); + assert(hw_addr); + + switch (arp_type) { + case ARPHRD_ETHER: + return _bind_raw_socket(ifindex, link, xid, + hw_addr, + (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_eth_bcast, + arp_type, port); + + case ARPHRD_INFINIBAND: + return _bind_raw_socket(ifindex, link, xid, + &HW_ADDR_NULL, + (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_ib_bcast, + arp_type, port); + default: + return -EINVAL; + } +} + +int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { + 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; + + s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); + if (s < 0) + return -errno; + + if (ip_service_type >= 0) + r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type); + else + r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6); + if (r < 0) + return r; + + r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); + if (r < 0) + return r; + + if (ifindex > 0) { + r = socket_bind_to_ifindex(s, ifindex); + if (r < 0) + return r; + } + + if (port == DHCP_PORT_SERVER) { + r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true); + if (r < 0) + return r; + if (address == INADDR_ANY) { + /* IP_PKTINFO filter should not be applied when packets are + allowed to enter/leave through the interface other than + DHCP server sits on(BindToInterface option). */ + r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true); + if (r < 0) + return r; + } + } else { + r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true); + if (r < 0) + return r; + } + + if (bind(s, &src.sa, sizeof(src.in)) < 0) + return -errno; + + return TAKE_FD(s); +} + +int dhcp_network_send_raw_socket( + int s, + const union sockaddr_union *link, + const void *packet, + size_t len) { + + /* Do not add assert(s >= 0) here, as this is called in fuzz-dhcp-server, and in that case this + * function should fail with negative errno. */ + + assert(link); + assert(packet); + assert(len > 0); + + if (sendto(s, packet, len, 0, &link->sa, SOCKADDR_LL_LEN(link->ll)) < 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, + }; + + assert(s >= 0); + assert(packet); + assert(len > 0); + + if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)) < 0) + return -errno; + + return 0; +} |