├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── attach_bpf.c ├── attach_bpf.h ├── cfg.c ├── cfg.rl ├── dhcp.h ├── dhcp4.c ├── dhcp4.h ├── dhcp6.c ├── dhcp6.h ├── dhcp_state.c ├── dhcp_state.h ├── duid.c ├── duid.h ├── dynlease.c ├── dynlease.h ├── dynlease.rl ├── get_current_ts.h ├── ipaddr.h ├── multicast6.c ├── multicast6.h ├── ndhs.c ├── nk ├── hwrng.c ├── hwrng.h ├── io.c ├── io.h ├── log.h ├── net_checksum16.h ├── netbits.h ├── privs.c ├── privs.h ├── random.c └── random.h ├── nl.c ├── nl.h ├── nlsocket.c ├── nlsocket.h ├── options.c ├── options.h ├── parsehelp.h ├── radv6.c ├── radv6.h └── sbufs.h /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | nk/*.o 3 | *.d 4 | nk/*.d 5 | ndhs 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2014-2022 Nicholas J. Kain 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 17 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 18 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 19 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 20 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NDHS_C_SRCS = $(sort attach_bpf.c cfg.c dhcp4.c dhcp6.c dhcp_state.c duid.c dynlease.c multicast6.c ndhs.c nl.c nlsocket.c options.c radv6.c nk/hwrng.c nk/random.c nk/io.c nk/privs.c) 2 | NDHS_OBJS = $(NDHS_C_SRCS:.c=.o) 3 | NDHS_DEP = $(NDHS_C_SRCS:.c=.d) 4 | INCL = -I. 5 | 6 | CFLAGS = -MMD -Os -s -flto -DNDEBUG -std=gnu99 -Wall -Wextra -Wimplicit-fallthrough=0 -Wformat=2 -Wformat-nonliteral -Wformat-security -Wshadow -Wpointer-arith -Wmissing-prototypes -Wcast-qual -Wsign-conversion -Wno-discarded-qualifiers -DNDHS_BUILD 7 | #CFLAGS = -MMD -Og -g -std=gnu99 -fsanitize=address,undefined -Wall -Wextra -Wimplicit-fallthrough=0 -Wformat=2 -Wformat-nonliteral -Wformat-security -Wshadow -Wpointer-arith -Wmissing-prototypes -Wcast-qual -Wsign-conversion -Wno-discarded-qualifiers -DNDHS_BUILD 8 | CPPFLAGS += $(INCL) 9 | 10 | all: ragel ndhs 11 | 12 | ndhs: $(NDHS_OBJS) 13 | $(CC) $(CFLAGS) $(INCL) -o $@ $^ 14 | 15 | -include $(NDHS_DEP) 16 | 17 | clean: 18 | rm -f $(NDHS_OBJS) $(NDHS_DEP) ndhs 19 | 20 | cleanragel: 21 | rm -f dynlease.c cfg.c 22 | 23 | dynlease.c: dynlease.rl 24 | ragel -T0 -o dynlease.c dynlease.rl 25 | 26 | cfg.c: cfg.rl 27 | ragel -T0 -o cfg.c cfg.rl 28 | 29 | ragel: dynlease.c cfg.c 30 | 31 | .PHONY: all clean cleanragel 32 | 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ndhs 2 | Copyright 2011-2024 Nicholas J. Kain. 3 | See LICENSE for licensing information. 4 | 5 | ## Introduction 6 | 7 | ndhs is a DHCPv4 and DHCPv6 server that also provides IPv6 router 8 | advertisements. It is intended to be run on a router; IPv6 assumes that 9 | the default gateway for a network will provide router advertisements. 10 | 11 | ndhs has been designed to be secure and function with minimal privilege. 12 | 13 | Because the implementation is relatively clean and modular, it should 14 | also be easy to extend for custom applications, as well as easy to audit 15 | for correctness. 16 | 17 | ## Requirements 18 | 19 | * Linux kernel 20 | * GCC or Clang 21 | * For developers: [Ragel](https://www.colm.net/open-source/ragel) 22 | 23 | ## Standard Usage 24 | 25 | Compile and install ndhs. 26 | * Build ndhs: `make` 27 | * Install the `ndhs` executable in a normal place. I would 28 | suggest `/usr/sbin` or `/usr/local/sbin`. 29 | 30 | Set up the user account and chroot directory for ndhs. Example: 31 | ``` 32 | $ su - 33 | # umask 077 34 | # groupadd ndhs 35 | 36 | # useradd -d /var/lib/ndhs -s /sbin/nologin -g ndhs ndhs 37 | 38 | # umask 077 39 | # mkdir -p /var/lib/ndhs/store 40 | # chown root.root /var/lib/ndhs 41 | # chmod a+rx /var/lib/ndhs 42 | # cd /var/lib/ndhs 43 | 44 | # mkdir dev 45 | # mknod dev/urandom c 1 9 46 | # mknod dev/null c 1 3 47 | # chown -R root.root dev 48 | # chmod a+rx dev 49 | # chmod a+r dev/urandom 50 | # chmod a+rw dev/null 51 | 52 | # chown ndhs.ndhs store 53 | # chmod 700 store 54 | ``` 55 | Set up a configure file. See below for more information. The default 56 | location for a configure file is `/etc/ndhs.conf`. 57 | 58 | Run ndhs. Use `ndhs --help` to see all possible options. I strongly suggest 59 | running ndhs under some sort of process supervision, such as 60 | [s6](http://www.skarnet.org/software/s6). This will allow for reliable 61 | functioning in the case of unforseen or unrecoverable errors. 62 | 63 | ## Configuration Format 64 | 65 | Comments are denoted by the POSIX-style `#` comment marker and may follow 66 | any command, so long as the `#` is separated from the final argument 67 | by at least one space or tab. Comments may also start at the beginning 68 | of a line, as one would expect. 69 | 70 | ### Global Values 71 | ``` 72 | user 73 | chroot 74 | bind4 ... 75 | bind6 ... 76 | default_preference 77 | default_lifetime 78 | s6_notify 79 | ``` 80 | 81 | `user` specifies the username of the account that ndhs will switch to 82 | after performing initial configuration. It must be a non-root account, 83 | and must have read/write access to the `/store` subdirectory of the 84 | chroot directory. 85 | 86 | `chroot` specifies the path to the directory that will serve as the root 87 | of the chroot jail for ndhs. The directory should be owned by root, 88 | and should not be writable by any other user. 89 | 90 | `bind4` specifies a list of interfaces on which ndhs will provide dhcp4 91 | service. 92 | 93 | `bind6` specifies a list of interfaces on which ndhs will provide dhcp6 94 | and router advertisement services. 95 | 96 | `default_preference` specifies the value of the Preference Option for 97 | dhcp6. It must be between `0` to `255` inclusive and defaults to `0` 98 | if not specified. A DHCP6 client will prefer to choose responses from 99 | the server with the highest preference value if both servers provide 100 | valid DHCP6 replies. This statement takes effect for all subsequent 101 | `interface` keywords. 102 | 103 | `default_lifetime` specifies the duration in seconds for which dhcp 104 | leases will be valid. 105 | 106 | `s6_notify` specifies the file descriptor number to which a newline will be 107 | written after ndhs begins processing requests. 108 | 109 | ### Interface-specific Values 110 | ``` 111 | interface 112 | v4 113 | v6 114 | gateway 115 | dns_server ... 116 | dns_search ... 117 | ntp_server ... 118 | dynamic_range 119 | dynamic_v6 120 | ``` 121 | 122 | `interface` is a toggle. It instructs ndhs that all subsequent 123 | interface-specific options will apply to that interface. Only one 124 | interface can be modified by these options at a time. 125 | 126 | `v4` specifies a static lease for a dhcp4 client. The client is 127 | determined by its mac address, which is specified in the standard 128 | `aa:bb:cc:dd:ee:ff` format. The ipv4 address is specified by the typical 129 | dotted-decimal fomat. 130 | 131 | `v6` specifies a static lease for a dhcp6 client. The client is 132 | determined by its duid and iaid. The duid is specified as either a 133 | string of hexadecimal digits specifying a byte sequence, or as a string 134 | of two-hexadecimal digits delimited by `-`, as is typical for the Windows 135 | `ifconfig /all` command. The iaid is specified by a string of decimal 136 | digits corresponding to a 32-bit unsigned value. The ipv6 address is 137 | specified by the typical hexadecimal string delimited by `:`. 138 | 139 | `dns_server` specifies a list of dns servers for the address. The ip 140 | addresses are in the typical string representations for ipv4 or ipv6 141 | addresses. Multiple addresses are delimited by spaces or tabs. Only 142 | the most recent `dns_server` option for an interface has any effect. 143 | 144 | `ntp_server` specifies a list of ntp servers for the address. The ip 145 | addresses are in the typical string representations for ipv4 or ipv6 146 | addresses. Multiple addresses are delimited by spaces or tabs. Only 147 | the most recent `ntp_server` option for an interface has any effect. 148 | 149 | `dynamic_range` enables ipv4 dynamic lease assignment for the interface 150 | and specifies the both-sides inclusive range of ipv4 addresses that 151 | will be used for dynamic lease assignment. When leases are assigned, 152 | an unused address from this range will be chosen randomly. 153 | 154 | `dynamic_v6` enables ipv6 dynamic lease assignment for the interface. 155 | When leases are assigned, an unused address will be chosen randomly. 156 | The prefix of the range is determined at program start by querying the 157 | Linux kernel netlink interface. 158 | 159 | ### Example Configuration 160 | ``` 161 | user ndhs 162 | chroot /var/lib/ndhs 163 | bind4 eth0 eth1 164 | bind6 eth0 165 | default_lifetime 3600 166 | 167 | # Supports both ipv6 and ipv4. 168 | interface eth0 169 | v4 aa:bb:cc:dd:ee:ff 192.168.1.2 # Desktop 170 | v6 0000000000000000000000000000 00000000 aaaa:bbbb:cccc:dddd:1:2:3:4 # Desktop 171 | v6 00-00-00-00-00-00-00-00-00-00-00-00-00-00 00000000 aaaa:bbbb:cccc:dddd:1:2:3:4 # Desktop 2 172 | subnet 255.255.255.0 173 | gateway 192.168.1.1 174 | broadcast 192.168.1.255 175 | dns_server 192.168.1.1 aaaa:bbbb:cccc:dddd::1 176 | dns_search example.net.invalid 177 | ntp_server 192.168.1.1 aaaa:bbbb:cccc:dddd::1 178 | dynamic_range 192.168.1.100 192.168.1.254 179 | dynamic_v6 180 | 181 | # Supports only ipv4. Maybe it's wifi? 182 | interface eth1 183 | v4 aa:bb:cc:dd:ee:ff 192.168.2.2 # Desktop 184 | subnet 255.255.255.0 185 | gateway 192.168.2.1 186 | broadcast 192.168.2.255 187 | dns_server 192.168.2.1 188 | dns_search example.net.invalid 189 | ntp_server 192.168.2.1 190 | dynamic_range 192.168.2.100 192.168.2.254 191 | ``` 192 | 193 | ## Remarks on IPv4 and IPv6 differences 194 | 195 | IPv6 is very different than IPv4. 196 | 197 | IPv6 supports two different methods (stateful, stateless) of automatic 198 | IP address allocation. IPv4 only supports stateful allocation. 199 | 200 | Stateful allocation is the familiar DHCPv4 approach, where a centralized 201 | server has authority for IP address allocation on a set of local network 202 | segments. Hosts make queries to this server and are provided with IP 203 | addresses by the server, which records the mappings (state) between hosts 204 | (identified by MAC or IAID/DUIDs) and IP addresses. 205 | 206 | DHCPv6 can support this model, but it also allows for stateless 207 | autoconfiguration, where address assignment is not explicitly tracked. 208 | 209 | IPv6 stateless address allocation eliminates the need for a centralized 210 | server to keep track of mappings between hosts and IP addresses. 211 | Instead, information about the network (prefix, dns/ntp servers) 212 | is provided to hosts by routers on the local network segment (link). 213 | Hosts use this information to calculate a probabalistically unique IP 214 | address, which is then verified for uniqueness by interrogating the 215 | network (using IPv6 Neighbor Discovery/Duplicate Address Detection). 216 | 217 | This is fine for situations where it does not matter what addresses 218 | are assigned to clients; these addresses may even intentionally change 219 | over time (see Privacy Extensions and Temporary Addresses). However, 220 | if it is necessary for mappings to remain constant, or to vary but be 221 | coordinated with DNS entries, stateful address assignment is necessary. 222 | 223 | Stateful assignment still requires router advertisements to be provided. 224 | Many types of necessary information (notably the default gateway) are 225 | provided via router advertisements and not by DHCPv6. 226 | 227 | ndhs is designed to support the stateful autoconfiguration model. 228 | It provides all functionality required for stateful autoconfiguration to 229 | fully function for hosts. It should be run only on IPv4/IPv6 routers, 230 | and only on interfaces on the router for which the router performs 231 | routing duties. 232 | 233 | ## Downloads 234 | 235 | * [GitLab](https://gitlab.com/niklata/ndhs) 236 | * [Codeberg](https://codeberg.org/niklata/ndhs) 237 | * [BitBucket](https://bitbucket.com/niklata/ndhs) 238 | * [GitHub](https://github.com/niklata/ndhs) 239 | 240 | ## Portability 241 | 242 | ndhs could be ported to non-Linux systems, but will require new code to 243 | replace the netlink mechanism used in Linux. Some security hardening 244 | features (`SO_LOCK_FILTER`) would need to be disabled, too. 245 | 246 | -------------------------------------------------------------------------------- /attach_bpf.c: -------------------------------------------------------------------------------- 1 | // Copyright 2016 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "nk/log.h" 12 | #include "attach_bpf.h" 13 | 14 | /* 15 | ldb [0] 16 | jne #133, bad 17 | ldb [1] 18 | jne #0, bad 19 | ret #-1 20 | bad: ret #0 21 | */ 22 | static const struct sock_filter sf_icmpra[] = { 23 | { 0x30, 0, 0, 0x00000000 }, 24 | { 0x15, 0, 3, 0x00000085 }, 25 | { 0x30, 0, 0, 0x00000001 }, 26 | { 0x15, 0, 1, 0x00000000 }, 27 | { 0x06, 0, 0, 0xffffffff }, 28 | { 0x06, 0, 0, 0x00000000 }, 29 | }; 30 | 31 | /* 32 | ldh [2] 33 | jne #547, bad 34 | ldh [4] 35 | jlt #12, bad 36 | ldb [8] 37 | jeq #2, bad 38 | jeq #7, bad 39 | jeq #10, bad 40 | jgt #12, bad 41 | ret #-1 42 | bad: ret #0 43 | */ 44 | static const struct sock_filter sf_dhcp6_info[] = { 45 | { 0x28, 0, 0, 0x00000002 }, 46 | { 0x15, 0, 8, 0x00000223 }, 47 | { 0x28, 0, 0, 0x00000004 }, 48 | { 0x35, 0, 6, 0x0000000c }, 49 | { 0x30, 0, 0, 0x00000008 }, 50 | { 0x15, 4, 0, 0x00000002 }, 51 | { 0x15, 3, 0, 0x00000007 }, 52 | { 0x15, 2, 0, 0x0000000a }, 53 | { 0x25, 1, 0, 0x0000000c }, 54 | { 0x06, 0, 0, 0xffffffff }, 55 | { 0x06, 0, 0, 0x00000000 }, 56 | }; 57 | 58 | bool attach_bpf_icmp6_ra(int fd, const char *ifname) 59 | { 60 | static const struct sock_fprog sfp_icmpra = { 61 | .len = sizeof sf_icmpra / sizeof sf_icmpra[0], 62 | .filter = (const struct sock_filter *)sf_icmpra, 63 | }; 64 | int r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_icmpra, 65 | sizeof sfp_icmpra); 66 | if (r >= 0) { 67 | int tv = 1; 68 | r = setsockopt(fd, SOL_SOCKET, SO_LOCK_FILTER, &tv, sizeof tv); 69 | if (r >= 0) 70 | return true; 71 | else 72 | log_line("%s: Failed to lock BPF for ICMPv6 socket: %s\n", 73 | ifname, strerror(errno)); 74 | } else 75 | log_line("%s: Failed to set BPF for ICMPv6 socket: %s\n", 76 | ifname, strerror(errno)); 77 | return false; 78 | } 79 | 80 | bool attach_bpf_dhcp6_info(int fd, const char *ifname) 81 | { 82 | static const struct sock_fprog sfp_dhcp6_info = { 83 | .len = sizeof sf_dhcp6_info / sizeof sf_dhcp6_info[0], 84 | .filter = (const struct sock_filter *)sf_dhcp6_info, 85 | }; 86 | int r = setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &sfp_dhcp6_info, 87 | sizeof sfp_dhcp6_info); 88 | if (r >= 0) { 89 | int tv = 1; 90 | r = setsockopt(fd, SOL_SOCKET, SO_LOCK_FILTER, &tv, sizeof tv); 91 | if (r >= 0) 92 | return true; 93 | else 94 | log_line("%s: Failed to lock BPF for DHCPv6 socket: %s\n", 95 | ifname, strerror(errno)); 96 | } else 97 | log_line("%s: Failed to set BPF for DHCPv6 socket: %s\n", 98 | ifname, strerror(errno)); 99 | return false; 100 | } 101 | 102 | -------------------------------------------------------------------------------- /attach_bpf.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_ATTACH_BPF_H_ 4 | #define NDHS_ATTACH_BPF_H_ 5 | extern bool attach_bpf_icmp6_ra(int fd, const char *ifname); 6 | extern bool attach_bpf_dhcp6_info(int fd, const char *ifname); 7 | #endif 8 | -------------------------------------------------------------------------------- /cfg.rl: -------------------------------------------------------------------------------- 1 | // -*- c -*- 2 | // Copyright 2016-2024 Nicholas J. Kain 3 | // SPDX-License-Identifier: MIT 4 | #include 5 | #include 6 | #include 7 | #include "dhcp_state.h" 8 | #include 9 | #include "nk/log.h" 10 | #include 11 | 12 | extern void set_user_runas(const char *username, size_t len); 13 | extern void set_chroot_path(const char *path, size_t len); 14 | extern void set_s6_notify_fd(int fd); 15 | 16 | #define MAX_LINE 2048 17 | 18 | struct cfg_parse_state { 19 | const char *st; 20 | int cs; 21 | 22 | char duid[128]; 23 | char ipaddrs[32][48]; 24 | uint8_t macaddr[6]; 25 | size_t duid_len; 26 | size_t nipaddrs; 27 | int ifindex; 28 | uint32_t iaid; 29 | uint32_t default_lifetime; 30 | uint8_t default_preference; 31 | bool parse_error; 32 | }; 33 | 34 | static void newline(struct cfg_parse_state *ps) { 35 | // Do NOT clear ifindex here; it is stateful between lines! 36 | memset(ps->duid, 0, sizeof ps->duid); 37 | memset(ps->ipaddrs, 0, sizeof ps->ipaddrs); 38 | memset(ps->macaddr, 0, sizeof ps->macaddr); 39 | ps->duid_len = 0; 40 | ps->nipaddrs = 0; 41 | ps->iaid = 0; 42 | ps->parse_error = false; 43 | } 44 | 45 | bool parse_config(const char *path); 46 | 47 | #define MARKED_STRING() cps->st, (p > cps->st ? (size_t)(p - cps->st) : 0) 48 | 49 | #include "parsehelp.h" 50 | 51 | static bool string_to_ipaddr(struct in6_addr *r, const char *s, size_t linenum) 52 | { 53 | if (!ipaddr_from_string(r, s)) { 54 | log_line("ip address on line %zu is invalid\n", linenum); 55 | return false; 56 | } 57 | return true; 58 | } 59 | 60 | %%{ 61 | machine cfg_line_m; 62 | access cps->; 63 | 64 | action St { cps->st = p; } 65 | 66 | action DuidEn { 67 | assign_strbuf(cps->duid, &cps->duid_len, sizeof cps->duid, cps->st, p); 68 | lc_string_inplace(cps->duid, cps->duid_len); 69 | } 70 | action IaidEn { 71 | char buf[64]; 72 | ptrdiff_t blen = p - cps->st; 73 | if (blen < 0 || blen >= (int)sizeof buf) { 74 | cps->parse_error = true; 75 | fbreak; 76 | } 77 | memcpy(buf, cps->st, (size_t)blen); buf[blen] = 0; 78 | if (sscanf(buf, "%" SCNu32, &cps->iaid) != 1) { 79 | cps->parse_error = true; 80 | fbreak; 81 | } 82 | } 83 | action MacAddrEn { 84 | char buf[32]; 85 | ptrdiff_t blen = p - cps->st; 86 | if (blen < 0 || blen >= (int)sizeof buf) { 87 | cps->parse_error = true; 88 | fbreak; 89 | } 90 | *(char *)mempcpy(buf, cps->st, (size_t)blen) = 0; 91 | if (sscanf(buf, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", 92 | &cps->macaddr[0], &cps->macaddr[1], &cps->macaddr[2], 93 | &cps->macaddr[3], &cps->macaddr[4], &cps->macaddr[5]) != 6) { 94 | cps->parse_error = true; 95 | fbreak; 96 | } 97 | } 98 | action IPAddrEn { 99 | size_t l; 100 | assign_strbuf(cps->ipaddrs[cps->nipaddrs], &l, sizeof cps->ipaddrs[cps->nipaddrs], cps->st, p); 101 | lc_string_inplace(cps->ipaddrs[cps->nipaddrs++], l); 102 | } 103 | action Bind4En { 104 | char buf[IFNAMSIZ]; 105 | ptrdiff_t blen = p - cps->st; 106 | if (blen < 0 || blen >= IFNAMSIZ) { 107 | log_line("interface name on line %zu is too long (>= %d)\n", linenum, IFNAMSIZ); 108 | cps->parse_error = true; 109 | fbreak; 110 | } 111 | memcpy(buf, cps->st, (size_t)blen); 112 | buf[blen] = 0; 113 | emplace_bind4(linenum, buf); 114 | } 115 | action Bind6En { 116 | char buf[IFNAMSIZ]; 117 | ptrdiff_t blen = p - cps->st; 118 | if (blen < 0 || blen >= IFNAMSIZ) { 119 | log_line("interface name on line %zu is too long (>= %d)\n", linenum, IFNAMSIZ); 120 | cps->parse_error = true; 121 | fbreak; 122 | } 123 | memcpy(buf, cps->st, (size_t)blen); 124 | buf[blen] = 0; 125 | emplace_bind6(linenum, buf); 126 | } 127 | action UserEn { set_user_runas(MARKED_STRING()); } 128 | action ChrootEn { set_chroot_path(MARKED_STRING()); } 129 | action S6NotifyEn { 130 | char buf[64]; 131 | ptrdiff_t blen = p - cps->st; 132 | if (blen < 0 || blen >= (int)sizeof buf) { 133 | cps->parse_error = true; 134 | fbreak; 135 | } 136 | memcpy(buf, cps->st, (size_t)blen); buf[blen] = 0; 137 | int fd; 138 | if (sscanf(buf, "%d", &fd) != 1) { 139 | cps->parse_error = true; 140 | fbreak; 141 | } 142 | set_s6_notify_fd(fd); 143 | } 144 | action DefLifeEn { 145 | char buf[64]; 146 | ptrdiff_t blen = p - cps->st; 147 | if (blen < 0 || blen >= (int)sizeof buf) { 148 | cps->parse_error = true; 149 | fbreak; 150 | } 151 | memcpy(buf, cps->st, (size_t)blen); buf[blen] = 0; 152 | if (sscanf(buf, "%" SCNu32, &cps->default_lifetime) != 1) { 153 | cps->parse_error = true; 154 | fbreak; 155 | } 156 | } 157 | action DefPrefEn { 158 | char buf[64]; 159 | ptrdiff_t blen = p - cps->st; 160 | if (blen < 0 || blen >= (int)sizeof buf) { 161 | cps->parse_error = true; 162 | fbreak; 163 | } 164 | memcpy(buf, cps->st, (size_t)blen); buf[blen] = 0; 165 | if (sscanf(buf, "%" SCNu8, &cps->default_preference) != 1) { 166 | log_line("default_preference on line %zu out of range [0,255]\n", linenum); 167 | cps->parse_error = true; 168 | fbreak; 169 | } 170 | } 171 | action InterfaceEn { 172 | char interface[IFNAMSIZ]; 173 | ptrdiff_t blen = p - cps->st; 174 | if (blen < 0 || blen >= (int)sizeof interface) { 175 | log_line("interface name on line %zu is too long (>= %d)\n", linenum, IFNAMSIZ); 176 | cps->parse_error = true; 177 | fbreak; 178 | } 179 | memcpy(interface, cps->st, (size_t)blen); 180 | interface[blen] = 0; 181 | cps->ifindex = emplace_interface(linenum, interface, cps->default_preference); 182 | } 183 | action DnsServerEn { 184 | struct in6_addr *addrs = calloc(cps->nipaddrs, sizeof(struct in6_addr)); 185 | if (!addrs) abort(); 186 | for (size_t i = 0; i < cps->nipaddrs; ++i) { 187 | if (!string_to_ipaddr(&addrs[i], cps->ipaddrs[i], strlen(cps->ipaddrs[i]))) { 188 | log_line("invalid ip address (%s) at line %zu", cps->ipaddrs[i], linenum); 189 | cps->parse_error = true; 190 | fbreak; 191 | } 192 | } 193 | emplace_dns_servers(linenum, cps->ifindex, addrs, cps->nipaddrs); 194 | } 195 | action DnsSearchEn { 196 | emplace_dns_search(linenum, cps->ifindex, MARKED_STRING()); 197 | } 198 | action NtpServerEn { 199 | struct in6_addr *addrs = calloc(cps->nipaddrs, sizeof(struct in6_addr)); 200 | if (!addrs) abort(); 201 | for (size_t i = 0; i < cps->nipaddrs; ++i) { 202 | if (!string_to_ipaddr(&addrs[i], cps->ipaddrs[i], strlen(cps->ipaddrs[i]))) { 203 | log_line("invalid ip address (%s) at line %zu", cps->ipaddrs[i], linenum); 204 | cps->parse_error = true; 205 | fbreak; 206 | } 207 | } 208 | emplace_ntp_servers(linenum, cps->ifindex, addrs, cps->nipaddrs); 209 | } 210 | action GatewayEn { 211 | struct in6_addr t; 212 | if (!string_to_ipaddr(&t, cps->ipaddrs[0], linenum)) { 213 | cps->parse_error = true; 214 | fbreak; 215 | } 216 | emplace_gateway_v4(linenum, cps->ifindex, &t); 217 | } 218 | action DynRangeEn { 219 | if (cps->nipaddrs != 2) { 220 | fprintf(stderr, "XXX: dynrange nipaddrs != 2 (%zu)\n", cps->nipaddrs); 221 | cps->parse_error = true; 222 | fbreak; 223 | } 224 | struct in6_addr tlo; 225 | if (!string_to_ipaddr(&tlo, cps->ipaddrs[0], linenum)) { 226 | cps->parse_error = true; 227 | fbreak; 228 | } 229 | struct in6_addr thi; 230 | if (!string_to_ipaddr(&thi, cps->ipaddrs[1], linenum)) { 231 | cps->parse_error = true; 232 | fbreak; 233 | } 234 | emplace_dynamic_range(linenum, cps->ifindex, &tlo, &thi, cps->default_lifetime); 235 | } 236 | action DynamicV6En { 237 | emplace_dynamic_v6(linenum, cps->ifindex); 238 | } 239 | action V4EntryEn { 240 | struct in6_addr t; 241 | if (!string_to_ipaddr(&t, cps->ipaddrs[0], linenum)) { 242 | cps->parse_error = true; 243 | fbreak; 244 | } 245 | emplace_dhcp4_state(linenum, cps->ifindex, cps->macaddr, &t, cps->default_lifetime); 246 | } 247 | action V6EntryEn { 248 | struct in6_addr t; 249 | if (!string_to_ipaddr(&t, cps->ipaddrs[0], linenum)) { 250 | cps->parse_error = true; 251 | fbreak; 252 | } 253 | emplace_dhcp6_state(linenum, cps->ifindex, 254 | cps->duid, cps->duid_len, 255 | cps->iaid, &t, cps->default_lifetime); 256 | } 257 | 258 | duid = xdigit+ >St %DuidEn; 259 | iaid = digit+ >St %IaidEn; 260 | macaddr = ((xdigit{2} ':'){5} xdigit{2}) >St %MacAddrEn; 261 | v4_addr = (digit{1,3} | '.')+ >St %IPAddrEn; 262 | v6_addr = (xdigit{1,4} | ':')+ >St %IPAddrEn; 263 | 264 | comment = space* ('#' any*)?; 265 | tcomment = (space+ '#' any*)?; 266 | bind4 = space* 'bind4' (space+ alnum+ >St %Bind4En)+ tcomment; 267 | bind6 = space* 'bind6' (space+ alnum+ >St %Bind6En)+ tcomment; 268 | user = space* 'user' space+ graph+ >St %UserEn tcomment; 269 | chroot = space* 'chroot' space+ graph+ >St %ChrootEn tcomment; 270 | s6_notify = space* 's6_notify' space+ digit+ >St %S6NotifyEn tcomment; 271 | default_lifetime = space* 'default_lifetime' space+ digit+ >St %DefLifeEn tcomment; 272 | default_preference = space* 'default_preference' space+ digit+ >St %DefPrefEn tcomment; 273 | interface = space* 'interface' space+ alnum+ >St %InterfaceEn tcomment; 274 | dns_server = space* 'dns_server' (space+ (v4_addr | v6_addr))+ tcomment %DnsServerEn; 275 | dns_search = space* 'dns_search' (space+ graph+ >St %DnsSearchEn)+ tcomment; 276 | ntp_server = space* 'ntp_server' (space+ (v4_addr | v6_addr))+ tcomment %NtpServerEn; 277 | gateway = space* 'gateway' space+ v4_addr %GatewayEn tcomment; 278 | dynamic_range = space* 'dynamic_range' space+ v4_addr space+ v4_addr %DynRangeEn tcomment; 279 | dynamic_v6 = space* 'dynamic_v6' %DynamicV6En tcomment; 280 | v4_entry = space* 'v4' space+ macaddr space+ v4_addr tcomment; 281 | v6_entry = space* 'v6' space+ duid space+ iaid space+ v6_addr tcomment; 282 | 283 | main := comment | bind4 | bind6 | user | chroot | s6_notify | default_lifetime | default_preference 284 | | interface | dns_server | dns_search | ntp_server | gateway 285 | | dynamic_range | dynamic_v6 | v6_entry %V6EntryEn | v4_entry %V4EntryEn; 286 | }%% 287 | 288 | #pragma GCC diagnostic push 289 | #pragma GCC diagnostic ignored "-Wunused-const-variable" 290 | %% write data; 291 | #pragma GCC diagnostic pop 292 | 293 | static int do_parse_cfg_line(struct cfg_parse_state *cps, const char *p, size_t plen, 294 | const size_t linenum) 295 | { 296 | const char *pe = p + plen; 297 | const char *eof = pe; 298 | 299 | %% write init; 300 | %% write exec; 301 | 302 | if (cps->parse_error) return -1; 303 | if (cps->cs >= cfg_line_m_first_final) 304 | return 1; 305 | if (cps->cs == cfg_line_m_error) 306 | return -1; 307 | return -2; 308 | } 309 | 310 | bool parse_config(const char *path) 311 | { 312 | bool ret = false; 313 | size_t linenum = 0; 314 | struct cfg_parse_state ps = { .ifindex = -1, .default_lifetime = 7200 }; 315 | char buf[MAX_LINE]; 316 | FILE *f = fopen(path, "r"); 317 | if (!f) { 318 | log_line("%s: failed to open config file '%s' for read: %s\n", 319 | __func__, path, strerror(errno)); 320 | goto out0; 321 | } 322 | while (!feof(f)) { 323 | if (!fgets(buf, sizeof buf, f)) { 324 | if (!feof(f)) { 325 | log_line("%s: io error fetching line of '%s'\n", __func__, path); 326 | goto out1; 327 | } 328 | break; 329 | } 330 | size_t llen = strlen(buf); 331 | if (llen == 0) 332 | continue; 333 | if (buf[llen-1] == '\n') 334 | buf[--llen] = 0; 335 | ++linenum; 336 | newline(&ps); 337 | int r = do_parse_cfg_line(&ps, buf, llen, linenum); 338 | if (r < 0) { 339 | if (r == -2) 340 | log_line("%s: Incomplete configuration at line %zu; ignoring\n", 341 | __func__, linenum); 342 | else 343 | log_line("%s: Malformed configuration at line %zu; ignoring.\n", 344 | __func__, linenum); 345 | continue; 346 | } 347 | } 348 | create_blobs(); 349 | ret = true; 350 | out1: 351 | fclose(f); 352 | out0: 353 | return ret; 354 | } 355 | 356 | -------------------------------------------------------------------------------- /dhcp.h: -------------------------------------------------------------------------------- 1 | // Copyright 2004-2017 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHC_DHCP_H_ 4 | #define NDHC_DHCP_H_ 5 | 6 | #include 7 | 8 | #define DHCP_CLIENT_PORT 68 9 | #define DHCP_MAGIC 0x63825363 10 | 11 | enum { 12 | DHCPNULL = 0, 13 | DHCPDISCOVER = 1, 14 | DHCPOFFER = 2, 15 | DHCPREQUEST = 3, 16 | DHCPDECLINE = 4, 17 | DHCPACK = 5, 18 | DHCPNAK = 6, 19 | DHCPRELEASE = 7, 20 | DHCPINFORM = 8 21 | }; 22 | 23 | struct dhcpmsg { 24 | uint8_t op; // Message type: 1 = BOOTREQUEST for clients. 25 | uint8_t htype; // ARP HW address type: always '1' for ethernet. 26 | uint8_t hlen; // Hardware address length: always '6' for ethernet. 27 | uint8_t hops; // Client sets to zero. 28 | uint32_t xid; // Transaction ID: random number identifying session 29 | uint16_t secs; // Filled by client: seconds since client began address 30 | // aquisition or renewal process. 31 | uint16_t flags; // DHCP flags 32 | uint32_t ciaddr; // Client IP: only filled in if client is in BOUND, RENEW, 33 | // or REBINDING and can reply to ARP requests 34 | uint32_t yiaddr; // 'your' (client) IP address 35 | uint32_t siaddr; // Always zero -- unused. 36 | uint32_t giaddr; // Always zero -- unused. 37 | uint8_t chaddr[16]; // Client MAC address 38 | uint8_t sname[64]; // More DHCP options (#3) 39 | uint8_t file[128]; // More DHCP options (#2) 40 | uint32_t cookie; // Magic number cookie that starts DHCP options 41 | uint8_t options[308]; // DHCP options field (#1) 42 | }; 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /dhcp4.c: -------------------------------------------------------------------------------- 1 | // Copyright 2011-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "dhcp4.h" 8 | #include "dhcp_state.h" 9 | #include "nlsocket.h" 10 | #include "dynlease.h" 11 | #include "sbufs.h" 12 | #include "nk/netbits.h" 13 | #include "nk/log.h" 14 | #include "nk/io.h" 15 | #include "nk/random.h" 16 | #include "ipaddr.h" 17 | #include "options.h" 18 | #include "get_current_ts.h" 19 | #include "dhcp.h" 20 | 21 | // Number of entries. Exists per interface. 22 | #define D4_CLIENT_STATE_TABLESIZE 256 23 | #define D4_XID_LIFE_SECS 60 24 | 25 | extern struct nk_random_state g_rngstate; 26 | extern struct NLSocket nl_socket; 27 | 28 | enum SendReplyType { 29 | SRT_UnicastCi, 30 | SRT_Broadcast, 31 | SRT_Relay, 32 | SRT_UnicastYiCh 33 | }; 34 | 35 | // Timestamps are cheap on modern hardware, so we can do 36 | // things precisely. We optimize for cache locality. 37 | struct D4State 38 | { 39 | struct timespec ts; // Set once at creation 40 | uint64_t hwaddr; 41 | uint32_t xid; 42 | uint32_t state; 43 | }; 44 | 45 | struct D4Listener 46 | { 47 | int fd; 48 | int ifindex; 49 | struct dhcpmsg dhcpmsg; 50 | struct in6_addr local_ip; 51 | char ifname[IFNAMSIZ]; 52 | struct D4State map[D4_CLIENT_STATE_TABLESIZE]; 53 | }; 54 | 55 | // hwaddr must be exactly 6 bytes 56 | static uint64_t hwaddr_to_int64(uint8_t *hwaddr) 57 | { 58 | return (uint64_t)hwaddr[0] | 59 | ((uint64_t)hwaddr[1] << 8) | 60 | ((uint64_t)hwaddr[2] << 16) | 61 | ((uint64_t)hwaddr[3] << 24) | 62 | ((uint64_t)hwaddr[4] << 32) | 63 | ((uint64_t)hwaddr[5] << 40); 64 | } 65 | 66 | static inline bool ip4_to_string(char *buf, size_t buflen, uint32_t addr) 67 | { 68 | return !!inet_ntop(AF_INET, &addr, buf, buflen); 69 | } 70 | 71 | static struct D4State *find(struct D4State *self, uint64_t h) 72 | { 73 | for (size_t i = 0; i < D4_CLIENT_STATE_TABLESIZE; ++i) { 74 | if (self[i].hwaddr == h) { 75 | struct timespec now; 76 | clock_gettime(CLOCK_BOOTTIME, &now); 77 | if (now.tv_sec > self[i].ts.tv_sec + D4_XID_LIFE_SECS) { 78 | self[i] = (struct D4State){0}; 79 | break; 80 | } 81 | return &self[i]; 82 | } 83 | } 84 | return NULL; 85 | } 86 | 87 | static bool D4State_add(struct D4State *self, uint32_t xid, uint8_t *hwaddr, uint8_t state) 88 | { 89 | if (!state) return false; 90 | uint64_t key = hwaddr_to_int64(hwaddr); 91 | struct timespec now; 92 | clock_gettime(CLOCK_BOOTTIME, &now); 93 | struct D4State *m = NULL, *e = NULL; 94 | struct D4State zi = {0}; 95 | for (size_t i = 0; i < D4_CLIENT_STATE_TABLESIZE; ++i) { 96 | if (self[i].ts.tv_sec && now.tv_sec > self[i].ts.tv_sec + D4_XID_LIFE_SECS) { 97 | // Expire entries as we see them. 98 | self[i] = (struct D4State){0}; 99 | } else { 100 | if (self[i].hwaddr == key) { 101 | m = &self[i]; 102 | break; 103 | } 104 | } 105 | if (!e && !memcmp(&self[i], &zi, sizeof zi)) e = &self[i]; 106 | } 107 | if (!m) { 108 | if (!e) return false; // Out of space. 109 | m = e; 110 | } 111 | *m = (struct D4State){ 112 | .ts = now, 113 | .hwaddr = key, 114 | .xid = xid, 115 | .state = state, 116 | }; 117 | return true; 118 | } 119 | 120 | static uint8_t D4State_get(struct D4State *self, uint32_t xid, uint8_t *hwaddr) 121 | { 122 | uint64_t key = hwaddr_to_int64(hwaddr); 123 | struct D4State *m = find(self, key); 124 | if (!m || m->xid != xid) return DHCPNULL; 125 | return m->state; 126 | } 127 | 128 | static void D4State_kill(struct D4State *self, uint8_t *hwaddr) 129 | { 130 | uint64_t key = hwaddr_to_int64(hwaddr); 131 | struct D4State *m = find(self, key); 132 | if (m) *m = (struct D4State){0}; 133 | } 134 | 135 | // Must be called after ifname is set and only should be called once. 136 | static bool create_dhcp4_socket(struct D4Listener *self) 137 | { 138 | struct ifreq ifr = {0}; 139 | const int iv = 1; 140 | size_t ifname_len = strlen(self->ifname); 141 | struct sockaddr_in sai = { 142 | .sin_family = AF_INET, 143 | .sin_port = htons(67), 144 | }; 145 | self->fd = socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC, IPPROTO_UDP); 146 | if (self->fd < 0) { 147 | log_line("dhcp4: Failed to create v4 UDP socket on %s: %s\n", self->ifname, strerror(errno)); 148 | goto err0; 149 | } 150 | if (setsockopt(self->fd, SOL_SOCKET, SO_BROADCAST, &iv, sizeof iv) == -1) { 151 | log_line("dhcp4: Failed to set broadcast flag on %s: %s\n", self->ifname, strerror(errno)); 152 | goto err1; 153 | } 154 | if (setsockopt(self->fd, SOL_SOCKET, SO_REUSEADDR, &iv, sizeof iv) == -1) { 155 | log_line("dhcp4: Failed to set reuse address flag on %s: %s\n", self->ifname, strerror(errno)); 156 | goto err1; 157 | } 158 | if (bind(self->fd, (const struct sockaddr *)&sai, sizeof sai)) { 159 | log_line("dhcp4: Failed to bind to UDP 67 on %s: %s\n", self->ifname, strerror(errno)); 160 | goto err1; 161 | } 162 | if (ifname_len >= sizeof ifr.ifr_name) { 163 | log_line("dhcp4: Interface name '%s' is too long: %zu >= %zu\n", 164 | self->ifname, ifname_len, sizeof ifr.ifr_name); 165 | goto err1; 166 | } 167 | memcpy(ifr.ifr_name, self->ifname, ifname_len); 168 | if (setsockopt(self->fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) { 169 | log_line("dhcp4: Failed to bind socket to device on %s: %s\n", self->ifname, strerror(errno)); 170 | goto err1; 171 | } 172 | return true; 173 | err1: 174 | close(self->fd); 175 | self->fd = -1; 176 | err0: 177 | return false; 178 | } 179 | 180 | // Only intended to be called once per listener. 181 | struct D4Listener *D4Listener_create(const char *ifname, const struct netif_info *ifinfo) 182 | { 183 | struct D4Listener *self; 184 | size_t ifname_src_size = strlen(ifname); 185 | if (ifname_src_size >= sizeof self->ifname) { 186 | log_line("D4Listener: Interface name (%s) too long\n", ifname); 187 | return NULL; 188 | } 189 | if (!ifinfo->has_v4_address) { 190 | log_line("dhcp4: Interface (%s) has no IP address\n", ifname); 191 | return NULL; 192 | } 193 | self = calloc(1, sizeof(struct D4Listener)); 194 | if (!self) return NULL; 195 | 196 | self->ifindex = ifinfo->index; 197 | *(char *)(mempcpy(self->ifname, ifname, ifname_src_size)) = 0; 198 | self->local_ip = ifinfo->v4_address; 199 | 200 | char abuf[48]; 201 | if (!create_dhcp4_socket(self)) goto err; 202 | if (!ipaddr_to_string(abuf, sizeof abuf, &self->local_ip)) goto err; 203 | log_line("dhcp4: IP address for %s is %s\n", ifname, abuf); 204 | 205 | return self; 206 | err: 207 | free(self); 208 | return NULL; 209 | } 210 | 211 | static void process_receive(struct D4Listener *self, const char *buf, size_t buflen, 212 | const struct sockaddr_in *sai); 213 | 214 | void D4Listener_process_input(struct D4Listener *self) 215 | { 216 | char buf[8192]; 217 | for (;;) { 218 | struct sockaddr_in sai; 219 | socklen_t sailen = sizeof sai; 220 | ssize_t buflen = recvfrom(self->fd, buf, sizeof buf, MSG_DONTWAIT, (struct sockaddr *)&sai, &sailen); 221 | if (buflen < 0) { 222 | int err = errno; 223 | if (err == EINTR) continue; 224 | if (err == EAGAIN || err == EWOULDBLOCK) break; 225 | suicide("dhcp4: recvfrom failed on %s: %s\n", self->ifname, strerror(err)); 226 | } 227 | process_receive(self, buf, (size_t)buflen, &sai); 228 | } 229 | } 230 | 231 | int D4Listener_fd(const struct D4Listener *self) 232 | { 233 | return self->fd; 234 | } 235 | 236 | static uint32_t local_ip(const struct D4Listener *self) 237 | { 238 | uint32_t ret; 239 | memcpy(&ret, ipaddr_v4_bytes(&self->local_ip), sizeof ret); 240 | return ret; 241 | } 242 | 243 | static void dhcpmsg_init(const struct D4Listener *self, struct dhcpmsg *dm, uint8_t type, uint32_t xid) 244 | { 245 | *dm = (struct dhcpmsg){ 246 | .op = 2, // BOOTREPLY (server) 247 | .htype = 1, 248 | .hlen = 6, 249 | .xid = xid, 250 | .cookie = htonl(DHCP_MAGIC), 251 | .options[0] = DCODE_END, 252 | }; 253 | memcpy(dm->chaddr, &self->dhcpmsg.chaddr, sizeof self->dhcpmsg.chaddr); 254 | add_option_msgtype(dm, type); 255 | add_option_serverid(dm, local_ip(self)); 256 | } 257 | 258 | static void send_to(int fd, const void *buf, size_t len, uint32_t addr, int port) 259 | { 260 | struct sockaddr_in sai = { 261 | .sin_family = AF_INET, 262 | .sin_port = htons(port), 263 | .sin_addr.s_addr = addr, 264 | }; 265 | ssize_t r = safe_sendto(fd, buf, len, 0, (const struct sockaddr *)&sai, sizeof sai); 266 | if (r < 0) log_line("dhcp4: D4Listener sendto failed: %s\n", strerror(errno)); 267 | } 268 | 269 | static void send_reply_do(struct D4Listener *self, const struct dhcpmsg *dm, enum SendReplyType srt) 270 | { 271 | ssize_t endloc = get_end_option_idx(dm); 272 | if (endloc < 0) return; 273 | size_t dmlen = sizeof *dm - (sizeof dm->options - 1 - (size_t)endloc); 274 | 275 | switch (srt) { 276 | case SRT_UnicastCi: send_to(self->fd, dm, dmlen, self->dhcpmsg.ciaddr, 68); break; 277 | case SRT_Broadcast: { 278 | const struct in6_addr *broadcast = query_broadcast(self->ifindex); 279 | if (!broadcast) suicide("dhcp4: misconfigured -- must have a broadcast address\n"); 280 | uint32_t bcaddr; 281 | memcpy(&bcaddr, ipaddr_v4_bytes(broadcast), sizeof bcaddr); 282 | send_to(self->fd, dm, dmlen, bcaddr, 68); 283 | break; 284 | } 285 | case SRT_Relay: send_to(self->fd, dm, dmlen, self->dhcpmsg.giaddr, 67); break; 286 | case SRT_UnicastYiCh: send_to(self->fd, dm, dmlen, self->dhcpmsg.yiaddr, 68); break; 287 | } 288 | } 289 | 290 | static void send_reply(struct D4Listener *self, const struct dhcpmsg *reply) 291 | { 292 | if (self->dhcpmsg.giaddr) send_reply_do(self, reply, SRT_Relay); 293 | else if (self->dhcpmsg.ciaddr) send_reply_do(self, reply, SRT_UnicastCi); 294 | else if (ntohs(self->dhcpmsg.flags) & 0x8000u) send_reply_do(self, reply, SRT_Broadcast); 295 | else if (self->dhcpmsg.yiaddr) send_reply_do(self, reply, SRT_UnicastYiCh); 296 | else send_reply_do(self, reply, SRT_Broadcast); 297 | } 298 | 299 | static bool iplist_option(struct dhcpmsg *reply, uint8_t code, const struct addrlist *ipl) 300 | { 301 | char buf[256]; // max option size is 255 bytes 302 | size_t off = 0; 303 | for (size_t i = 0; i < ipl->n; ++i) { 304 | if (off + 4 >= sizeof buf) break; // silently drop if too many 305 | if (ipaddr_is_v4(&ipl->addrs[i])) { 306 | memcpy(buf + off, ipaddr_v4_bytes(&ipl->addrs[i]), 4); 307 | off += 4; 308 | } 309 | } 310 | buf[off] = 0; 311 | if (!off) return false; 312 | add_option_string(reply, code, buf, off); 313 | return true; 314 | } 315 | 316 | static struct in6_addr u32_ipaddr(uint32_t v) 317 | { 318 | struct in6_addr ret; 319 | ipaddr_from_v4_bytes(&ret, &v); 320 | return ret; 321 | } 322 | 323 | static bool allot_dynamic_ip(struct D4Listener *self, struct dhcpmsg *reply, const uint8_t *hwaddr, bool do_assign) 324 | { 325 | uint32_t dynamic_lifetime; 326 | if (!query_use_dynamic_v4(self->ifindex, &dynamic_lifetime)) 327 | return false; 328 | 329 | log_line("dhcp4: Checking dynamic IP.\n"); 330 | 331 | struct in6_addr dr_lo, dr_hi; 332 | if (!query_dynamic_range(self->ifindex, &dr_lo, &dr_hi)) { 333 | log_line("dhcp4: No dynamic range is associated. Can't assign an IP.\n"); 334 | return false; 335 | } 336 | int64_t expire_time = get_current_ts() + dynamic_lifetime; 337 | 338 | struct in6_addr v4a = dynlease4_query_refresh(self->ifindex, hwaddr, expire_time); 339 | if (memcmp(&v4a, &in6addr_any, 16)) { 340 | if (!ipaddr_is_v4(&v4a)) { 341 | log_line("dhcp4: allot_dynamic_ip - bad address\n"); 342 | return false; 343 | } 344 | memcpy(&reply->yiaddr, ipaddr_v4_bytes(&v4a), sizeof reply->yiaddr); 345 | add_u32_option(reply, DCODE_LEASET, htonl(dynamic_lifetime)); 346 | char abuf[48] = "oom"; 347 | ipaddr_to_string(abuf, sizeof abuf, &v4a); 348 | log_line("dhcp4: Assigned existing dynamic IP: %s\n", abuf); 349 | return true; 350 | } 351 | log_line("dhcp4: Selecting an unused dynamic IP.\n"); 352 | 353 | // IP is randomly selected from the dynamic range. 354 | uint32_t al = decode32be(ipaddr_v4_bytes(&dr_lo)); 355 | uint32_t ah = decode32be(ipaddr_v4_bytes(&dr_hi)); 356 | uint64_t ar = ah > al ? ah - al : al - ah; 357 | // The extremely small distribution skew does not matter here. 358 | uint64_t rqs = nk_random_u64(&g_rngstate) % (ar + 1); 359 | 360 | // OK, here we have bisected our range using rqs. 361 | // [al .. ah] => [al .. rqs .. ah] 362 | // So we scan from [rqs, ah], taking the first empty slot. 363 | // If no success, scan from [al, rqs), taking the first empty slot. 364 | // If no success, then all IPs are taken, so return false. 365 | for (uint32_t i = al + rqs; i <= ah; ++i) { 366 | struct in6_addr iaddr = u32_ipaddr(htonl(i)); 367 | bool matched = do_assign ? dynlease4_add(self->ifindex, &iaddr, hwaddr, expire_time) 368 | : dynlease4_exists(self->ifindex, &iaddr, hwaddr); 369 | if (matched) { 370 | reply->yiaddr = htonl(i); 371 | add_u32_option(reply, DCODE_LEASET, htonl(dynamic_lifetime)); 372 | return true; 373 | } 374 | } 375 | for (uint32_t i = al; i < al + rqs; ++i) { 376 | struct in6_addr iaddr = u32_ipaddr(htonl(i)); 377 | bool matched = do_assign ? dynlease4_add(self->ifindex, &iaddr, hwaddr, expire_time) 378 | : dynlease4_exists(self->ifindex, &iaddr, hwaddr); 379 | if (matched) { 380 | reply->yiaddr = htonl(i); 381 | add_u32_option(reply, DCODE_LEASET, htonl(dynamic_lifetime)); 382 | return true; 383 | } 384 | } 385 | return false; 386 | } 387 | 388 | static bool create_reply(struct D4Listener *self, struct dhcpmsg *reply, const uint8_t *hwaddr, bool do_assign) 389 | { 390 | const struct dhcpv4_entry *dv4s = query_dhcp4_state(self->ifindex, hwaddr); 391 | if (!dv4s) { 392 | if (!allot_dynamic_ip(self, reply, hwaddr, do_assign)) 393 | return false; 394 | } else { 395 | memcpy(&reply->yiaddr, ipaddr_v4_bytes(&dv4s->address), sizeof reply->yiaddr); 396 | add_u32_option(reply, DCODE_LEASET, htonl(dv4s->lifetime)); 397 | } 398 | const struct in6_addr *subnet = query_subnet(self->ifindex); 399 | if (!subnet) return false; 400 | uint32_t subnet_addr; 401 | memcpy(&subnet_addr, ipaddr_v4_bytes(subnet), sizeof subnet_addr); 402 | add_option_subnet_mask(reply, subnet_addr); 403 | 404 | const struct in6_addr *broadcast = query_broadcast(self->ifindex); 405 | if (!broadcast) return false; 406 | uint32_t broadcast_addr; 407 | memcpy(&broadcast_addr, ipaddr_v4_bytes(broadcast), sizeof broadcast_addr); 408 | add_option_broadcast(reply, broadcast_addr); 409 | 410 | const struct in6_addr *router = query_gateway_v4(self->ifindex); 411 | if (router) { 412 | uint32_t router_addr; 413 | memcpy(&router_addr, ipaddr_v4_bytes(router), sizeof router_addr); 414 | add_option_router(reply, router_addr); 415 | } 416 | 417 | struct addrlist dns_servers = query_dns_servers(self->ifindex); 418 | if (dns_servers.n) iplist_option(reply, DCODE_DNS, &dns_servers); 419 | 420 | struct addrlist ntp_servers = query_ntp_servers(self->ifindex); 421 | if (ntp_servers.n) iplist_option(reply, DCODE_NTPSVR, &ntp_servers); 422 | 423 | struct blob d4b = query_dns4_search_blob(self->ifindex); 424 | if (d4b.n && d4b.s) add_option_domain_name(reply, d4b.s, d4b.n); 425 | 426 | log_line("dhcp4: Sending reply %u.%u.%u.%u\n", reply->yiaddr & 255, 427 | (reply->yiaddr >> 8) & 255, (reply->yiaddr >> 16) & 255, (reply->yiaddr >> 24) & 255); 428 | 429 | return true; 430 | } 431 | 432 | static void reply_discover(struct D4Listener *self) 433 | { 434 | struct dhcpmsg reply; 435 | dhcpmsg_init(self, &reply, DHCPOFFER, self->dhcpmsg.xid); 436 | if (create_reply(self, &reply, self->dhcpmsg.chaddr, true)) 437 | send_reply(self, &reply); 438 | } 439 | 440 | static void reply_request(struct D4Listener *self) 441 | { 442 | struct dhcpmsg reply; 443 | dhcpmsg_init(self, &reply, DHCPACK, self->dhcpmsg.xid); 444 | if (create_reply(self, &reply, self->dhcpmsg.chaddr, true)) { 445 | send_reply(self, &reply); 446 | } 447 | D4State_kill(self->map, self->dhcpmsg.chaddr); 448 | } 449 | 450 | static void reply_inform(struct D4Listener *self) 451 | { 452 | struct dhcpmsg reply; 453 | dhcpmsg_init(self, &reply, DHCPACK, self->dhcpmsg.xid); 454 | if (create_reply(self, &reply, self->dhcpmsg.chaddr, false)) { 455 | // http://tools.ietf.org/html/draft-ietf-dhc-dhcpinform-clarify-06 456 | reply.htype = self->dhcpmsg.htype; 457 | reply.hlen = self->dhcpmsg.hlen; 458 | memcpy(&reply.chaddr, &self->dhcpmsg.chaddr, sizeof reply.chaddr); 459 | reply.ciaddr = self->dhcpmsg.ciaddr; 460 | // xid was already set equal 461 | reply.flags = self->dhcpmsg.flags; 462 | reply.hops = 0; 463 | reply.secs = 0; 464 | reply.yiaddr = 0; 465 | reply.siaddr = 0; 466 | if (self->dhcpmsg.ciaddr) 467 | send_reply_do(self, &reply, SRT_UnicastCi); 468 | else if (self->dhcpmsg.giaddr) { 469 | uint16_t fl = ntohs(reply.flags); 470 | reply.flags = htons(fl | 0x8000u); 471 | send_reply_do(self, &reply, SRT_Relay); 472 | } else 473 | send_reply_do(self, &reply, SRT_Broadcast); 474 | } 475 | } 476 | 477 | static void do_release(struct D4Listener *self) 478 | { 479 | struct in6_addr ciaddr = u32_ipaddr(self->dhcpmsg.ciaddr); 480 | if (!dynlease4_exists(self->ifindex, &ciaddr, self->dhcpmsg.chaddr)) { 481 | char buf[32] = "invalid ip"; 482 | ip4_to_string(buf, sizeof buf, self->dhcpmsg.ciaddr); 483 | log_line("dhcp4: do_release: ignoring spoofed release request for %s.\n", buf); 484 | return; 485 | } 486 | dynlease4_del(self->ifindex, &ciaddr, self->dhcpmsg.chaddr); 487 | } 488 | 489 | static uint8_t validate_dhcp(const struct D4Listener *self, size_t len) 490 | { 491 | if (len < offsetof(struct dhcpmsg, options)) 492 | return DHCPNULL; 493 | if (ntohl(self->dhcpmsg.cookie) != DHCP_MAGIC) 494 | return DHCPNULL; 495 | return get_option_msgtype(&self->dhcpmsg); 496 | } 497 | 498 | static void process_receive(struct D4Listener *self, const char *buf, size_t buflen, 499 | const struct sockaddr_in *sai) 500 | { 501 | if (sai->sin_family != AF_INET) return; 502 | size_t msglen = buflen < sizeof self->dhcpmsg ? buflen : sizeof self->dhcpmsg; 503 | self->dhcpmsg = (struct dhcpmsg){0}; 504 | memcpy(&self->dhcpmsg, buf, msglen); 505 | uint8_t msgtype = validate_dhcp(self, msglen); 506 | if (!msgtype) 507 | return; 508 | 509 | uint8_t cs = D4State_get(self->map, self->dhcpmsg.xid, self->dhcpmsg.chaddr); 510 | if (cs == DHCPNULL) { 511 | switch (msgtype) { 512 | case DHCPREQUEST: 513 | case DHCPDISCOVER: 514 | cs = msgtype; 515 | if (!D4State_add(self->map, self->dhcpmsg.xid, self->dhcpmsg.chaddr, cs)) 516 | return; // Possible DoS; silently drop. 517 | break; 518 | case DHCPINFORM: 519 | // No need to track state since we just INFORM => ACK 520 | case DHCPDECLINE: 521 | case DHCPRELEASE: 522 | cs = msgtype; 523 | break; 524 | default: return; 525 | } 526 | } else { 527 | if (cs == DHCPDISCOVER && msgtype == DHCPREQUEST) 528 | cs = DHCPREQUEST; 529 | } 530 | 531 | switch (cs) { 532 | case DHCPDISCOVER: reply_discover(self); break; 533 | case DHCPREQUEST: reply_request(self); break; 534 | case DHCPINFORM: reply_inform(self); break; 535 | case DHCPDECLINE: log_line("dhcp4: Received a DHCPDECLINE. Clients conflict?\n"); 536 | case DHCPRELEASE: do_release(self); break; 537 | } 538 | } 539 | 540 | -------------------------------------------------------------------------------- /dhcp4.h: -------------------------------------------------------------------------------- 1 | // Copyright 2011-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_DHCP4_H 4 | #define NDHS_DHCP4_H 5 | 6 | struct netif_info; 7 | struct D4Listener; 8 | struct D4Listener *D4Listener_create(const char *ifname, const struct netif_info *ifinfo); 9 | void D4Listener_process_input(struct D4Listener *self); 10 | int D4Listener_fd(const struct D4Listener *self); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /dhcp6.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_DHCP6_H_ 4 | #define NDHS_DHCP6_H_ 5 | 6 | #include 7 | 8 | struct netif_info; 9 | struct D6Listener; 10 | struct D6Listener *D6Listener_create(const char *ifname, const struct netif_info *ifinfo, uint8_t preference); 11 | void D6Listener_process_input(struct D6Listener *self); 12 | void D6Listener_destroy(struct D6Listener *self); 13 | int D6Listener_fd(const struct D6Listener *self); 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /dhcp_state.c: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include "dhcp_state.h" 5 | #include 6 | #include 7 | #include "nk/log.h" 8 | 9 | extern struct NLSocket nl_socket; 10 | 11 | struct str_slist 12 | { 13 | struct str_slist *next; 14 | char str[]; 15 | }; 16 | 17 | static void str_slist_append(struct str_slist **head, const char *s, size_t slen) 18 | { 19 | struct str_slist **e = head; 20 | while (*e) e = &(*e)->next; 21 | assert(!*e); 22 | *e = malloc(sizeof(struct str_slist) + slen + 1); 23 | if (!*e) abort(); 24 | (*e)->next = NULL; 25 | *(char *)mempcpy(&((*e)->str), s, slen) = 0; 26 | } 27 | 28 | static void str_slist_destroy(struct str_slist **head) 29 | { 30 | struct str_slist *e = *head; 31 | while (e) { 32 | struct str_slist *n = e->next; 33 | free(e); 34 | e = n; 35 | } 36 | *head = NULL; 37 | } 38 | 39 | struct interface_data 40 | { 41 | int ifindex; 42 | struct dhcpv4_entry *s4addrs; // static assigned v4 leases 43 | struct dhcpv6_entry *s6addrs; // static assigned v6 leases 44 | struct addrlist dnsaddrs; 45 | struct addrlist ntpaddrs; 46 | struct in6_addr subnet; 47 | struct in6_addr broadcast; 48 | struct in6_addr gateway_v4; 49 | struct in6_addr dynamic_range_lo; 50 | struct in6_addr dynamic_range_hi; 51 | struct str_slist *p_dns_search; 52 | char *d4_dns_search_blob; 53 | char *ra6_dns_search_blob; 54 | size_t d4_dns_search_blob_size; 55 | size_t ra6_dns_search_blob_size; 56 | uint32_t dynamic_lifetime; 57 | uint8_t preference; 58 | bool use_dhcpv4:1; 59 | bool use_dhcpv6:1; 60 | bool use_dynamic_v4:1; 61 | bool use_dynamic_v6:1; 62 | }; 63 | 64 | static struct interface_data *interface_state[MAX_NL_INTERFACES]; 65 | 66 | // Performs DNS label wire encoding cf RFC1035 3.1 67 | // Returns negative values on error, positive values are the number 68 | // of bytes added to the out buffer. 69 | // return of -2 means input was invalid 70 | // return of -1 means out buffer ran out of space 71 | #define MAX_DNS_LABELS 256 72 | static int dns_label(char *out, size_t outlen, const char *ds) 73 | { 74 | size_t locx[MAX_DNS_LABELS * 2]; 75 | size_t locn = 0; 76 | size_t dslen = strlen(ds); 77 | const char *out_st = out; 78 | 79 | if (dslen <= 0) 80 | return 0; 81 | 82 | // First we build up a list of label start/end offsets. 83 | size_t s = 0, idx = 0; 84 | bool in_label = false; 85 | for (size_t j = 0; j < dslen; ++j) { 86 | char i = ds[j]; 87 | if (i == '.') { 88 | if (in_label) { 89 | if (locn >= MAX_DNS_LABELS) return -2; // label too long 90 | locx[2 * locn ] = s; 91 | locx[2 * locn + 1] = idx; 92 | ++locn; 93 | in_label = false; 94 | } else { 95 | return -2; // malformed input 96 | } 97 | } else { 98 | if (!in_label) { 99 | s = idx; 100 | in_label = true; 101 | } 102 | } 103 | ++idx; 104 | } 105 | // We don't demand a trailing dot. 106 | if (in_label) { 107 | if (locn >= MAX_DNS_LABELS) return -2; 108 | locx[2 * locn ] = s; 109 | locx[2 * locn + 1] = idx; 110 | ++locn; 111 | in_label = false; 112 | } 113 | 114 | // Now we just need to attach the label length octet followed 115 | // by the label contents. 116 | for (size_t i = 0, imax = locn; i < imax; ++i) { 117 | size_t st = locx[2 * i]; 118 | size_t en = locx[2 * i + 1]; 119 | size_t len = en >= st ? en - st : 0; 120 | if (len > 63) return -2; // label too long 121 | if (outlen < 1 + en - st) return -1; // out of space 122 | *out++ = len; --outlen; 123 | memcpy(out, &ds[st], en - st); 124 | out += len; 125 | outlen -= len; 126 | } 127 | // Terminating zero length label. 128 | if (outlen < 1) return -1; // out of space 129 | *out++ = 0; --outlen; 130 | 131 | // false means domain name is too long 132 | return out - out_st <= 255 ? out - out_st : -2; 133 | } 134 | 135 | static void create_d4_dns_search_blob(char **out, size_t *outlen, 136 | struct str_slist *dns_search) 137 | { 138 | size_t blen = 0; 139 | char buf[256]; // must be >= 255 bytes 140 | 141 | for (struct str_slist *e = dns_search; e; e = e->next) { 142 | int r = dns_label(buf + blen, sizeof buf - blen, e->str); 143 | if (r < 0) { 144 | if (r == -1) { 145 | log_line("too many names in dns_search\n"); 146 | break; 147 | } else { 148 | log_line("malformed input to dns_search\n"); 149 | continue; 150 | } 151 | } 152 | blen += (size_t)r; 153 | } 154 | assert(blen <= 255); 155 | if (*out) { free(*out); *out = NULL; } 156 | *out = malloc(blen); 157 | if (!*out) abort(); 158 | *outlen = blen; 159 | memcpy(*out, buf, blen); 160 | } 161 | 162 | static void create_ra6_dns_search_blob(char **out, size_t *outlen, 163 | struct str_slist *dns_search) 164 | { 165 | size_t blen = 0; 166 | char buf[2048]; // must be >= 8*256 bytes 167 | 168 | for (struct str_slist *e = dns_search; e; e = e->next) { 169 | int r = dns_label(buf + blen, sizeof buf - blen, e->str); 170 | if (r < 0) { 171 | if (r == -1) { 172 | log_line("too many names in dns_search\n"); 173 | break; 174 | } else { 175 | log_line("malformed input to dns_search\n"); 176 | continue; 177 | } 178 | } 179 | blen += (size_t)r; 180 | } 181 | assert(blen <= 8 * 254); 182 | if (*out) { free(*out); *out = NULL; } 183 | *out = malloc(blen); 184 | if (!*out) abort(); 185 | *outlen = blen; 186 | memcpy(*out, buf, blen); 187 | } 188 | 189 | void create_blobs(void) 190 | { 191 | for (size_t i = 0; i < MAX_NL_INTERFACES; ++i) { 192 | struct interface_data *p = interface_state[i]; 193 | if (p) { 194 | create_d4_dns_search_blob(&p->d4_dns_search_blob, &p->d4_dns_search_blob_size, p->p_dns_search); 195 | create_ra6_dns_search_blob(&p->ra6_dns_search_blob, &p->ra6_dns_search_blob_size, p->p_dns_search); 196 | str_slist_destroy(&p->p_dns_search); 197 | } 198 | } 199 | } 200 | 201 | static struct interface_data *lookup_interface_by_name(const char *interface) 202 | { 203 | if (!strlen(interface)) return NULL; 204 | 205 | int ifindex = NLSocket_get_ifindex(&nl_socket, interface); 206 | if (ifindex == -1) return NULL; 207 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 208 | return interface_state[ifindex]; 209 | } 210 | 211 | static struct interface_data *lookup_or_create_interface(const char *interface) 212 | { 213 | if (!strlen(interface)) return NULL; 214 | struct interface_data *is = lookup_interface_by_name(interface); 215 | if (!is) { 216 | int ifindex = NLSocket_get_ifindex(&nl_socket, interface); 217 | if (ifindex == -1) return NULL; 218 | assert(!interface_state[ifindex]); 219 | interface_state[ifindex] = calloc(1, sizeof(struct interface_data)); 220 | if (!interface_state[ifindex]) abort(); 221 | interface_state[ifindex]->ifindex = ifindex; 222 | is = interface_state[ifindex]; 223 | } 224 | return is; 225 | } 226 | 227 | bool emplace_bind4(size_t linenum, const char *interface) 228 | { 229 | struct interface_data *is = lookup_or_create_interface(interface); 230 | if (!is) { 231 | log_line("interface specified at line %zu does not exist\n", linenum); 232 | return false; 233 | } 234 | is->use_dhcpv4 = true; 235 | return true; 236 | } 237 | 238 | bool emplace_bind6(size_t linenum, const char *interface) 239 | { 240 | struct interface_data *is = lookup_or_create_interface(interface); 241 | if (!is) { 242 | log_line("interface specified at line %zu does not exist\n", linenum); 243 | return false; 244 | } 245 | is->use_dhcpv6 = true; 246 | return true; 247 | } 248 | 249 | int emplace_interface(size_t linenum, const char *interface, uint8_t preference) 250 | { 251 | struct interface_data *is = lookup_interface_by_name(interface); 252 | if (is) { 253 | is->preference = preference; 254 | return is->ifindex; 255 | } 256 | log_line("interface specified at line %zu is not bound\n", linenum); 257 | return -1; 258 | } 259 | 260 | bool emplace_dhcp6_state(size_t linenum, int ifindex, 261 | const char *duid, size_t duid_len, 262 | uint32_t iaid, const struct in6_addr *v6_addr, uint32_t default_lifetime) 263 | { 264 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 265 | struct interface_data *is = interface_state[ifindex]; 266 | if (is) { 267 | struct dhcpv6_entry *t = malloc(sizeof(struct dhcpv6_entry) + duid_len); 268 | if (!t) abort(); 269 | memcpy(t->duid, duid, duid_len); 270 | t->address = *v6_addr; 271 | t->duid_len = duid_len; 272 | t->lifetime = default_lifetime; 273 | t->iaid = iaid; 274 | t->next = is->s6addrs; 275 | is->s6addrs = t; 276 | return true; 277 | } 278 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 279 | return false; 280 | } 281 | 282 | bool emplace_dhcp4_state(size_t linenum, int ifindex, const uint8_t *macaddr, 283 | const struct in6_addr *v4_addr, uint32_t default_lifetime) 284 | { 285 | if (!ipaddr_is_v4(v4_addr)) { 286 | log_line("Bad IPv4 address at line %zu\n", linenum); 287 | return false; 288 | } 289 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 290 | struct interface_data *is = interface_state[ifindex]; 291 | if (is) { 292 | struct dhcpv4_entry *t = malloc(sizeof(struct dhcpv4_entry)); 293 | if (!t) abort(); 294 | memcpy(t->macaddr, macaddr, sizeof t->macaddr); 295 | t->address = *v4_addr; 296 | t->lifetime = default_lifetime; 297 | t->next = is->s4addrs; 298 | is->s4addrs = t; 299 | return true; 300 | } 301 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 302 | return false; 303 | } 304 | 305 | bool emplace_dns_servers(size_t linenum, int ifindex, struct in6_addr *addrs, size_t naddrs) 306 | { 307 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 308 | struct interface_data *is = interface_state[ifindex]; 309 | if (is) { 310 | free(is->dnsaddrs.addrs); 311 | is->dnsaddrs.n = naddrs; 312 | is->dnsaddrs.addrs = addrs; 313 | return true; 314 | } 315 | free(addrs); 316 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 317 | return false; 318 | } 319 | 320 | bool emplace_ntp_servers(size_t linenum, int ifindex, struct in6_addr *addrs, size_t naddrs) 321 | { 322 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 323 | struct interface_data *is = interface_state[ifindex]; 324 | if (is) { 325 | free(is->ntpaddrs.addrs); 326 | is->ntpaddrs.n = naddrs; 327 | is->ntpaddrs.addrs = addrs; 328 | return true; 329 | } 330 | free(addrs); 331 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 332 | return false; 333 | } 334 | 335 | bool emplace_subnet(int ifindex, const struct in6_addr *addr) 336 | { 337 | if (!ipaddr_is_v4(addr)) { 338 | log_line("%s: Bad IP address for interface #%d\n", __func__, ifindex); 339 | return false; 340 | } 341 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 342 | struct interface_data *is = interface_state[ifindex]; 343 | if (is) { 344 | is->subnet = *addr; 345 | return true; 346 | } 347 | return false; 348 | } 349 | 350 | bool emplace_gateway_v4(size_t linenum, int ifindex, const struct in6_addr *addr) 351 | { 352 | if (!ipaddr_is_v4(addr)) { 353 | log_line("%s: Bad IP address for interface #%d\n", __func__, ifindex); 354 | return false; 355 | } 356 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 357 | struct interface_data *is = interface_state[ifindex]; 358 | if (is) { 359 | is->gateway_v4 = *addr; 360 | return true; 361 | } 362 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 363 | return false; 364 | } 365 | 366 | bool emplace_broadcast(int ifindex, const struct in6_addr *addr) 367 | { 368 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 369 | struct interface_data *is = interface_state[ifindex]; 370 | if (is) { 371 | is->broadcast = *addr; 372 | return true; 373 | } 374 | return false; 375 | } 376 | 377 | bool emplace_dynamic_range(size_t linenum, int ifindex, 378 | const struct in6_addr *lo_addr, const struct in6_addr *hi_addr, 379 | uint32_t dynamic_lifetime) 380 | { 381 | if (!ipaddr_is_v4(lo_addr) || !ipaddr_is_v4(hi_addr)) { 382 | log_line("Bad IPv4 address at line %zu\n", linenum); 383 | return false; 384 | } 385 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 386 | struct interface_data *is = interface_state[ifindex]; 387 | if (is) { 388 | bool inorder = memcmp(lo_addr, hi_addr, sizeof *lo_addr) <= 0; 389 | is->dynamic_range_lo = inorder? *lo_addr : *hi_addr; 390 | is->dynamic_range_hi = inorder? *hi_addr : *lo_addr; 391 | is->dynamic_lifetime = dynamic_lifetime; 392 | is->use_dynamic_v4 = true; 393 | return true; 394 | } 395 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 396 | return false; 397 | } 398 | 399 | bool emplace_dynamic_v6(size_t linenum, int ifindex) 400 | { 401 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 402 | struct interface_data *is = interface_state[ifindex]; 403 | if (is) { 404 | is->use_dynamic_v6 = true; 405 | return true; 406 | } 407 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 408 | return false; 409 | } 410 | 411 | bool emplace_dns_search(size_t linenum, int ifindex, const char *label, size_t label_len) 412 | { 413 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 414 | struct interface_data *is = interface_state[ifindex]; 415 | if (is) { 416 | str_slist_append(&is->p_dns_search, label, label_len); 417 | return true; 418 | } 419 | log_line("%s: No interface specified at line %zu\n", __func__, linenum); 420 | return false; 421 | } 422 | 423 | const struct dhcpv6_entry *query_dhcp6_state(int ifindex, 424 | const char *duid, size_t duid_len, 425 | uint32_t iaid) 426 | { 427 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 428 | struct interface_data *is = interface_state[ifindex]; 429 | if (!is) return NULL; 430 | for (struct dhcpv6_entry *p = is->s6addrs; p; p = p->next) { 431 | if (p->duid_len == duid_len && p->iaid == iaid 432 | && !memcmp(p->duid, duid, duid_len)) 433 | return p; 434 | } 435 | return NULL; 436 | } 437 | 438 | const struct dhcpv4_entry *query_dhcp4_state(int ifindex, const uint8_t *hwaddr) 439 | { 440 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 441 | struct interface_data *is = interface_state[ifindex]; 442 | if (!is) return NULL; 443 | for (struct dhcpv4_entry *p = is->s4addrs; p; p = p->next) { 444 | if (!memcmp(p->macaddr, hwaddr, sizeof p->macaddr)) return p; 445 | } 446 | return NULL; 447 | } 448 | 449 | struct addrlist query_dns_servers(int ifindex) 450 | { 451 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) { 452 | err: return (struct addrlist){ .n = 0, .addrs = NULL }; 453 | } 454 | struct interface_data *is = interface_state[ifindex]; 455 | if (!is) goto err; 456 | return is->dnsaddrs; 457 | } 458 | 459 | struct addrlist query_ntp_servers(int ifindex) 460 | { 461 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) { 462 | err: return (struct addrlist){ .n = 0, .addrs = NULL }; 463 | } 464 | struct interface_data *is = interface_state[ifindex]; 465 | if (!is) goto err; 466 | return is->ntpaddrs; 467 | } 468 | 469 | struct blob query_dns4_search_blob(int ifindex) 470 | { 471 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) { 472 | err: return (struct blob){ .n = 0, .s = NULL }; 473 | } 474 | struct interface_data *is = interface_state[ifindex]; 475 | if (!is) goto err; 476 | return (struct blob){ .n = is->d4_dns_search_blob_size, .s = is->d4_dns_search_blob }; 477 | } 478 | 479 | struct blob query_dns6_search_blob(int ifindex) 480 | { 481 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) { 482 | err: return (struct blob){ .n = 0, .s = NULL }; 483 | } 484 | struct interface_data *is = interface_state[ifindex]; 485 | if (!is) goto err; 486 | return (struct blob){ .n = is->ra6_dns_search_blob_size, .s = is->ra6_dns_search_blob }; 487 | } 488 | 489 | const struct in6_addr *query_gateway_v4(int ifindex) 490 | { 491 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 492 | struct interface_data *is = interface_state[ifindex]; 493 | if (!is) return NULL; 494 | return &is->gateway_v4; 495 | } 496 | 497 | const struct in6_addr *query_subnet(int ifindex) 498 | { 499 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 500 | struct interface_data *is = interface_state[ifindex]; 501 | if (!is) return NULL; 502 | return &is->subnet; 503 | } 504 | 505 | const struct in6_addr *query_broadcast(int ifindex) 506 | { 507 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 508 | struct interface_data *is = interface_state[ifindex]; 509 | if (!is) return NULL; 510 | return &is->broadcast; 511 | } 512 | 513 | bool query_dynamic_range(int ifindex, struct in6_addr *lo, struct in6_addr *hi) 514 | { 515 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 516 | struct interface_data *is = interface_state[ifindex]; 517 | if (!is) return false; 518 | *lo = is->dynamic_range_lo; 519 | *hi = is->dynamic_range_hi; 520 | return true; 521 | } 522 | 523 | bool query_use_dynamic_v4(int ifindex, uint32_t *dynamic_lifetime) 524 | { 525 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 526 | struct interface_data *is = interface_state[ifindex]; 527 | if (!is) return false; 528 | *dynamic_lifetime = is->dynamic_lifetime; 529 | return is->use_dynamic_v4; 530 | } 531 | 532 | bool query_use_dynamic_v6(int ifindex, uint32_t *dynamic_lifetime) 533 | { 534 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 535 | struct interface_data *is = interface_state[ifindex]; 536 | if (!is) return false; 537 | *dynamic_lifetime = is->dynamic_lifetime; 538 | return is->use_dynamic_v6; 539 | } 540 | 541 | bool query_unused_addr_v6(int ifindex, const struct in6_addr *addr) 542 | { 543 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 544 | struct interface_data *is = interface_state[ifindex]; 545 | if (!is) return true; 546 | for (struct dhcpv6_entry *p = is->s6addrs; p; p = p->next) { 547 | if (!memcmp(&p->address, addr, sizeof *addr)) return false; 548 | } 549 | return true; 550 | } 551 | 552 | size_t bound_interfaces_count(void) 553 | { 554 | size_t ret = 0; 555 | for (size_t i = 0; i < MAX_NL_INTERFACES; ++i) { 556 | if (interface_state[i]) ++ret; 557 | } 558 | return ret; 559 | } 560 | 561 | void bound_interfaces_foreach(void (*fn)(const struct netif_info *, bool, bool, uint8_t, void *), void *userptr) 562 | { 563 | for (size_t i = 0; i < MAX_NL_INTERFACES; ++i) { 564 | struct interface_data *p = interface_state[i]; 565 | if (p) { 566 | struct netif_info *ifinfo = NLSocket_get_ifinfo(&nl_socket, p->ifindex); 567 | if (!ifinfo) continue; 568 | fn(ifinfo, p->use_dhcpv4, p->use_dhcpv6, p->preference, userptr); 569 | } 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /dhcp_state.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_DHCP_STATE_H_ 4 | #define NDHS_DHCP_STATE_H_ 5 | 6 | #include 7 | 8 | #include 9 | 10 | struct dhcpv6_entry { 11 | struct dhcpv6_entry *next; 12 | struct in6_addr address; 13 | size_t duid_len; 14 | uint32_t lifetime; 15 | uint32_t iaid; 16 | char duid[]; 17 | }; 18 | 19 | struct dhcpv4_entry { 20 | struct dhcpv4_entry *next; 21 | uint8_t macaddr[6]; 22 | struct in6_addr address; 23 | uint32_t lifetime; 24 | }; 25 | 26 | struct blob { 27 | size_t n; 28 | const char *s; 29 | }; 30 | 31 | struct addrlist { 32 | size_t n; 33 | struct in6_addr *addrs; 34 | }; 35 | 36 | void create_blobs(void); 37 | bool emplace_bind4(size_t linenum, const char *interface); 38 | bool emplace_bind6(size_t linenum, const char *interface); 39 | int emplace_interface(size_t linenum, const char *interface, uint8_t preference); 40 | bool emplace_dhcp6_state(size_t linenum, int ifindex, 41 | const char *duid, size_t duid_len, 42 | uint32_t iaid, const struct in6_addr *v6_addr, uint32_t default_lifetime); 43 | bool emplace_dhcp4_state(size_t linenum, int ifindex, const uint8_t *macaddr, 44 | const struct in6_addr *v4_addr, uint32_t default_lifetime); 45 | bool emplace_dns_servers(size_t linenum, int ifindex, struct in6_addr *addrs, size_t naddrs); 46 | bool emplace_ntp_servers(size_t linenum, int ifindex, struct in6_addr *addrs, size_t naddrs); 47 | bool emplace_subnet(int ifindex, const struct in6_addr *addr); 48 | bool emplace_gateway_v4(size_t linenum, int ifindex, const struct in6_addr *addr); 49 | bool emplace_broadcast(int ifindex, const struct in6_addr *addr); 50 | bool emplace_dynamic_range(size_t linenum, int ifindex, 51 | const struct in6_addr *lo_addr, const struct in6_addr *hi_addr, 52 | uint32_t dynamic_lifetime); 53 | bool emplace_dynamic_v6(size_t linenum, int ifindex); 54 | bool emplace_dns_search(size_t linenum, int ifindex, const char *label, size_t label_len); 55 | const struct dhcpv6_entry *query_dhcp6_state(int ifindex, 56 | const char *duid, size_t duid_len, 57 | uint32_t iaid); 58 | const struct dhcpv4_entry *query_dhcp4_state(int ifindex, const uint8_t *hwaddr); 59 | struct addrlist query_dns_servers(int ifindex); 60 | struct addrlist query_ntp_servers(int ifindex); 61 | struct blob query_dns4_search_blob(int ifindex); 62 | struct blob query_dns6_search_blob(int ifindex); 63 | const struct in6_addr *query_gateway_v4(int ifindex); 64 | const struct in6_addr *query_subnet(int ifindex); 65 | const struct in6_addr *query_broadcast(int ifindex); 66 | bool query_dynamic_range(int ifindex, struct in6_addr *lo, struct in6_addr *hi); 67 | bool query_use_dynamic_v4(int ifindex, uint32_t *dynamic_lifetime); 68 | bool query_use_dynamic_v6(int ifindex, uint32_t *dynamic_lifetime); 69 | bool query_unused_addr_v6(int ifindex, const struct in6_addr *addr); 70 | size_t bound_interfaces_count(void); 71 | void bound_interfaces_foreach(void (*fn)(const struct netif_info *, bool, bool, uint8_t, void *), void *userptr); 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /duid.c: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "nk/io.h" 12 | #include "nk/log.h" 13 | #include "nk/random.h" 14 | #include "duid.h" 15 | 16 | extern struct nk_random_state g_rngstate; 17 | 18 | #define DUID_PATH "/store/duid.txt" 19 | char g_server_duid[SERVER_DUID_LEN]; 20 | 21 | static void print_duid(void) 22 | { 23 | if (SERVER_DUID_LEN <= 0) return; 24 | log_line("DUID is '"); 25 | char tbuf[16] = {0}; 26 | for (unsigned i = 0; i < SERVER_DUID_LEN; ++i) { 27 | snprintf(tbuf, sizeof tbuf, "%.2hhx", (uint8_t)g_server_duid[i]); 28 | log_line("%s", tbuf); 29 | } 30 | log_line("'\n"); 31 | } 32 | 33 | // Use DUID-UUID (RFC6355) 34 | static void generate_duid(void) 35 | { 36 | size_t off = 0; 37 | 38 | uint16_t typefield = htons(4); 39 | memcpy(g_server_duid + off, &typefield, sizeof typefield); 40 | off += sizeof typefield; 41 | uint64_t r0 = nk_random_u64(&g_rngstate); 42 | uint64_t r1 = nk_random_u64(&g_rngstate); 43 | memcpy(g_server_duid + off, &r0, sizeof r0); 44 | off += sizeof r0; 45 | memcpy(g_server_duid + off, &r1, sizeof r1); 46 | off += sizeof r1; 47 | 48 | int fd = open(DUID_PATH, O_WRONLY|O_TRUNC|O_CREAT|O_CLOEXEC, 0644); 49 | if (fd < 0) suicide("%s: failed to open %s for write\n", __func__, DUID_PATH); 50 | ssize_t r = safe_write(fd, g_server_duid, SERVER_DUID_LEN); 51 | if (r < 0 || r != SERVER_DUID_LEN) 52 | suicide("%s: failed to write duid to %s\n", __func__, DUID_PATH); 53 | print_duid(); 54 | close(fd); 55 | } 56 | 57 | void duid_load_from_file(void) 58 | { 59 | int fd = open(DUID_PATH, O_RDONLY|O_CLOEXEC, 0); 60 | if (fd < 0) { 61 | log_line("No DUID found. Generating a DUID.\n"); 62 | generate_duid(); 63 | return; 64 | } 65 | ssize_t r = safe_read(fd, g_server_duid, SERVER_DUID_LEN); 66 | if (r < 0) suicide("%s: failed to read duid from %s\n", __func__, DUID_PATH); 67 | if (r != SERVER_DUID_LEN) { 68 | log_line("DUID is too short to be valid. Generating a new DUID.\n"); 69 | generate_duid(); 70 | goto out0; 71 | } 72 | print_duid(); 73 | out0: 74 | close(fd); 75 | } 76 | 77 | -------------------------------------------------------------------------------- /duid.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_DUID_H_ 4 | #define NDHS_DUID_H_ 5 | 6 | #include 7 | #define SERVER_DUID_LEN (sizeof(uint16_t) + sizeof(uint64_t) * 2) 8 | 9 | extern char g_server_duid[SERVER_DUID_LEN]; 10 | void duid_load_from_file(void); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /dynlease.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_DYNLEASE_H_ 4 | #define NDHS_DYNLEASE_H_ 5 | 6 | #include 7 | 8 | size_t dynlease6_count(int ifindex); 9 | void dynlease_gc(void); 10 | bool dynlease4_add(int ifindex, const struct in6_addr *addr, 11 | const uint8_t *macaddr, int64_t expire_time); 12 | bool dynlease6_add(int ifindex, const struct in6_addr *addr, 13 | const char *duid, size_t duid_len, 14 | uint32_t iaid, int64_t expire_time); 15 | struct in6_addr dynlease4_query_refresh(int ifindex, const uint8_t *macaddr, 16 | int64_t expire_time); 17 | struct in6_addr dynlease6_query_refresh(int ifindex, 18 | const char *duid, size_t duid_len, 19 | uint32_t iaid, int64_t expire_time); 20 | bool dynlease4_exists(int ifindex, const struct in6_addr *addr, 21 | const uint8_t *macaddr); 22 | bool dynlease4_del(int ifindex, const struct in6_addr *addr, 23 | const uint8_t *macaddr); 24 | bool dynlease6_del(int ifindex, const struct in6_addr *addr, 25 | const char *duid, size_t duid_len, uint32_t iaid); 26 | bool dynlease_serialize(const char *path); 27 | bool dynlease_deserialize(const char *path); 28 | 29 | #endif 30 | -------------------------------------------------------------------------------- /dynlease.rl: -------------------------------------------------------------------------------- 1 | // -*- c -*- 2 | // Copyright 2016-2024 Nicholas J. Kain 3 | // SPDX-License-Identifier: MIT 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include // for MAX_NL_INTERFACES 11 | #include 12 | #include 13 | #include "nk/log.h" 14 | #include 15 | #include 16 | 17 | #define MAX_LINE 2048 18 | // The RFC allows for 128 raw bytes, which corresponds 19 | // to a value of 256. 20 | #define MAX_DUID 256 21 | 22 | extern struct NLSocket nl_socket; 23 | 24 | struct lease_state_v4 25 | { 26 | struct lease_state_v4 *next; 27 | struct in6_addr addr; 28 | uint8_t macaddr[6]; 29 | int64_t expire_time; 30 | }; 31 | 32 | struct lease_state_v6 33 | { 34 | struct lease_state_v6 *next; 35 | struct in6_addr addr; 36 | size_t duid_len; 37 | int64_t expire_time; 38 | uint32_t iaid; 39 | char duid[]; // not null terminated, hex string 40 | }; 41 | 42 | // Maps interfaces to lease data. 43 | static struct lease_state_v4 *dyn_leases_v4[MAX_NL_INTERFACES]; 44 | static struct lease_state_v6 *dyn_leases_v6[MAX_NL_INTERFACES]; 45 | static struct lease_state_v4 *ls4_freelist; 46 | static struct lease_state_v6 *ls6_freelist; 47 | static uint32_t n_leases_v6[MAX_NL_INTERFACES]; 48 | 49 | size_t dynlease6_count(int ifindex) 50 | { 51 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return 0; 52 | return n_leases_v6[ifindex]; 53 | } 54 | 55 | void dynlease_gc(void) 56 | { 57 | int64_t ts = get_current_ts(); 58 | for (size_t i = 0; i < MAX_NL_INTERFACES; ++i) { 59 | if (dyn_leases_v4[i]) { 60 | struct lease_state_v4 **prev = &dyn_leases_v4[i]; 61 | for (struct lease_state_v4 *p = dyn_leases_v4[i]; p;) { 62 | if (p->expire_time < ts) { 63 | *prev = p->next; 64 | p->next = ls4_freelist; 65 | ls4_freelist = p->next; 66 | p = p->next; 67 | } 68 | if (p) { 69 | prev = &p->next; 70 | p = p->next; 71 | } 72 | } 73 | } 74 | if (dyn_leases_v6[i]) { 75 | struct lease_state_v6 **prev = &dyn_leases_v6[i]; 76 | for (struct lease_state_v6 *p = dyn_leases_v6[i]; p;) { 77 | if (p->expire_time < ts) { 78 | *prev = p->next; 79 | p->next = ls6_freelist; 80 | ls6_freelist = p->next; 81 | p = p->next; 82 | assert(n_leases_v6[i] > 0); 83 | --n_leases_v6[i]; 84 | } 85 | if (p) { 86 | prev = &p->next; 87 | p = p->next; 88 | } 89 | } 90 | } 91 | } 92 | } 93 | 94 | bool dynlease4_add(int ifindex, const struct in6_addr *v4_addr, const uint8_t *macaddr, 95 | int64_t expire_time) 96 | { 97 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 98 | 99 | for (struct lease_state_v4 *p = dyn_leases_v4[ifindex]; p; p = p->next) { 100 | if (!memcmp(&p->addr, v4_addr, sizeof p->addr)) { 101 | if (!memcmp(&p->macaddr, macaddr, 6)) { 102 | p->expire_time = expire_time; 103 | return true; 104 | } 105 | return false; 106 | } 107 | } 108 | struct lease_state_v4 *n = ls4_freelist; 109 | if (n) { 110 | ls4_freelist = n->next; 111 | } else { 112 | n = malloc(sizeof(struct lease_state_v4)); 113 | if (!n) abort(); 114 | } 115 | n->next = dyn_leases_v4[ifindex]; 116 | n->addr = *v4_addr; 117 | memcpy(n->macaddr, macaddr, sizeof n->macaddr); 118 | n->expire_time = expire_time; 119 | dyn_leases_v4[ifindex] = n; 120 | return true; 121 | } 122 | 123 | static bool duid_compare(const char *a, size_t al, const char *b, size_t bl) 124 | { 125 | return al == bl && !memcmp(a, b, al); 126 | } 127 | 128 | bool dynlease6_add(int ifindex, const struct in6_addr *v6_addr, 129 | const char *duid, size_t duid_len, uint32_t iaid, int64_t expire_time) 130 | { 131 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 132 | 133 | for (struct lease_state_v6 *p = dyn_leases_v6[ifindex]; p; p = p->next) { 134 | if (!memcmp(&p->addr, v6_addr, sizeof p->addr)) { 135 | if (!duid_compare(p->duid, p->duid_len, duid, duid_len) && p->iaid == iaid) { 136 | p->expire_time = expire_time; 137 | return true; 138 | } 139 | return false; 140 | } 141 | } 142 | struct lease_state_v6 *n = ls6_freelist; 143 | if (n && n->duid_len < duid_len) n = NULL; 144 | if (n) { 145 | ls6_freelist = n->next; 146 | } else { 147 | n = malloc(sizeof(struct lease_state_v6) + duid_len); 148 | if (!n) abort(); 149 | } 150 | n->next = dyn_leases_v6[ifindex]; 151 | n->addr = *v6_addr; 152 | n->duid_len = duid_len; 153 | n->expire_time = expire_time; 154 | n->iaid = iaid; 155 | memcpy(n->duid, duid, duid_len); 156 | dyn_leases_v6[ifindex] = n; 157 | ++n_leases_v6[ifindex]; 158 | return true; 159 | } 160 | 161 | struct in6_addr dynlease4_query_refresh(int ifindex, const uint8_t *macaddr, 162 | int64_t expire_time) 163 | { 164 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return in6addr_any; 165 | 166 | for (struct lease_state_v4 *p = dyn_leases_v4[ifindex]; p; p = p->next) { 167 | if (!memcmp(&p->macaddr, macaddr, 6)) { 168 | p->expire_time = expire_time; 169 | return p->addr; 170 | } 171 | } 172 | return in6addr_any; 173 | } 174 | 175 | struct in6_addr dynlease6_query_refresh(int ifindex, const char *duid, size_t duid_len, 176 | uint32_t iaid, int64_t expire_time) 177 | { 178 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return in6addr_any; 179 | 180 | for (struct lease_state_v6 *p = dyn_leases_v6[ifindex]; p; p = p->next) { 181 | if (!duid_compare(p->duid, p->duid_len, duid, duid_len) && p->iaid == iaid) { 182 | p->expire_time = expire_time; 183 | return p->addr; 184 | } 185 | } 186 | return in6addr_any; 187 | } 188 | 189 | bool dynlease4_exists(int ifindex, const struct in6_addr *v4_addr, const uint8_t *macaddr) 190 | { 191 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 192 | 193 | int64_t ts = get_current_ts(); 194 | for (struct lease_state_v4 *p = dyn_leases_v4[ifindex]; p; p = p->next) { 195 | if (!memcmp(&p->addr, v4_addr, sizeof p->addr) && !memcmp(&p->macaddr, macaddr, 6)) { 196 | return ts < p->expire_time; 197 | } 198 | } 199 | return false; 200 | } 201 | 202 | bool dynlease4_del(int ifindex, const struct in6_addr *v4_addr, const uint8_t *macaddr) 203 | { 204 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 205 | 206 | struct lease_state_v4 **prev = &dyn_leases_v4[ifindex]; 207 | for (struct lease_state_v4 *p = dyn_leases_v4[ifindex]; p; prev = &p->next, p = p->next) { 208 | if (!memcmp(&p->addr, v4_addr, sizeof p->addr) && !memcmp(&p->macaddr, macaddr, 6)) { 209 | *prev = p->next; 210 | p->next = ls4_freelist; 211 | ls4_freelist = p->next; 212 | return true; 213 | } 214 | } 215 | return false; 216 | } 217 | 218 | bool dynlease6_del(int ifindex, const struct in6_addr *v6_addr, 219 | const char *duid, size_t duid_len, uint32_t iaid) 220 | { 221 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return false; 222 | 223 | struct lease_state_v6 **prev = &dyn_leases_v6[ifindex]; 224 | for (struct lease_state_v6 *p = dyn_leases_v6[ifindex]; p; prev = &p->next, p = p->next) { 225 | if (!memcmp(&p->addr, v6_addr, sizeof p->addr) 226 | && !duid_compare(p->duid, p->duid_len, duid, duid_len) && p->iaid == iaid) { 227 | *prev = p->next; 228 | p->next = ls6_freelist; 229 | ls6_freelist = p->next; 230 | assert(n_leases_v6[ifindex] > 0); 231 | --n_leases_v6[ifindex]; 232 | return true; 233 | } 234 | } 235 | return false; 236 | } 237 | 238 | // v4 239 | // v6 240 | 241 | bool dynlease_serialize(const char *path) 242 | { 243 | bool ret = false; 244 | size_t pathlen = strlen(path); 245 | int fd = -1; 246 | char tmp_path[PATH_MAX]; 247 | if (pathlen + 5 > sizeof tmp_path) abort(); 248 | memcpy(tmp_path, path, pathlen); 249 | memcpy(tmp_path + pathlen, ".tmp", 5); 250 | 251 | FILE *f = fopen(tmp_path, "w"); 252 | if (!f) { 253 | log_line("%s: failed to open '%s' for dynamic lease serialization\n", 254 | __func__, path); 255 | goto out0; 256 | } 257 | int64_t ts = get_current_ts(); 258 | for (size_t c = 0; c < MAX_NL_INTERFACES; ++c) { 259 | if (!dyn_leases_v4[c]) continue; 260 | const struct netif_info *nlinfo = NLSocket_get_ifinfo(&nl_socket, c); 261 | if (!nlinfo) continue; 262 | const char *iface = nlinfo->name; 263 | for (struct lease_state_v4 *p = dyn_leases_v4[c]; p; p = p->next) { 264 | // Don't write out dynamic leases that have expired. 265 | if (ts >= p->expire_time) 266 | continue; 267 | char abuf[48]; 268 | if (!ipaddr_to_string(abuf, sizeof abuf, &p->addr)) goto out1; 269 | if (fprintf(f, "v4 %s %s %.2hhx:%.2hhx:%.2hhx:%.2hhx:%.2hhx:%.2hhx %zu\n", 270 | iface, abuf, 271 | p->macaddr[0], p->macaddr[1], p->macaddr[2], 272 | p->macaddr[3], p->macaddr[4], p->macaddr[5], p->expire_time) < 0) { 273 | log_line("%s: fprintf failed: %s\n", __func__, strerror(errno)); 274 | goto out1; 275 | } 276 | } 277 | } 278 | for (size_t c = 0; c < MAX_NL_INTERFACES; ++c) { 279 | if (!dyn_leases_v6[c]) continue; 280 | const struct netif_info *nlinfo = NLSocket_get_ifinfo(&nl_socket, c); 281 | if (!nlinfo) continue; 282 | const char *iface = nlinfo->name; 283 | for (struct lease_state_v6 *p = dyn_leases_v6[c]; p; p = p->next) { 284 | // Don't write out dynamic leases that have expired. 285 | if (ts >= p->expire_time) continue; 286 | // A valid DUID is required. 287 | if (p->duid_len == 0) continue; 288 | 289 | char abuf[48]; 290 | if (!ipaddr_to_string(abuf, sizeof abuf, &p->addr)) goto out1; 291 | if (fprintf(f, "v6 %s %s ", iface, abuf) < 0) goto err0; 292 | for (size_t k = 0; k < p->duid_len; ++k) { 293 | if (fprintf(f, "%.2hhx", p->duid[k]) < 0) goto err0; 294 | } 295 | if (fprintf(f, " %u %lu\n", p->iaid, p->expire_time) < 0) goto err0; 296 | continue; 297 | err0: 298 | log_line("%s: fprintf failed: %s\n", __func__, strerror(errno)); 299 | goto out1; 300 | } 301 | } 302 | if (fflush(f)) { 303 | log_line("%s: fflush failed: %s\n", __func__, strerror(errno)); 304 | goto out1; 305 | } 306 | fd = fileno(f); 307 | if (fdatasync(fd)) { 308 | log_line("%s: fdatasync failed: %s\n", __func__, strerror(errno)); 309 | goto out1; 310 | } 311 | if (rename(tmp_path, path)) { 312 | log_line("%s: rename failed: %s\n", __func__, strerror(errno)); 313 | goto out1; 314 | } 315 | ret = true; 316 | out1: 317 | fclose(f); 318 | unlink(tmp_path); 319 | out0: 320 | return ret; 321 | } 322 | 323 | // v4 324 | // v6 325 | 326 | struct dynlease_parse_state { 327 | const char *st; 328 | int cs; 329 | 330 | int64_t expire_time; 331 | size_t duid_len; 332 | int ifindex; 333 | uint32_t iaid; 334 | bool parse_error; 335 | char duid[MAX_DUID]; 336 | char interface[IFNAMSIZ]; 337 | char v6_addr[48]; 338 | char v4_addr[16]; 339 | uint8_t macaddr[6]; 340 | }; 341 | 342 | static void newline(struct dynlease_parse_state *self) { 343 | *self = (struct dynlease_parse_state){ 344 | .st = self->st, 345 | .cs = self->cs, 346 | .ifindex = -1, 347 | }; 348 | } 349 | 350 | #include "parsehelp.h" 351 | 352 | %%{ 353 | machine dynlease_line_m; 354 | access cps->; 355 | 356 | action St { cps->st = p; } 357 | 358 | action InterfaceEn { 359 | assign_strbuf(cps->interface, NULL, sizeof cps->interface, cps->st, p); 360 | const struct netif_info *nlinfo = NLSocket_get_ifinfo_by_name(&nl_socket, cps->interface); 361 | cps->ifindex = nlinfo ? nlinfo->index : -1; 362 | } 363 | action DuidEn { 364 | assign_strbuf(cps->duid, &cps->duid_len, sizeof cps->duid, cps->st, p); 365 | lc_string_inplace(cps->duid, cps->duid_len); 366 | } 367 | action IaidEn { 368 | char buf[64]; 369 | ptrdiff_t blen = p - cps->st; 370 | if (blen < 0 || blen >= (int)sizeof buf) { 371 | cps->parse_error = true; 372 | fbreak; 373 | } 374 | memcpy(buf, cps->st, (size_t)blen); buf[blen] = 0; 375 | if (sscanf(buf, "%" SCNu32, &cps->iaid) != 1) { 376 | cps->parse_error = true; 377 | fbreak; 378 | } 379 | } 380 | action MacAddrEn { 381 | char buf[32]; 382 | ptrdiff_t blen = p - cps->st; 383 | if (blen < 0 || blen >= (int)sizeof buf) { 384 | cps->parse_error = true; 385 | fbreak; 386 | } 387 | *((char *)mempcpy(buf, cps->st, (size_t)blen)) = 0; 388 | if (sscanf(buf, "%2hhx:%2hhx:%2hhx:%2hhx:%2hhx:%2hhx", 389 | &cps->macaddr[0], &cps->macaddr[1], &cps->macaddr[2], 390 | &cps->macaddr[3], &cps->macaddr[4], &cps->macaddr[5]) != 6) { 391 | cps->parse_error = true; 392 | fbreak; 393 | } 394 | } 395 | action V4AddrEn { 396 | size_t l; 397 | assign_strbuf(cps->v4_addr, &l, sizeof cps->v4_addr, cps->st, p); 398 | lc_string_inplace(cps->v4_addr, l); 399 | } 400 | action V6AddrEn { 401 | size_t l; 402 | assign_strbuf(cps->v6_addr, &l, sizeof cps->v6_addr, cps->st, p); 403 | lc_string_inplace(cps->v6_addr, l); 404 | } 405 | action ExpireTimeEn { 406 | char buf[64]; 407 | ptrdiff_t blen = p - cps->st; 408 | if (blen < 0 || blen >= (int)sizeof buf) { 409 | cps->parse_error = true; 410 | fbreak; 411 | } 412 | memcpy(buf, cps->st, (size_t)blen); buf[blen] = 0; 413 | if (sscanf(buf, "%" SCNi64, &cps->expire_time) != 1) { 414 | cps->parse_error = true; 415 | fbreak; 416 | } 417 | } 418 | 419 | action V4EntryEn { 420 | struct in6_addr ipa; 421 | if (!ipaddr_from_string(&ipa, cps->v4_addr)) { 422 | log_line("Bad IP address at line %zu: %s\n", linenum, cps->v4_addr); 423 | cps->parse_error = true; 424 | fbreak; 425 | } 426 | dynlease4_add(cps->ifindex, &ipa, cps->macaddr, cps->expire_time); 427 | } 428 | action V6EntryEn { 429 | struct in6_addr ipa; 430 | if (!ipaddr_from_string(&ipa, cps->v6_addr)) { 431 | log_line("Bad IP address at line %zu: %s\n", linenum, cps->v6_addr); 432 | cps->parse_error = true; 433 | fbreak; 434 | } 435 | dynlease6_add(cps->ifindex, &ipa, cps->duid, cps->duid_len, cps->iaid, cps->expire_time); 436 | } 437 | 438 | interface = alnum+ >St %InterfaceEn; 439 | duid = (xdigit+ | (xdigit{2} ('-' xdigit{2})*)+) >St %DuidEn; 440 | iaid = digit+ >St %IaidEn; 441 | macaddr = ((xdigit{2} ':'){5} xdigit{2}) >St %MacAddrEn; 442 | v4_addr = (digit{1,3} | '.')+ >St %V4AddrEn; 443 | v6_addr = (xdigit{1,4} | ':')+ >St %V6AddrEn; 444 | expire_time = digit+ >St %ExpireTimeEn; 445 | 446 | v4_entry = space* 'v4' space+ interface space+ v4_addr space+ macaddr space+ expire_time space*; 447 | v6_entry = space* 'v6' space+ interface space+ v6_addr space+ duid space+ iaid space+ expire_time space*; 448 | 449 | main := v4_entry %V4EntryEn | v6_entry %V6EntryEn; 450 | }%% 451 | 452 | #pragma GCC diagnostic push 453 | #pragma GCC diagnostic ignored "-Wunused-const-variable" 454 | %% write data; 455 | #pragma GCC diagnostic pop 456 | 457 | static int do_parse_dynlease_line(struct dynlease_parse_state *cps, const char *p, size_t plen, 458 | const size_t linenum) 459 | { 460 | const char *pe = p + plen; 461 | const char *eof = pe; 462 | 463 | %% write init; 464 | %% write exec; 465 | 466 | if (cps->parse_error) return -1; 467 | if (cps->cs >= dynlease_line_m_first_final) 468 | return 1; 469 | if (cps->cs == dynlease_line_m_error) 470 | return -1; 471 | return -2; 472 | } 473 | 474 | bool dynlease_deserialize(const char *path) 475 | { 476 | bool ret = false; 477 | size_t linenum = 0; 478 | struct dynlease_parse_state ps = { .st = NULL, .cs = 0, .parse_error = false }; 479 | char buf[MAX_LINE]; 480 | FILE *f = fopen(path, "r"); 481 | if (!f) { 482 | log_line("%s: failed to open '%s' for dynamic lease deserialization\n", 483 | __func__, path); 484 | goto out0; 485 | } 486 | for (size_t i = 0; i < MAX_NL_INTERFACES; ++i) { 487 | if (dyn_leases_v4[i]) abort(); 488 | if (dyn_leases_v6[i]) abort(); 489 | } 490 | while (!feof(f)) { 491 | if (!fgets(buf, sizeof buf, f)) { 492 | if (!feof(f)) { 493 | log_line("%s: io error fetching line of '%s'\n", __func__, path); 494 | goto out1; 495 | } 496 | break; 497 | } 498 | size_t llen = strlen(buf); 499 | if (llen == 0) 500 | continue; 501 | if (buf[llen-1] == '\n') 502 | buf[--llen] = 0; 503 | ++linenum; 504 | newline(&ps); 505 | int r = do_parse_dynlease_line(&ps, buf, llen, linenum); 506 | if (r < 0) { 507 | if (r == -2) 508 | log_line("%s: Incomplete dynlease at line %zu; ignoring\n", 509 | __func__, linenum); 510 | else 511 | log_line("%s: Malformed dynlease at line %zu; ignoring.\n", 512 | __func__, linenum); 513 | continue; 514 | } 515 | } 516 | ret = true; 517 | out1: 518 | fclose(f); 519 | out0: 520 | return ret; 521 | } 522 | 523 | -------------------------------------------------------------------------------- /get_current_ts.h: -------------------------------------------------------------------------------- 1 | #ifndef NDHS_GET_CURRENT_TS_H_ 2 | #define NDHS_GET_CURRENT_TS_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | static inline int64_t get_current_ts(void) 9 | { 10 | struct timespec ts; 11 | if (clock_gettime(CLOCK_BOOTTIME, &ts)) abort(); 12 | return ts.tv_sec; 13 | } 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /ipaddr.h: -------------------------------------------------------------------------------- 1 | #ifndef NKLIB_IPADDR_H_ 2 | #define NKLIB_IPADDR_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #ifndef _WIN32 10 | #include 11 | #include 12 | #else 13 | #include 14 | #include 15 | #endif 16 | 17 | // These are helpers to access ip addresses stored in in6_addr 18 | // in6_addr works fine for v4 addresses; they simply map onto the 19 | // IPv4-mapped range in IPv6. 20 | 21 | static inline bool ipaddr_from_string(struct in6_addr *addr, const char *s) 22 | { 23 | if (inet_pton(AF_INET6, s, addr) != 1) { 24 | // Automagically try to handle IPv4 addresses as IPV6-mapped. 25 | char buf[256] = "::ffff:"; 26 | size_t slen = strlen(s); 27 | if (slen > sizeof buf - 8) return false; 28 | memcpy(buf + 7, s, slen); 29 | buf[slen + 7] = 0; 30 | if (inet_pton(AF_INET6, buf, addr) != 1) 31 | return false; 32 | } 33 | return true; 34 | } 35 | 36 | static inline bool ipaddr_is_v4(const struct in6_addr *addr) 37 | { 38 | const char *p = (const char *)addr; 39 | for (size_t i = 0; i < 10; ++i) if (p[i]) return false; 40 | return (uint8_t)p[10] == 0xff && (uint8_t)p[11] == 0xff; 41 | } 42 | 43 | static inline bool ipaddr_to_string(char *buf, size_t buflen, const struct in6_addr *addr) 44 | { 45 | if (ipaddr_is_v4(addr)) { 46 | // So that we don't print v4 with the ::ffff: mapped prefix 47 | struct in_addr a4; 48 | const char *c = (const char *)addr; 49 | memcpy(&a4, c + 12, 4); 50 | if (!inet_ntop(AF_INET, &a4, buf, buflen)) return false; 51 | } else { 52 | if (!inet_ntop(AF_INET6, addr, buf, buflen)) return false; 53 | } 54 | return true; 55 | } 56 | 57 | // Check whether the address is v4 first via ipaddr_is_v4() 58 | static inline const char *ipaddr_v4_bytes(const struct in6_addr *addr) 59 | { 60 | return (const char *)addr + 12; 61 | } 62 | 63 | // For v6, simply memcpy() 64 | static inline void ipaddr_from_v4_bytes(struct in6_addr *addr, const void *inv) 65 | { 66 | char *caddr = (char *)addr; 67 | const char *in = (const char *)inv; 68 | for (size_t i = 0; i < 10; ++i) caddr[i] = 0; 69 | caddr[10] = (char)0xff; 70 | caddr[11] = (char)0xff; 71 | for (size_t i = 12; i < 16; ++i) caddr[i] = *in++; 72 | } 73 | 74 | static inline bool ipaddr_u32_compare_masked(uint32_t a, uint32_t b, unsigned mask) 75 | { 76 | assert(mask <= 32); 77 | mask = mask <= 32 ? mask : 32; 78 | a = htonl(a); 79 | b = htonl(b); 80 | uint32_t m = mask < 32 ? UINT32_MAX >> mask : 0; 81 | return (a | m) == (b | m); 82 | 83 | } 84 | 85 | static inline bool ipaddr_compare_masked(const struct in6_addr *a, const struct in6_addr *b, unsigned mask) 86 | { 87 | bool av4 = ipaddr_is_v4(a); 88 | bool bv4 = ipaddr_is_v4(b); 89 | if (av4 != bv4) return false; 90 | if (av4) { 91 | uint32_t a32, b32; 92 | memcpy(&a32, ipaddr_v4_bytes(a), sizeof a32); 93 | memcpy(&b32, ipaddr_v4_bytes(b), sizeof b32); 94 | mask = mask <= 32 ? mask : 32; 95 | return ipaddr_u32_compare_masked(a32, b32, mask); 96 | } else { 97 | uint32_t a32[4], b32[4]; 98 | memcpy(a32, a, sizeof a32); 99 | memcpy(b32, b, sizeof b32); 100 | mask = mask <= 128 ? mask : 128; 101 | for (size_t i = 0; i < 3; ++i) { 102 | bool ci = ipaddr_u32_compare_masked(a32[i], b32[i], mask); 103 | if (mask <= 32) return ci; 104 | mask -= 32; 105 | if (!ci) return false; 106 | } 107 | return ipaddr_u32_compare_masked(a32[3], b32[3], mask); 108 | } 109 | } 110 | 111 | #endif 112 | -------------------------------------------------------------------------------- /multicast6.c: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include "multicast6.h" 4 | #include "nlsocket.h" 5 | #include "nk/log.h" 6 | 7 | extern struct NLSocket nl_socket; 8 | 9 | bool attach_multicast_sockaddr_in6(int fd, const char *ifname, const struct sockaddr_in6 *mc6addr) 10 | { 11 | int ifidx = NLSocket_get_ifindex(&nl_socket, ifname); 12 | if (ifidx < 0) { 13 | log_line("Failed to get interface index for %s\n", ifname); 14 | return false; 15 | } 16 | struct ifreq ifr = {0}; 17 | memcpy(ifr.ifr_name, ifname, strlen(ifname)); 18 | if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof ifr) < 0) { 19 | log_line("failed to bind socket to device: %s\n", strerror(errno)); 20 | return false; 21 | } 22 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &ifidx, sizeof ifidx) < 0) { 23 | log_line("failed to set multicast interface for socket: %s\n", strerror(errno)); 24 | return false; 25 | } 26 | int loopback = 0; 27 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loopback, sizeof loopback) < 0) { 28 | log_line("failed to disable multicast loopback for socket: %s\n", strerror(errno)); 29 | return false; 30 | } 31 | int hops = 255; 32 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &hops, sizeof hops) < 0) { 33 | log_line("failed to disable multicast hops for socket: %s\n", strerror(errno)); 34 | return false; 35 | } 36 | struct ipv6_mreq mr = { 37 | .ipv6mr_multiaddr = mc6addr->sin6_addr, 38 | .ipv6mr_interface = (unsigned)ifidx, 39 | }; 40 | if (setsockopt(fd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, &mr, sizeof mr) < 0) { 41 | log_line("failed to join router multicast group for socket: %s\n", strerror(errno)); 42 | return false; 43 | } 44 | return true; 45 | } 46 | 47 | bool attach_multicast_in6_addr(int fd, const char *ifname, const struct in6_addr *mc6addr) 48 | { 49 | struct sockaddr_in6 sai = { 50 | .sin6_family = AF_INET6, 51 | .sin6_addr = *mc6addr, 52 | }; 53 | return attach_multicast_sockaddr_in6(fd, ifname, &sai); 54 | } 55 | -------------------------------------------------------------------------------- /multicast6.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_MULTICAST6_H_ 4 | #define NDHS_MULTICAST6_H_ 5 | 6 | #include 7 | #include 8 | 9 | bool attach_multicast_sockaddr_in6(int fd, const char *ifname, const struct sockaddr_in6 *mc6addr); 10 | bool attach_multicast_in6_addr(int fd, const char *ifname, const struct in6_addr *mc6addr); 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /ndhs.c: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #define NDHS_VERSION "3.0" 4 | #define LEASEFILE_PATH "/store/dynlease.txt" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include "nk/log.h" 26 | #include "nk/privs.h" 27 | #include "nk/io.h" 28 | #include "nk/random.h" 29 | #include "nlsocket.h" 30 | #include "dhcp6.h" 31 | #include "dhcp4.h" 32 | #include "radv6.h" 33 | #include "dhcp_state.h" 34 | #include "dynlease.h" 35 | #include "duid.h" 36 | 37 | enum pfd_type 38 | { 39 | PFD_TYPE_NETLINK, 40 | PFD_TYPE_DHCP4, 41 | PFD_TYPE_DHCP6, 42 | PFD_TYPE_RADV6, 43 | }; 44 | 45 | struct pfd_meta 46 | { 47 | enum pfd_type pfdt; 48 | // These are owning pointers. 49 | union { 50 | struct D4Listener *ld4; 51 | struct D6Listener *ld6; 52 | struct RA6Listener *lr6; 53 | }; 54 | }; 55 | 56 | struct nk_random_state g_rngstate; 57 | 58 | static const char *configfile = "/etc/ndhs.conf"; 59 | static char *chroot_path; 60 | static uid_t ndhs_uid; 61 | static gid_t ndhs_gid; 62 | static int s6_notify_fd = -1; 63 | 64 | struct NLSocket nl_socket; 65 | 66 | static struct pollfd *poll_array; 67 | static struct pfd_meta *poll_meta; 68 | static size_t poll_size = 1; 69 | 70 | extern bool parse_config(const char *path); 71 | 72 | void set_user_runas(const char *username, size_t len); 73 | void set_chroot_path(const char *path, size_t len); 74 | void set_s6_notify_fd(int fd); 75 | 76 | static void count_bound_listeners(const struct netif_info *, bool use_v4, bool use_v6, uint8_t, void *) 77 | { 78 | if (use_v4) poll_size += 1; 79 | if (use_v6) poll_size += 2; 80 | } 81 | 82 | static void get_interface_addresses(const struct netif_info *ifinfo, bool, bool, uint8_t, void *) 83 | { 84 | if (!NLSocket_get_interface_addresses(&nl_socket, ifinfo->index)) { 85 | // Indicates that the kernel changed the list of interfaces 86 | // between bound_interfaces_names() and now. 87 | suicide("netlink: Interface %s does not exist! Restarting.\n", ifinfo->name); 88 | } 89 | } 90 | 91 | static void create_interface_listener(const struct netif_info *ifinfo, 92 | bool use_v4, bool use_v6, 93 | uint8_t preference, void *ud) 94 | { 95 | size_t *pfdc = ud; 96 | if (use_v6) { 97 | struct D6Listener *d6l = D6Listener_create(ifinfo->name, ifinfo, preference); 98 | if (d6l) { 99 | struct RA6Listener *r6l = RA6Listener_create(ifinfo->name, ifinfo); 100 | if (r6l) { 101 | poll_array[*pfdc] = (struct pollfd){ .fd = D6Listener_fd(d6l), .events = POLLIN|POLLHUP|POLLERR }; 102 | poll_meta[(*pfdc)++] = (struct pfd_meta){ .pfdt = PFD_TYPE_DHCP6, .ld6 = d6l }; 103 | poll_array[*pfdc] = (struct pollfd){ .fd = RA6Listener_fd(r6l), .events = POLLIN|POLLHUP|POLLERR }; 104 | poll_meta[(*pfdc)++] = (struct pfd_meta){ .pfdt = PFD_TYPE_RADV6, .lr6 = r6l }; 105 | } else { 106 | log_line("Can't bind to rav6 interface: %s\n", ifinfo->name); 107 | D6Listener_destroy(d6l); 108 | } 109 | } else { 110 | log_line("Can't bind to dhcpv6 interface: %s\n", ifinfo->name); 111 | } 112 | } 113 | if (use_v4) { 114 | struct D4Listener *d4l = D4Listener_create(ifinfo->name, ifinfo); 115 | if (d4l) { 116 | poll_array[*pfdc] = (struct pollfd){ .fd = D4Listener_fd(d4l), .events = POLLIN|POLLHUP|POLLERR }; 117 | poll_meta[(*pfdc)++] = (struct pfd_meta){ .pfdt = PFD_TYPE_DHCP4, .ld4 = d4l }; 118 | } else { 119 | log_line("Can't bind to dhcpv4 interface: %s\n", ifinfo->name); 120 | } 121 | } 122 | } 123 | 124 | static void init_listeners(void) 125 | { 126 | bound_interfaces_foreach(count_bound_listeners, NULL); 127 | poll_array = malloc(poll_size * sizeof *poll_array); 128 | poll_meta = malloc(poll_size * sizeof *poll_meta); 129 | if (!poll_array || !poll_meta) abort(); 130 | 131 | bound_interfaces_foreach(get_interface_addresses, NULL); 132 | 133 | poll_array[0] = (struct pollfd){ .fd = nl_socket.fd, .events = POLLIN|POLLHUP|POLLERR }; 134 | poll_meta[0] = (struct pfd_meta){ .pfdt = PFD_TYPE_NETLINK }; 135 | 136 | size_t pfdc = 1; 137 | bound_interfaces_foreach(create_interface_listener, &pfdc); 138 | assert(pfdc <= poll_size); 139 | poll_size = pfdc; 140 | } 141 | 142 | void set_user_runas(const char *username, size_t len) 143 | { 144 | char buf[256]; 145 | if (len >= sizeof buf) 146 | suicide("user %.*s is too long: %zu\n", (int)len, username, len); 147 | memcpy(buf, username, len); 148 | buf[len] = 0; 149 | if (nk_uidgidbyname(buf, &ndhs_uid, &ndhs_gid)) 150 | suicide("invalid user '%s' specified\n", buf); 151 | } 152 | 153 | void set_chroot_path(const char *path, size_t len) 154 | { 155 | chroot_path = strndup(path, len); 156 | } 157 | 158 | void set_s6_notify_fd(int fd) 159 | { 160 | s6_notify_fd = fd; 161 | } 162 | 163 | static volatile sig_atomic_t l_signal_exit; 164 | static void signal_handler(int signo) 165 | { 166 | int serrno = errno; 167 | if (signo == SIGINT || signo == SIGTERM) { 168 | l_signal_exit = 1; 169 | } 170 | errno = serrno; 171 | } 172 | 173 | static void setup_signals_ndhs(void) 174 | { 175 | static const int ss[] = { SIGINT, SIGTERM, SIGKILL }; 176 | sigset_t mask; 177 | if (sigprocmask(0, 0, &mask) < 0) 178 | suicide("sigprocmask failed\n"); 179 | for (int i = 0; ss[i] != SIGKILL; ++i) 180 | if (sigdelset(&mask, ss[i])) 181 | suicide("sigdelset failed\n"); 182 | if (sigaddset(&mask, SIGPIPE)) 183 | suicide("sigaddset failed\n"); 184 | if (sigprocmask(SIG_SETMASK, &mask, NULL) < 0) 185 | suicide("sigprocmask failed\n"); 186 | 187 | struct sigaction sa = { .sa_handler = signal_handler, .sa_flags = SA_RESTART }; 188 | if (sigemptyset(&sa.sa_mask)) 189 | suicide("sigemptyset failed\n"); 190 | for (int i = 0; ss[i] != SIGKILL; ++i) 191 | if (sigaction(ss[i], &sa, NULL)) 192 | suicide("sigaction failed\n"); 193 | sa.sa_handler = SIG_IGN; 194 | sa.sa_flags = SA_NOCLDWAIT; 195 | if (sigaction(SIGCHLD, &sa, NULL)) 196 | suicide("sigaction failed\n"); 197 | } 198 | 199 | static void usage(void) 200 | { 201 | printf("ndhs " NDHS_VERSION ", DHCPv4/DHCPv6 and IPv6 Router Advertisement server.\n"); 202 | printf("Copyright 2014-2024 Nicholas J. Kain\n"); 203 | printf("ndhs [options] [configfile]...\n\nOptions:\n"); 204 | printf("--config -c [] Path to configuration file.\n"); 205 | printf("--version -v Print version and exit.\n"); 206 | printf("--help -h Print this help and exit.\n"); 207 | } 208 | 209 | static void print_version(void) 210 | { 211 | log_line("ndhs " NDHS_VERSION ", ipv6 router advertisment and dhcp server.\n" 212 | "Copyright 2014-2024 Nicholas J. Kain\n\n" 213 | "Permission is hereby granted, free of charge, to any person obtaining\n" 214 | "a copy of this software and associated documentation files (the\n" 215 | "\"Software\"), to deal in the Software without restriction, including\n" 216 | "without limitation the rights to use, copy, modify, merge, publish,\n" 217 | "distribute, sublicense, and/or sell copies of the Software, and to\n" 218 | "permit persons to whom the Software is furnished to do so, subject to\n" 219 | "the following conditions:\n\n" 220 | "The above copyright notice and this permission notice shall be\n" 221 | "included in all copies or substantial portions of the Software.\n\n" 222 | "THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND,\n" 223 | "EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\n" 224 | "MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND\n" 225 | "NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE\n" 226 | "LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION\n" 227 | "OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION\n" 228 | "WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n" 229 | ); 230 | } 231 | 232 | static void process_options(int ac, char *av[]) 233 | { 234 | static const struct option long_options[] = { 235 | {"config", 1, NULL, 'c'}, 236 | {"version", 0, NULL, 'v'}, 237 | {"help", 0, NULL, 'h'}, 238 | {NULL, 0, NULL, 0 } 239 | }; 240 | for (;;) { 241 | int c = getopt_long(ac, av, "c:vh", long_options, NULL); 242 | if (c == -1) break; 243 | switch (c) { 244 | case 'c': configfile = strdup(optarg); break; 245 | case 'v': print_version(); exit(EXIT_SUCCESS); break; 246 | case 'h': usage(); exit(EXIT_SUCCESS); break; 247 | default: break; 248 | } 249 | } 250 | 251 | nk_random_init(&g_rngstate); 252 | NLSocket_init(&nl_socket); 253 | 254 | if (!parse_config(configfile)) 255 | suicide("Failed to load configuration file.\n"); 256 | 257 | if (!bound_interfaces_count()) 258 | suicide("No interfaces have been bound\n"); 259 | if (!ndhs_uid || !ndhs_gid) 260 | suicide("No non-root user account is specified.\n"); 261 | if (!chroot_path) 262 | suicide("No chroot path is specified.\n"); 263 | 264 | init_listeners(); 265 | 266 | umask(077); 267 | setup_signals_ndhs(); 268 | 269 | nk_set_chroot(chroot_path); 270 | duid_load_from_file(); 271 | dynlease_deserialize(LEASEFILE_PATH); 272 | nk_set_uidgid(ndhs_uid, ndhs_gid, NULL, 0); 273 | 274 | if (s6_notify_fd >= 0) { 275 | const char buf[] = "\n"; 276 | safe_write(s6_notify_fd, buf, 1); 277 | close(s6_notify_fd); 278 | } 279 | } 280 | 281 | int main(int ac, char *av[]) 282 | { 283 | process_options(ac, av); 284 | 285 | for (;;) { 286 | int timeout = INT_MAX; 287 | for (size_t i = 0, iend = poll_size; i < iend; ++i) { 288 | if (poll_array[i].revents & (POLLHUP|POLLERR)) 289 | suicide("fd closed unexpectedly\n"); 290 | switch (poll_meta[i].pfdt) { 291 | case PFD_TYPE_NETLINK: 292 | if (poll_array[i].revents & POLLIN) NLSocket_process_input(&nl_socket); 293 | break; 294 | case PFD_TYPE_DHCP4: 295 | if (poll_array[i].revents & POLLIN) D4Listener_process_input(poll_meta[i].ld4); 296 | break; 297 | case PFD_TYPE_DHCP6: 298 | if (poll_array[i].revents & POLLIN) D6Listener_process_input(poll_meta[i].ld6); 299 | break; 300 | case PFD_TYPE_RADV6: { 301 | if (poll_array[i].revents & POLLIN) RA6Listener_process_input(poll_meta[i].lr6); 302 | int t = RA6Listener_send_periodic_advert(poll_meta[i].lr6); 303 | timeout = timeout < t ? timeout : t; 304 | break; 305 | } 306 | } 307 | } 308 | dynlease_gc(); 309 | if (poll(poll_array, poll_size, timeout > 0 ? timeout : 0) < 0) { 310 | if (errno != EINTR) suicide("poll failed\n"); 311 | } 312 | if (l_signal_exit) break; 313 | } 314 | 315 | dynlease_serialize(LEASEFILE_PATH); 316 | 317 | return 0; 318 | } 319 | 320 | -------------------------------------------------------------------------------- /nk/hwrng.c: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "nk/hwrng.h" 14 | #include "nk/log.h" 15 | #include "nk/io.h" 16 | 17 | static bool nk_getrandom(char *seed, size_t len) 18 | { 19 | size_t fetched = 0; 20 | while (fetched < len) { 21 | size_t sz = len - fetched; 22 | if (sz > 256) sz = 256; 23 | int r = getentropy(seed + fetched, sz); 24 | if (r < 0) { 25 | log_line("%s: getrandom() failed: %s\n", __func__, strerror(errno)); 26 | return false; 27 | } 28 | fetched += sz; 29 | } 30 | return true; 31 | } 32 | 33 | static bool nk_get_rnd_clk(char *seed, size_t len) 34 | { 35 | struct timespec ts; 36 | for (size_t i = 0; i < len; ++i) { 37 | int r = clock_gettime(CLOCK_REALTIME, &ts); 38 | if (r < 0) { 39 | log_line("%s: Could not call clock_gettime(CLOCK_REALTIME): %s\n", 40 | __func__, strerror(errno)); 41 | return false; 42 | } 43 | char *p = (char *)&ts.tv_sec; 44 | char *q = (char *)&ts.tv_nsec; 45 | for (size_t j = 0; j < sizeof ts.tv_sec; ++j) 46 | seed[i] ^= p[j]; 47 | for (size_t j = 0; j < sizeof ts.tv_nsec; ++j) 48 | seed[i] ^= q[j]; 49 | // Force some scheduler jitter. 50 | static const struct timespec st = { .tv_sec=0, .tv_nsec=1 }; 51 | nanosleep(&st, (struct timespec *)0); 52 | } 53 | return true; 54 | } 55 | 56 | static bool nk_get_urandom(char *seed, size_t len) 57 | { 58 | int fd = open("/dev/urandom", O_RDONLY|O_CLOEXEC); 59 | if (fd < 0) { 60 | log_line("%s: Could not open /dev/urandom: %s\n", __func__, 61 | strerror(errno)); 62 | return false; 63 | } 64 | bool ret = true; 65 | int r = safe_read(fd, seed, len); 66 | if (r < 0) { 67 | ret = false; 68 | log_line("%s: Could not read /dev/urandom: %s\n", 69 | __func__, strerror(errno)); 70 | } 71 | close(fd); 72 | return ret; 73 | } 74 | 75 | void nk_hwrng_bytes(void *seed, size_t len) 76 | { 77 | char *s = (char *)seed; 78 | if (nk_getrandom(s, len)) 79 | return; 80 | if (nk_get_urandom(s, len)) 81 | return; 82 | log_line("%s: Seeding PRNG via system clock. May be predictable.\n", 83 | __func__); 84 | if (nk_get_rnd_clk(s, len)) 85 | return; 86 | suicide("%s: All methods to seed PRNG failed. Exiting.\n", __func__); 87 | } 88 | 89 | -------------------------------------------------------------------------------- /nk/hwrng.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2022 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NCMLIB_HWCRNG__ 4 | #define NCMLIB_HWCRNG__ 5 | 6 | #include 7 | void nk_hwrng_bytes(void *seed, size_t len); 8 | 9 | #endif 10 | 11 | -------------------------------------------------------------------------------- /nk/io.c: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2022 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include "nk/io.h" 4 | 5 | // POSIX says read/write/etc() with len param > SSIZE_MAX is implementation defined. 6 | // So we avoid implementation-defined behavior with the bounding in each safe_* fn. 7 | 8 | /* returns -1 on error, >= 0 and equal to # chars read on success */ 9 | ssize_t safe_read(int fd, char *buf, size_t len) 10 | { 11 | size_t s = 0; 12 | if (len > SSIZE_MAX) len = SSIZE_MAX; 13 | while (s < len) { 14 | ssize_t r = read(fd, buf + s, len - s); 15 | if (r == 0) 16 | break; 17 | if (r < 0) { 18 | if (errno == EINTR) 19 | continue; 20 | else if ((errno == EAGAIN || errno == EWOULDBLOCK) && s > 0) 21 | return (ssize_t)s; 22 | else 23 | return -1; 24 | } 25 | s += (size_t)r; 26 | } 27 | return (ssize_t)s; 28 | } 29 | 30 | /* returns -1 on error, >= 0 and equal to # chars written on success */ 31 | ssize_t safe_write(int fd, const char *buf, size_t len) 32 | { 33 | size_t s = 0; 34 | if (len > SSIZE_MAX) len = SSIZE_MAX; 35 | while (s < len) { 36 | ssize_t r = write(fd, buf + s, len - s); 37 | if (r < 0) { 38 | if (errno == EINTR) 39 | continue; 40 | else if ((errno == EAGAIN || errno == EWOULDBLOCK) && s > 0) 41 | return (ssize_t)s; 42 | else 43 | return -1; 44 | } 45 | s += (size_t)r; 46 | } 47 | return (ssize_t)s; 48 | } 49 | 50 | /* returns -1 on error, >= 0 and equal to # chars written on success */ 51 | ssize_t safe_sendto(int fd, const char *buf, size_t len, int flags, 52 | const struct sockaddr *dest_addr, socklen_t addrlen) 53 | { 54 | size_t s = 0; 55 | if (len > SSIZE_MAX) len = SSIZE_MAX; 56 | while (s < len) { 57 | ssize_t r = sendto(fd, buf + s, len - s, flags, dest_addr, addrlen); 58 | if (r < 0) { 59 | if (errno == EINTR) 60 | continue; 61 | else if ((errno == EAGAIN || errno == EWOULDBLOCK) && s > 0) 62 | return (ssize_t)s; 63 | else 64 | return -1; 65 | } 66 | s += (size_t)r; 67 | } 68 | return (ssize_t)s; 69 | } 70 | 71 | ssize_t safe_recv(int fd, char *buf, size_t len, int flags) 72 | { 73 | size_t s = 0; 74 | if (len > SSIZE_MAX) len = SSIZE_MAX; 75 | while (s < len) { 76 | ssize_t r = recv(fd, buf + s, len - s, flags); 77 | if (r == 0) 78 | break; 79 | if (r < 0) { 80 | if (errno == EINTR) 81 | continue; 82 | else if ((errno == EAGAIN || errno == EWOULDBLOCK) && s > 0) 83 | return (ssize_t)s; 84 | else 85 | return -1; 86 | } 87 | s += (size_t)r; 88 | } 89 | return (ssize_t)s; 90 | } 91 | -------------------------------------------------------------------------------- /nk/io.h: -------------------------------------------------------------------------------- 1 | // Copyright 2010-2022 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NCM_IO_H_ 4 | #define NCM_IO_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | ssize_t safe_read(int fd, char *buf, size_t len); 13 | // Same as above, but will only call read one time. 14 | // Meant to be used with a blocking fd where we need <= len bytes. 15 | static inline ssize_t safe_read_once(int fd, char *buf, size_t len) 16 | { 17 | if (len > SSIZE_MAX) len = SSIZE_MAX; 18 | ssize_t r; 19 | for (;;) { 20 | r = read(fd, buf, len); 21 | if (r >= 0 || errno != EINTR) break; 22 | } 23 | return r; 24 | } 25 | 26 | ssize_t safe_write(int fd, const char *buf, size_t len); 27 | ssize_t safe_sendto(int fd, const char *buf, size_t len, int flags, 28 | const struct sockaddr *dest_addr, socklen_t addrlen); 29 | 30 | ssize_t safe_recv(int fd, char *buf, size_t len, int flags); 31 | // Same as above, but will only call read one time. 32 | // Meant to be used with a blocking fd where we need <= len bytes. 33 | static inline ssize_t safe_recv_once(int fd, char *buf, size_t len, int flags) 34 | { 35 | if (len > SSIZE_MAX) len = SSIZE_MAX; 36 | ssize_t r; 37 | for (;;) { 38 | r = recv(fd, buf, len, flags); 39 | if (r >= 0 || errno != EINTR) break; 40 | } 41 | return r; 42 | } 43 | 44 | static inline ssize_t safe_recvmsg(int fd, struct msghdr *msg, int flags) 45 | { 46 | ssize_t r; 47 | for (;;) { 48 | r = recvmsg(fd, msg, flags); 49 | if (r >= 0 || errno != EINTR) break; 50 | } 51 | return r; 52 | } 53 | static inline int safe_ftruncate(int fd, off_t length) 54 | { 55 | int r; 56 | for (;;) { 57 | r = ftruncate(fd, length); 58 | if (!r || errno != EINTR) break; 59 | } 60 | return r; 61 | } 62 | 63 | #endif 64 | 65 | -------------------------------------------------------------------------------- /nk/log.h: -------------------------------------------------------------------------------- 1 | // Copyright 2003-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NCM_LOG_H_ 4 | #define NCM_LOG_H_ 5 | 6 | #include 7 | #include 8 | 9 | #define log_line(...) do { \ 10 | dprintf(2, __VA_ARGS__); \ 11 | } while (0) 12 | 13 | #ifndef NDEBUG 14 | #define log_debug(...) log_line(__VA_ARGS__) 15 | #else 16 | #define log_debug(...) do {} while(0) 17 | #endif 18 | 19 | #define suicide(...) do { \ 20 | dprintf(2, __VA_ARGS__); \ 21 | exit(EXIT_FAILURE); } while (0) 22 | 23 | #endif 24 | 25 | -------------------------------------------------------------------------------- /nk/net_checksum16.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NCMLIB_NET_CHECKSUM16_H 4 | #define NCMLIB_NET_CHECKSUM16_H 5 | 6 | // RFC 1071 is still a good reference. 7 | 8 | #include 9 | 10 | // When summing ones-complement 16-bit values using a 32-bit unsigned 11 | // representation, fold the carry bits that have spilled into the upper 12 | // 16-bits of the 32-bit unsigned value back into the 16-bit ones-complement 13 | // binary value. 14 | static inline uint16_t net_checksum16_foldcarry(uint32_t v) 15 | { 16 | v = (v >> 16) + (v & 0xffff); 17 | v += v >> 16; 18 | return v; 19 | } 20 | 21 | // Produces the correct result on little endian in the sense that 22 | // the binary value returned, when stored to memory, will match 23 | // the result on big endian; if the numeric value returned 24 | // must match big endian results, then call ntohs() on the result. 25 | static uint16_t net_checksum16(const void *buf, size_t size) 26 | { 27 | const char *b = (const char *)buf; 28 | const char *bend = b + size; 29 | uint32_t sum = 0, t = 0; 30 | uint8_t z[4] = { 0 }; 31 | switch (size & 3) { 32 | case 3: z[2] = (uint8_t)*--bend; 33 | case 2: z[1] = (uint8_t)*--bend; 34 | case 1: z[0] = (uint8_t)*--bend; 35 | default: break; 36 | } 37 | memcpy(&t, z, 4); 38 | sum += t & 0xffffu; 39 | sum += (t >> 16); 40 | for (; b < bend; b += 4) { 41 | memcpy(&t, b, 4); 42 | sum += t & 0xffffu; 43 | sum += (t >> 16); 44 | } 45 | return ~net_checksum16_foldcarry(sum); 46 | } 47 | 48 | // For two sequences of bytes A and B that return checksums CS(A) and CS(B), 49 | // this function will calculate the checksum CS(AB) of the concatenated value 50 | // AB given the checksums of the individual parts CS(A) and CS(B). 51 | static inline uint16_t net_checksum16_add(uint16_t a, uint16_t b) 52 | { 53 | const uint32_t A = a; 54 | const uint32_t B = b; 55 | return ~net_checksum16_foldcarry((~A & 0xffffu) + (~B & 0xffffu)); 56 | } 57 | 58 | #endif 59 | 60 | -------------------------------------------------------------------------------- /nk/netbits.h: -------------------------------------------------------------------------------- 1 | #ifndef NDHS_NETBITS_H_ 2 | #define NDHS_NETBITS_H_ 3 | 4 | #include 5 | 6 | static inline void encode32be(void *dest, uint32_t v) 7 | { 8 | char *d = dest; 9 | d[0] = v >> 24; 10 | d[1] = (v >> 16) & 0xff; 11 | d[2] = (v >> 8) & 0xff; 12 | d[3] = v & 0xff; 13 | } 14 | 15 | static inline void encode16be(void *dest, uint16_t v) 16 | { 17 | char *d = dest; 18 | d[0] = v >> 8; 19 | d[1] = v & 0xff; 20 | } 21 | 22 | static inline uint32_t decode32be(const void *src) 23 | { 24 | const char *s = src; 25 | return ((uint32_t)s[0] << 24) 26 | | (((uint32_t)s[1] << 16) & 0xff0000) 27 | | (((uint32_t)s[2] << 8) & 0xff00) 28 | | ((uint32_t)s[3] & 0xff); 29 | } 30 | 31 | static inline uint16_t decode16be(const void *src) 32 | { 33 | const char *s = src; 34 | return ((uint16_t)s[0] << 8) 35 | | ((uint16_t)s[1] & 0xff); 36 | } 37 | 38 | #endif 39 | 40 | -------------------------------------------------------------------------------- /nk/privs.c: -------------------------------------------------------------------------------- 1 | // Copyright 2005-2022 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef _GNU_SOURCE 4 | #define _GNU_SOURCE 5 | #endif 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #ifdef __linux__ 17 | #include 18 | #include 19 | #endif 20 | #include "nk/privs.h" 21 | #include "nk/log.h" 22 | 23 | static inline bool nk_isdigit(int c) { return c >= '0' && c <= '9'; } 24 | 25 | void nk_set_chroot(const char *chroot_dir) 26 | { 27 | if (chroot(chroot_dir)) 28 | suicide("%s: chroot('%s') failed: %s\n", __func__, chroot_dir, 29 | strerror(errno)); 30 | if (chdir("/")) 31 | suicide("%s: chdir('/') failed: %s\n", __func__, strerror(errno)); 32 | } 33 | 34 | #ifdef __linux__ 35 | static size_t nk_get_capability_vinfo(uint32_t *version) 36 | { 37 | struct __user_cap_header_struct hdr = {0}; 38 | if (capget(&hdr, (cap_user_data_t)0) < 0) { 39 | if (errno != EINVAL) 40 | suicide("%s: capget failed: %s\n", __func__, strerror(errno)); 41 | } 42 | switch (hdr.version) { 43 | case _LINUX_CAPABILITY_VERSION_1: 44 | *version = _LINUX_CAPABILITY_VERSION_1; 45 | return _LINUX_CAPABILITY_U32S_1; 46 | case _LINUX_CAPABILITY_VERSION_2: 47 | *version = _LINUX_CAPABILITY_VERSION_2; 48 | return _LINUX_CAPABILITY_U32S_2; 49 | default: log_line("%s: unknown capability version %x, using %x\n", 50 | __func__, *version, _LINUX_CAPABILITY_VERSION_3); 51 | // fall through 52 | case _LINUX_CAPABILITY_VERSION_3: 53 | *version = _LINUX_CAPABILITY_VERSION_3; 54 | return _LINUX_CAPABILITY_U32S_3; 55 | } 56 | } 57 | static void nk_set_no_new_privs(void) 58 | { 59 | if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) 60 | suicide("%s: prctl failed: %s\n", __func__, strerror(errno)); 61 | } 62 | static size_t nk_set_capability_prologue(const unsigned char *caps, 63 | size_t caplen, 64 | uint32_t *cversion) 65 | { 66 | if (!caps || !caplen) 67 | return 0; 68 | size_t csize = nk_get_capability_vinfo(cversion); 69 | if (prctl(PR_SET_KEEPCAPS, 1)) 70 | suicide("%s: prctl failed: %s\n", __func__, strerror(errno)); 71 | return csize; 72 | } 73 | 74 | #define MAX_CSIZE 3 75 | static void nk_set_capability_epilogue(const unsigned char *caps, 76 | size_t caplen, uint32_t cversion, 77 | size_t csize) 78 | { 79 | if (!caps || !caplen) 80 | return; 81 | if (csize > MAX_CSIZE) suicide("%s: MAX_CSIZE < %zu\n", __func__, csize); 82 | struct __user_cap_header_struct hdr = { 83 | .version = cversion, 84 | .pid = 0, 85 | }; 86 | struct __user_cap_data_struct data[MAX_CSIZE]; 87 | uint32_t mask[MAX_CSIZE] = {0}; 88 | for (size_t i = 0; i < caplen; ++i) { 89 | size_t j = caps[i] / 32; 90 | if (j >= csize) 91 | suicide("%s: caps[%zu] == %d, which is >= %zu and out of range\n", 92 | __func__, i, caps[i], csize * 32); 93 | mask[j] |= (uint32_t)CAP_TO_MASK(caps[i] - 32 * j); 94 | } 95 | for (size_t i = 0; i < csize; ++i) { 96 | data[i].effective = mask[i]; 97 | data[i].permitted = mask[i]; 98 | data[i].inheritable = 0; 99 | } 100 | if (capset(&hdr, (cap_user_data_t)&data) < 0) 101 | suicide("%s: capset failed: %s\n", __func__, strerror(errno)); 102 | nk_set_no_new_privs(); 103 | } 104 | #else 105 | static size_t nk_set_capability_prologue(const unsigned char *caps, 106 | size_t caplen, 107 | uint32_t *cversion) 108 | { (void)caps; (void)caplen; (void)cversion; return 0; } 109 | static void nk_set_capability_epilogue(const unsigned char *caps, 110 | size_t caplen, uint32_t cversion, 111 | size_t csize) 112 | { (void)caps; (void)caplen; (void)cversion; (void)csize; } 113 | #endif 114 | 115 | void nk_set_uidgid(uid_t uid, gid_t gid, const unsigned char *caps, 116 | size_t caplen) 117 | { 118 | uint32_t cversion = 0; 119 | size_t csize = nk_set_capability_prologue(caps, caplen, &cversion); 120 | if (setgroups(1, &gid)) 121 | suicide("%s: setgroups failed: %s\n", __func__, strerror(errno)); 122 | if (setresgid(gid, gid, gid)) 123 | suicide("%s: setresgid failed: %s\n", __func__, strerror(errno)); 124 | if (setresuid(uid, uid, uid)) 125 | suicide("%s: setresuid failed: %s\n", __func__, strerror(errno)); 126 | uid_t ruid, euid, suid; 127 | if (getresuid(&ruid, &euid, &suid)) 128 | suicide("%s: getresuid failed: %s\n", __func__, strerror(errno)); 129 | if (ruid != uid || euid != uid || suid != uid) 130 | suicide("%s: getresuid failed; the OS or libc is broken\n", __func__); 131 | gid_t rgid, egid, sgid; 132 | if (getresgid(&rgid, &egid, &sgid)) 133 | suicide("%s: getresgid failed: %s\n", __func__, strerror(errno)); 134 | if (rgid != gid || egid != gid || sgid != gid) 135 | suicide("%s: getresgid failed; the OS or libc is broken\n", __func__); 136 | if (uid && setreuid((uid_t)-1, 0) == 0) 137 | suicide("%s: OS or libc broken; able to restore privs after drop\n", 138 | __func__); 139 | nk_set_capability_epilogue(caps, caplen, cversion, csize); 140 | } 141 | 142 | uid_t nk_uidgidbyname(const char *username, uid_t *uid, gid_t *gid) 143 | { 144 | if (!username) 145 | return (uid_t)-1; 146 | struct passwd *pws = getpwnam(username); 147 | if (!pws) { 148 | for (size_t i = 0; username[i]; ++i) { 149 | if (!nk_isdigit(username[i])) 150 | return (uid_t)-1; 151 | } 152 | char *p; 153 | long lt = strtol(username, &p, 10); 154 | if (errno == ERANGE && (lt == LONG_MIN || lt == LONG_MAX)) 155 | return (uid_t)-1; 156 | if (lt < 0 || lt > (long)UINT_MAX) 157 | return (uid_t)-1; 158 | if (p == username) 159 | return (uid_t)-1; 160 | pws = getpwuid((uid_t)lt); 161 | if (!pws) 162 | return (uid_t)-1; 163 | } 164 | if (gid) 165 | *gid = pws->pw_gid; 166 | if (uid) 167 | *uid = pws->pw_uid; 168 | return (uid_t)0; 169 | } 170 | 171 | gid_t nk_gidbyname(const char *groupname, gid_t *gid) 172 | { 173 | if (!groupname) 174 | return (gid_t)-1; 175 | struct group *grp = getgrnam(groupname); 176 | if (!grp) { 177 | for (size_t i = 0; groupname[i]; ++i) { 178 | if (!nk_isdigit(groupname[i])) 179 | return (gid_t)-1; 180 | } 181 | char *p; 182 | long lt = strtol(groupname, &p, 10); 183 | if (errno == ERANGE && (lt == LONG_MIN || lt == LONG_MAX)) 184 | return (gid_t)-1; 185 | if (lt < 0 || lt > (long)UINT_MAX) 186 | return (gid_t)-1; 187 | if (p == groupname) 188 | return (gid_t)-1; 189 | grp = getgrgid((gid_t)lt); 190 | if (!grp) 191 | return (gid_t)-1; 192 | } 193 | if (gid) 194 | return grp->gr_gid; 195 | return (gid_t)0; 196 | } 197 | 198 | -------------------------------------------------------------------------------- /nk/privs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2005-2014 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NCM_PRIVS_H_ 4 | #define NCM_PRIVS_H_ 5 | 6 | #include 7 | #ifdef __linux__ 8 | #include 9 | #endif 10 | 11 | void nk_set_chroot(const char *chroot_dir); 12 | void nk_set_uidgid(uid_t uid, gid_t gid, const unsigned char *caps, 13 | size_t caplen); 14 | uid_t nk_uidgidbyname(const char *username, uid_t *uid, gid_t *gid); 15 | gid_t nk_gidbyname(const char *groupname, gid_t *gid); 16 | 17 | #endif 18 | 19 | -------------------------------------------------------------------------------- /nk/random.c: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2023 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include "nk/hwrng.h" 5 | #include "nk/random.h" 6 | 7 | void nk_random_init(struct nk_random_state *s) 8 | { 9 | nk_hwrng_bytes(s->seed, sizeof(uint64_t) * 3); 10 | s->seed[3] = 1; 11 | for (size_t i = 0; i < 12; ++i) nk_random_u64(s); 12 | } 13 | 14 | static inline uint64_t rotl64(const uint64_t x, int k) { 15 | return (x << k) | (x >> (64 - k)); 16 | } 17 | 18 | uint64_t nk_random_u64(struct nk_random_state *s) 19 | { 20 | const uint64_t t = s->seed[0] + s->seed[1] + s->seed[3]++; 21 | s->seed[0] = s->seed[1] ^ (s->seed[1] >> 11); 22 | s->seed[1] = s->seed[2] + (s->seed[2] << 3); 23 | s->seed[2] = rotl64(s->seed[2], 24) + t; 24 | return t; 25 | } 26 | 27 | -------------------------------------------------------------------------------- /nk/random.h: -------------------------------------------------------------------------------- 1 | // Copyright 2013-2018 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NCMLIB_RANDOM__ 4 | #define NCMLIB_RANDOM__ 5 | #include 6 | 7 | struct nk_random_state { 8 | uint64_t seed[4]; 9 | }; 10 | 11 | void nk_random_init(struct nk_random_state *s); 12 | uint64_t nk_random_u64(struct nk_random_state *s); 13 | static inline uint32_t nk_random_u32(struct nk_random_state *s) 14 | { 15 | // Discard lower bits as they have less linear complexity. 16 | return nk_random_u64(s) >> 32; 17 | } 18 | 19 | #endif 20 | 21 | -------------------------------------------------------------------------------- /nl.c: -------------------------------------------------------------------------------- 1 | // Copyright 2011-2018 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "nk/log.h" 13 | #include "nk/io.h" 14 | #include "nl.h" 15 | 16 | int rtattr_assign(struct rtattr *attr, int type, void *data) 17 | { 18 | struct rtattr **tb = data; 19 | if (type >= IFA_MAX) 20 | return 0; 21 | tb[type] = attr; 22 | return 0; 23 | } 24 | 25 | #define NLMSG_TAIL(nmsg) \ 26 | ((struct rtattr *) (((uint8_t*) (nmsg)) + \ 27 | NLMSG_ALIGN((nmsg)->nlmsg_len))) 28 | 29 | int nl_add_rtattr(struct nlmsghdr *n, size_t max_length, int type, 30 | const void *data, size_t data_length) 31 | { 32 | size_t length = RTA_LENGTH(data_length); 33 | 34 | if (NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length) > max_length) 35 | return -E2BIG; 36 | 37 | struct rtattr *rta = NLMSG_TAIL(n); 38 | rta->rta_type = type; 39 | rta->rta_len = length; 40 | memcpy(RTA_DATA(rta), data, data_length); 41 | n->nlmsg_len = NLMSG_ALIGN(n->nlmsg_len) + RTA_ALIGN(length); 42 | 43 | return 0; 44 | } 45 | 46 | void nl_rtattr_parse(const struct nlmsghdr *nlh, size_t offset, 47 | nl_rtattr_parse_fn workfn, void *data) 48 | { 49 | struct rtattr *attr = 50 | (struct rtattr *)((char *)NLMSG_DATA(nlh) + NLMSG_ALIGN(offset)); 51 | size_t rtlen = nlh->nlmsg_len - NLMSG_HDRLEN - NLMSG_ALIGN(offset); 52 | for (; RTA_OK(attr, rtlen); attr = RTA_NEXT(attr, rtlen)) { 53 | if (workfn(attr, attr->rta_type, data) < 0) 54 | break; 55 | } 56 | } 57 | 58 | ssize_t nl_recv_buf(int fd, char *buf, size_t blen) 59 | { 60 | struct sockaddr_nl addr; 61 | struct iovec iov = { 62 | .iov_base = buf, 63 | .iov_len = blen, 64 | }; 65 | struct msghdr msg = { 66 | .msg_name = &addr, 67 | .msg_namelen = sizeof addr, 68 | .msg_iov = &iov, 69 | .msg_iovlen = 1, 70 | }; 71 | ssize_t ret; 72 | ret = safe_recvmsg(fd, &msg, MSG_DONTWAIT); 73 | if (ret < 0) { 74 | if (errno == EAGAIN || errno == EWOULDBLOCK) 75 | return 0; 76 | log_line("%s: recvmsg failed: %s\n", __func__, strerror(errno)); 77 | return -1; 78 | } 79 | if (msg.msg_flags & MSG_TRUNC) { 80 | log_line("%s: Buffer not long enough for message.\n", __func__); 81 | return -1; 82 | } 83 | if (msg.msg_namelen != sizeof addr) { 84 | log_line("%s: Response was not of the same address family.\n", 85 | __func__); 86 | return -1; 87 | } 88 | return ret; 89 | } 90 | 91 | int nl_foreach_nlmsg(char *buf, size_t blen, uint32_t seq, uint32_t portid, 92 | nlmsg_foreach_fn pfn, void *fnarg) 93 | { 94 | const struct nlmsghdr *nlh = (const struct nlmsghdr *)buf; 95 | 96 | assert(pfn); 97 | for (;NLMSG_OK(nlh, blen); nlh = NLMSG_NEXT(nlh, blen)) { 98 | // PortID should be zero for messages from the kernel. 99 | if (nlh->nlmsg_pid && portid && nlh->nlmsg_pid != portid) 100 | continue; 101 | if (seq && nlh->nlmsg_seq != seq) 102 | continue; 103 | 104 | if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) { 105 | pfn(nlh, fnarg); 106 | } else { 107 | switch (nlh->nlmsg_type) { 108 | case NLMSG_ERROR: 109 | log_line("%s: Received a NLMSG_ERROR: %s\n", 110 | __func__, strerror(nlmsg_get_error(nlh))); 111 | return -1; 112 | case NLMSG_DONE: 113 | return 0; 114 | case NLMSG_OVERRUN: 115 | log_line("%s: Received a NLMSG_OVERRUN.\n", __func__); 116 | case NLMSG_NOOP: 117 | default: 118 | break; 119 | } 120 | } 121 | } 122 | return 0; 123 | } 124 | 125 | static int nl_sendgetlink_do(int fd, uint32_t seq, int ifindex, int by_ifindex) 126 | { 127 | char nlbuf[512] = {0}; 128 | struct nlmsghdr *nlh = (struct nlmsghdr *)nlbuf; 129 | struct ifinfomsg *ifinfomsg; 130 | 131 | nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifinfomsg)); 132 | nlh->nlmsg_type = RTM_GETLINK; 133 | nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; 134 | nlh->nlmsg_seq = seq; 135 | 136 | if (by_ifindex) { 137 | ifinfomsg = NLMSG_DATA(nlh); 138 | ifinfomsg->ifi_index = ifindex; 139 | } 140 | 141 | struct sockaddr_nl addr = { 142 | .nl_family = AF_NETLINK, 143 | }; 144 | ssize_t r = safe_sendto(fd, nlbuf, nlh->nlmsg_len, 0, 145 | (struct sockaddr *)&addr, sizeof addr); 146 | if (r < 0 || (size_t)r != nlh->nlmsg_len) { 147 | if (r < 0) 148 | log_line("%s: sendto socket failed: %s\n", __func__, 149 | strerror(errno)); 150 | else 151 | log_line("%s: sendto short write: %zd < %u\n", __func__, r, 152 | nlh->nlmsg_len); 153 | return -1; 154 | } 155 | return 0; 156 | } 157 | 158 | int nl_sendgetlinks(int fd, uint32_t seq) 159 | { 160 | return nl_sendgetlink_do(fd, seq, 0, 0); 161 | } 162 | 163 | int nl_sendgetlink(int fd, uint32_t seq, int ifindex) 164 | { 165 | return nl_sendgetlink_do(fd, seq, ifindex, 1); 166 | } 167 | 168 | static int nl_sendgetaddr_do(int fd, uint32_t seq, uint32_t ifindex, int by_ifindex, 169 | int afamily, int by_afamily) 170 | { 171 | char nlbuf[512] = {0}; 172 | struct nlmsghdr *nlh = (struct nlmsghdr *)nlbuf; 173 | struct ifaddrmsg *ifaddrmsg; 174 | 175 | nlh->nlmsg_len = NLMSG_LENGTH(sizeof(struct ifaddrmsg)); 176 | nlh->nlmsg_type = RTM_GETADDR; 177 | nlh->nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT; 178 | nlh->nlmsg_seq = seq; 179 | 180 | ifaddrmsg = NLMSG_DATA(nlh); 181 | if (by_afamily) 182 | ifaddrmsg->ifa_family = afamily; 183 | if (by_ifindex) 184 | ifaddrmsg->ifa_index = ifindex; 185 | 186 | struct sockaddr_nl addr = { 187 | .nl_family = AF_NETLINK, 188 | }; 189 | ssize_t r = safe_sendto(fd, nlbuf, nlh->nlmsg_len, 0, 190 | (struct sockaddr *)&addr, sizeof addr); 191 | if (r < 0 || (size_t)r != nlh->nlmsg_len) { 192 | if (r < 0) 193 | log_line("%s: sendto socket failed: %s\n", __func__, 194 | strerror(errno)); 195 | else 196 | log_line("%s: sendto short write: %zd < %u\n", __func__, r, 197 | nlh->nlmsg_len); 198 | return -1; 199 | } 200 | return 0; 201 | } 202 | 203 | int nl_sendgetaddrs(int fd, uint32_t seq) 204 | { 205 | return nl_sendgetaddr_do(fd, seq, 0, 0, 0, 0); 206 | } 207 | 208 | int nl_sendgetaddrs4(int fd, uint32_t seq) 209 | { 210 | return nl_sendgetaddr_do(fd, seq, 0, 0, AF_INET, 1); 211 | } 212 | 213 | int nl_sendgetaddrs6(int fd, uint32_t seq) 214 | { 215 | return nl_sendgetaddr_do(fd, seq, 0, 0, AF_INET6, 1); 216 | } 217 | 218 | int nl_sendgetaddr(int fd, uint32_t seq, uint32_t ifindex) 219 | { 220 | return nl_sendgetaddr_do(fd, seq, ifindex, 1, 0, 0); 221 | } 222 | 223 | int nl_sendgetaddr4(int fd, uint32_t seq, uint32_t ifindex) 224 | { 225 | return nl_sendgetaddr_do(fd, seq, ifindex, 1, AF_INET, 1); 226 | } 227 | 228 | int nl_sendgetaddr6(int fd, uint32_t seq, uint32_t ifindex) 229 | { 230 | return nl_sendgetaddr_do(fd, seq, ifindex, 1, AF_INET6, 1); 231 | } 232 | 233 | int nl_open(int nltype, unsigned nlgroup, uint32_t *nlportid) 234 | { 235 | int fd; 236 | fd = socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK | SOCK_CLOEXEC, nltype); 237 | if (fd < 0) { 238 | log_line("%s: socket failed: %s\n", __func__, strerror(errno)); 239 | return -1; 240 | } 241 | socklen_t al; 242 | struct sockaddr_nl nlsock = { 243 | .nl_family = AF_NETLINK, 244 | .nl_groups = nlgroup, 245 | }; 246 | if (bind(fd, (struct sockaddr *)&nlsock, sizeof nlsock) < 0) { 247 | log_line("%s: bind to group failed: %s\n", 248 | __func__, strerror(errno)); 249 | goto err_close; 250 | } 251 | al = sizeof nlsock; 252 | if (getsockname(fd, (struct sockaddr *)&nlsock, &al) < 0) { 253 | log_line("%s: getsockname failed: %s\n", 254 | __func__, strerror(errno)); 255 | goto err_close; 256 | } 257 | if (al != sizeof nlsock) { 258 | log_line("%s: Bound socket doesn't have right family size.\n", 259 | __func__); 260 | goto err_close; 261 | } 262 | if (nlsock.nl_family != AF_NETLINK) { 263 | log_line("%s: Bound socket isn't AF_NETLINK.\n", 264 | __func__); 265 | goto err_close; 266 | } 267 | if (nlportid) 268 | *nlportid = nlsock.nl_pid; 269 | return fd; 270 | err_close: 271 | close(fd); 272 | return -1; 273 | } 274 | 275 | -------------------------------------------------------------------------------- /nl.h: -------------------------------------------------------------------------------- 1 | // Copyright 2011-2017 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NK_NL_H_ 4 | #define NK_NL_H_ 5 | 6 | // Limited netlink code. The horrors... 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | static inline int nlmsg_get_error(const struct nlmsghdr *nlh) 13 | { 14 | const struct nlmsgerr *err = (const struct nlmsgerr *)NLMSG_DATA(nlh); 15 | if (nlh->nlmsg_len < sizeof(struct nlmsgerr) + NLMSG_HDRLEN) 16 | return EBADMSG; 17 | return -err->error; 18 | } 19 | 20 | int rtattr_assign(struct rtattr *attr, int type, void *data); 21 | int nl_add_rtattr(struct nlmsghdr *n, size_t max_length, int type, 22 | const void *data, size_t data_length); 23 | typedef int (*nl_rtattr_parse_fn)(struct rtattr *attr, int type, void *data); 24 | void nl_rtattr_parse(const struct nlmsghdr *nlh, size_t offset, 25 | nl_rtattr_parse_fn workfn, void *data); 26 | 27 | ssize_t nl_recv_buf(int fd, char *buf, size_t blen); 28 | 29 | typedef void (*nlmsg_foreach_fn)(const struct nlmsghdr *, void *); 30 | int nl_foreach_nlmsg(char *buf, size_t blen, uint32_t seq, 31 | uint32_t portid, 32 | nlmsg_foreach_fn pfn, void *fnarg); 33 | int nl_sendgetlinks(int fd, uint32_t seq); 34 | int nl_sendgetlink(int fd, uint32_t seq, int ifindex); 35 | int nl_sendgetaddr(int fd, uint32_t seq, uint32_t ifindex); 36 | int nl_sendgetaddr4(int fd, uint32_t seq, uint32_t ifindex); 37 | int nl_sendgetaddr6(int fd, uint32_t seq, uint32_t ifindex); 38 | int nl_sendgetaddrs(int fd, uint32_t seq); 39 | int nl_sendgetaddrs4(int fd, uint32_t seq); 40 | int nl_sendgetaddrs6(int fd, uint32_t seq); 41 | 42 | int nl_open(int nltype, unsigned nlgroup, uint32_t *nlportid); 43 | 44 | #endif 45 | -------------------------------------------------------------------------------- /nlsocket.c: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include "nlsocket.h" 7 | #include "dhcp_state.h" 8 | #include "nk/log.h" 9 | #include "nk/random.h" 10 | #include "nl.h" 11 | 12 | extern struct nk_random_state g_rngstate; 13 | 14 | struct netif_addrinfo 15 | { 16 | char if_name[IFNAMSIZ]; 17 | int if_index; 18 | struct in6_addr address; 19 | struct in6_addr peer_address; 20 | struct in6_addr broadcast_address; 21 | struct in6_addr anycast_address; 22 | unsigned char addr_type; 23 | unsigned char prefixlen; 24 | unsigned char flags; 25 | unsigned char scope; 26 | }; 27 | 28 | static void request_links(struct NLSocket *self); 29 | static void request_addrs(struct NLSocket *self, int ifidx); 30 | static void process_receive(struct NLSocket *self, const char *buf, size_t bytes_xferred, 31 | unsigned seq, unsigned portid); 32 | 33 | void NLSocket_init(struct NLSocket *self) 34 | { 35 | self->nlseq = nk_random_u64(&g_rngstate); 36 | self->got_newlink = false; 37 | self->query_ifindex = -1; 38 | 39 | self->fd = nl_open(NETLINK_ROUTE, RTMGRP_LINK, 0); 40 | if (self->fd < 0) suicide("NLSocket: failed to create netlink socket\n"); 41 | 42 | request_links(self); 43 | 44 | struct pollfd pfd = { .fd = self->fd, .events = POLLIN|POLLHUP|POLLERR }; 45 | for (;(self->got_newlink == false);) { 46 | if (poll(&pfd, 1, -1) < 0) { 47 | if (errno == EINTR) continue; 48 | suicide("poll failed\n"); 49 | } 50 | if (pfd.revents & (POLLHUP|POLLERR)) { 51 | suicide("nlfd closed unexpectedly\n"); 52 | } 53 | if (pfd.revents & POLLIN) { 54 | NLSocket_process_input(self); 55 | } 56 | } 57 | } 58 | 59 | bool NLSocket_get_interface_addresses(struct NLSocket *self, int ifindex) 60 | { 61 | self->query_ifindex = ifindex; 62 | if (self->query_ifindex < 0) return false; 63 | request_addrs(self, self->query_ifindex); 64 | 65 | struct pollfd pfd = { .fd = self->fd, .events = POLLIN|POLLHUP|POLLERR }; 66 | while (self->query_ifindex >= 0) { 67 | if (poll(&pfd, 1, -1) < 0) { 68 | if (errno == EINTR) continue; 69 | suicide("poll failed\n"); 70 | } 71 | if (pfd.revents & (POLLHUP|POLLERR)) { 72 | suicide("nlfd closed unexpectedly\n"); 73 | } 74 | if (pfd.revents & POLLIN) { 75 | NLSocket_process_input(self); 76 | } 77 | } 78 | return true; 79 | } 80 | 81 | void NLSocket_process_input(struct NLSocket *self) 82 | { 83 | char buf[8192]; 84 | for (;;) { 85 | ssize_t buflen = recv(self->fd, buf, sizeof buf, MSG_DONTWAIT); 86 | if (buflen < 0) { 87 | int err = errno; 88 | if (err == EINTR) continue; 89 | if (err == EAGAIN || err == EWOULDBLOCK) break; 90 | suicide("nlsocket: recv failed: %s\n", strerror(err)); 91 | } 92 | process_receive(self, buf, (size_t)buflen, 0, 0); 93 | } 94 | } 95 | 96 | static void request_links(struct NLSocket *self) 97 | { 98 | uint32_t link_seq = self->nlseq++; 99 | if (nl_sendgetlinks(self->fd, link_seq) < 0) 100 | suicide("nlsocket: failed to get initial rtlink state\n"); 101 | } 102 | 103 | static void request_addrs(struct NLSocket *self, int ifidx) 104 | { 105 | uint32_t addr_seq = self->nlseq++; 106 | if (nl_sendgetaddr(self->fd, addr_seq, (uint32_t)ifidx) < 0) 107 | suicide("nlsocket: failed to get initial rtaddr state\n"); 108 | } 109 | 110 | static void parse_raw_address6(struct in6_addr *addr, struct rtattr *tb[], size_t type) 111 | { 112 | memcpy(addr, RTA_DATA(tb[type]), sizeof *addr); 113 | } 114 | static void parse_raw_address4(struct in6_addr *addr, struct rtattr *tb[], size_t type) 115 | { 116 | ipaddr_from_v4_bytes(addr, RTA_DATA(tb[type])); 117 | } 118 | 119 | static void process_rt_addr_msgs(struct NLSocket *self, const struct nlmsghdr *nlh) 120 | { 121 | struct ifaddrmsg *ifa = NLMSG_DATA(nlh); 122 | struct rtattr *tb[IFA_MAX] = {0}; 123 | nl_rtattr_parse(nlh, sizeof *ifa, rtattr_assign, tb); 124 | 125 | struct netif_addrinfo nia = { 126 | .addr_type = ifa->ifa_family, 127 | .prefixlen = ifa->ifa_prefixlen, 128 | .flags = ifa->ifa_flags, 129 | .if_index = (int)ifa->ifa_index, 130 | .scope = ifa->ifa_scope, 131 | }; 132 | if (nia.addr_type != AF_INET6 && nia.addr_type != AF_INET) 133 | return; 134 | if (tb[IFA_ADDRESS]) { 135 | if (nia.addr_type == AF_INET6) 136 | parse_raw_address6(&nia.address, tb, IFA_ADDRESS); 137 | else 138 | parse_raw_address4(&nia.address, tb, IFA_ADDRESS); 139 | } 140 | if (tb[IFA_LOCAL]) { 141 | if (nia.addr_type == AF_INET6) 142 | parse_raw_address6(&nia.peer_address, tb, IFA_LOCAL); 143 | else 144 | parse_raw_address4(&nia.peer_address, tb, IFA_LOCAL); 145 | } 146 | if (tb[IFA_LABEL]) { 147 | const char *v = RTA_DATA(tb[IFA_LABEL]); 148 | size_t src_size = strlen(v); 149 | if (src_size >= sizeof nia.if_name) { 150 | log_line("nlsocket: Interface name (%s) too long\n", v); 151 | return; 152 | } 153 | *((char *)mempcpy(nia.if_name, v, src_size)) = 0; 154 | } 155 | if (tb[IFA_BROADCAST]) { 156 | if (nia.addr_type == AF_INET6) 157 | parse_raw_address6(&nia.broadcast_address, tb, IFA_BROADCAST); 158 | else 159 | parse_raw_address4(&nia.broadcast_address, tb, IFA_BROADCAST); 160 | } 161 | if (tb[IFA_ANYCAST]) { 162 | if (nia.addr_type == AF_INET6) 163 | parse_raw_address6(&nia.anycast_address, tb, IFA_ANYCAST); 164 | else 165 | parse_raw_address4(&nia.anycast_address, tb, IFA_ANYCAST); 166 | } 167 | 168 | switch (nlh->nlmsg_type) { 169 | case RTM_NEWADDR: { 170 | if (self->query_ifindex == nia.if_index) self->query_ifindex = -1; 171 | if (nia.if_index >= 0 && nia.if_index < MAX_NL_INTERFACES) { 172 | struct netif_info *n = &self->interfaces[nia.if_index]; 173 | if (nia.addr_type == AF_INET || ipaddr_is_v4(&nia.address)) { 174 | n->has_v4_address = true; 175 | n->v4_address = nia.address; 176 | emplace_broadcast(nia.if_index, &nia.broadcast_address); 177 | 178 | uint32_t subnet = 0xffffffffu; 179 | for (unsigned j = 0, jend = 32 - nia.prefixlen; j < jend; ++j) subnet <<= 1; 180 | subnet = htonl(subnet); 181 | char sbuf[INET_ADDRSTRLEN+1]; 182 | if (inet_ntop(AF_INET, &subnet, sbuf, sizeof sbuf)) { 183 | struct in6_addr taddr; 184 | if (!ipaddr_from_string(&taddr, sbuf)) abort(); 185 | emplace_subnet(nia.if_index, &taddr); 186 | } 187 | return; 188 | } else if (nia.addr_type == AF_INET6) { 189 | if (nia.scope == RT_SCOPE_UNIVERSE) { 190 | n->has_v6_address_global = true; 191 | n->v6_address_global = nia.address; 192 | n->v6_prefixlen_global = nia.prefixlen; 193 | } else if (nia.scope == RT_SCOPE_LINK) { 194 | n->has_v6_address_link = true; 195 | n->v6_address_link = nia.address; 196 | } 197 | } 198 | return; 199 | } 200 | log_line("nlsocket: Address for unknown interface %s\n", nia.if_name); 201 | } 202 | case RTM_DELADDR: { 203 | if (nia.if_index >= 0 && nia.if_index < MAX_NL_INTERFACES) { 204 | struct netif_info *n = &self->interfaces[nia.if_index]; 205 | if (n->has_v4_address && (nia.addr_type == AF_INET || ipaddr_is_v4(&nia.address))) { 206 | if (!memcmp(&n->v4_address, &nia.address, sizeof nia.address)) { 207 | memset(&n->v4_address, 0, sizeof n->v4_address); 208 | n->has_v4_address = false; 209 | } 210 | } else if ((n->has_v6_address_global || n->has_v6_address_link) && nia.addr_type == AF_INET6) { 211 | if (nia.scope == RT_SCOPE_UNIVERSE) { 212 | if (!memcmp(&n->v6_address_global, &nia.address, sizeof nia.address)) { 213 | memset(&n->v6_address_global, 0, sizeof n->v6_address_global); 214 | n->has_v6_address_global = false; 215 | } 216 | } else if (nia.scope == RT_SCOPE_LINK) { 217 | if (!memcmp(&n->v6_address_link, &nia.address, sizeof nia.address)) { 218 | memset(&n->v6_address_link, 0, sizeof n->v6_address_link); 219 | n->has_v6_address_link = false; 220 | } 221 | } 222 | } 223 | return; 224 | } 225 | } 226 | default: 227 | log_line("nlsocket: Unhandled address message type: %u\n", nlh->nlmsg_type); 228 | return; 229 | } 230 | } 231 | 232 | static void process_rt_link_msgs(struct NLSocket *self, const struct nlmsghdr *nlh) 233 | { 234 | struct ifinfomsg *ifm = NLMSG_DATA(nlh); 235 | struct rtattr *tb[IFLA_MAX] = {0}; 236 | nl_rtattr_parse(nlh, sizeof *ifm, rtattr_assign, tb); 237 | 238 | struct netif_info nii = { 239 | .family = ifm->ifi_family, 240 | .device_type = ifm->ifi_type, 241 | .index = ifm->ifi_index, 242 | .flags = ifm->ifi_flags, 243 | .change_mask = ifm->ifi_change, 244 | .is_active = ifm->ifi_flags & IFF_UP, 245 | }; 246 | if (tb[IFLA_ADDRESS]) { 247 | const uint8_t *mac = RTA_DATA(tb[IFLA_ADDRESS]); 248 | memcpy(nii.macaddr, mac, sizeof nii.macaddr); 249 | } 250 | if (tb[IFLA_BROADCAST]) { 251 | const uint8_t *mac = RTA_DATA(tb[IFLA_ADDRESS]); 252 | memcpy(nii.macbc, mac, sizeof nii.macbc); 253 | } 254 | if (tb[IFLA_IFNAME]) { 255 | const char *v = RTA_DATA(tb[IFLA_IFNAME]); 256 | size_t src_size = strlen(v); 257 | if (src_size >= sizeof nii.name) { 258 | log_line("nlsocket: Interface name (%s) in link message is too long\n", v); 259 | return; 260 | } 261 | *((char *)mempcpy(nii.name, v, src_size)) = 0; 262 | } 263 | if (tb[IFLA_MTU]) 264 | nii.mtu = *(uint32_t *)(RTA_DATA(tb[IFLA_MTU])); 265 | if (tb[IFLA_LINK]) 266 | nii.link_type = *(int32_t *)(RTA_DATA(tb[IFLA_LINK])); 267 | 268 | switch (nlh->nlmsg_type) { 269 | case RTM_NEWLINK: { 270 | if (ifm->ifi_index >= MAX_NL_INTERFACES) { 271 | log_line("nlsocket: Attempt to add interface with out-of-range index (%d)\n", ifm->ifi_index); 272 | break; 273 | } 274 | bool update = false; 275 | if (!strcmp(self->interfaces[ifm->ifi_index].name, nii.name)) { 276 | // We don't alter name or index, and addresses are not 277 | // sent in this message, so don't alter those. 278 | struct netif_info *n = &self->interfaces[ifm->ifi_index]; 279 | n->family = nii.family; 280 | n->device_type = nii.device_type; 281 | n->flags = nii.flags; 282 | n->change_mask = nii.change_mask; 283 | n->mtu = nii.mtu; 284 | n->link_type = nii.link_type; 285 | memcpy(&n->macaddr, &nii.macaddr, sizeof n->macaddr); 286 | memcpy(&n->macbc, &nii.macbc, sizeof n->macbc); 287 | n->is_active = nii.is_active; 288 | update = true; 289 | } 290 | if (!update) memcpy(&self->interfaces[ifm->ifi_index], &nii, sizeof self->interfaces[0]); 291 | log_line("nlsocket: Adding link info: %s\n", nii.name); 292 | break; 293 | } 294 | case RTM_DELLINK: { 295 | if (ifm->ifi_index >= MAX_NL_INTERFACES) { 296 | log_line("nlsocket: Attempt to delete interface with out-of-range index (%d)\n", ifm->ifi_index); 297 | break; 298 | } 299 | memset(&self->interfaces[ifm->ifi_index], 0, sizeof self->interfaces[0]); 300 | break; 301 | } 302 | default: 303 | log_line("nlsocket: Unhandled link message type: %u\n", nlh->nlmsg_type); 304 | break; 305 | } 306 | } 307 | 308 | static void process_nlmsg(struct NLSocket *self, const struct nlmsghdr *nlh) 309 | { 310 | switch(nlh->nlmsg_type) { 311 | case RTM_NEWLINK: 312 | case RTM_DELLINK: 313 | process_rt_link_msgs(self, nlh); 314 | break; 315 | case RTM_NEWADDR: 316 | case RTM_DELADDR: 317 | process_rt_addr_msgs(self, nlh); 318 | break; 319 | default: 320 | log_line("nlsocket: Unhandled RTNETLINK msg type: %u\n", nlh->nlmsg_type); 321 | break; 322 | } 323 | } 324 | 325 | static void process_receive(struct NLSocket *self, const char *buf, size_t bytes_xferred, 326 | unsigned seq, unsigned portid) 327 | { 328 | const struct nlmsghdr *nlh = (const struct nlmsghdr *)buf; 329 | for (;NLMSG_OK(nlh, bytes_xferred); nlh = NLMSG_NEXT(nlh, bytes_xferred)) { 330 | // Should be 0 for messages from the kernel. 331 | if (nlh->nlmsg_pid && portid && nlh->nlmsg_pid != portid) 332 | continue; 333 | if (seq && nlh->nlmsg_seq != seq) 334 | continue; 335 | 336 | if (nlh->nlmsg_type >= NLMSG_MIN_TYPE) { 337 | process_nlmsg(self, nlh); 338 | } else { 339 | switch (nlh->nlmsg_type) { 340 | case NLMSG_ERROR: { 341 | log_line("nlsocket: Received a NLMSG_ERROR: %s\n", 342 | strerror(nlmsg_get_error(nlh))); 343 | struct nlmsgerr *nle = NLMSG_DATA(nlh); 344 | log_line("error=%u len=%u type=%u flags=%u seq=%u pid=%u\n", 345 | nle->error, nle->msg.nlmsg_len, nle->msg.nlmsg_type, 346 | nle->msg.nlmsg_flags, nle->msg.nlmsg_seq, 347 | nle->msg.nlmsg_pid); 348 | break; 349 | } 350 | case NLMSG_OVERRUN: log_line("nlsocket: Received a NLMSG_OVERRUN.\n"); 351 | case NLMSG_NOOP: break; 352 | case NLMSG_DONE: self->got_newlink = true; break; 353 | default: break; 354 | } 355 | } 356 | } 357 | } 358 | 359 | -------------------------------------------------------------------------------- /nlsocket.h: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_NLSOCKET_H_ 4 | #define NDHS_NLSOCKET_H_ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include "nl.h" 11 | 12 | #define MAX_NL_INTERFACES 50 13 | 14 | struct netif_info 15 | { 16 | char name[IFNAMSIZ]; 17 | char macaddr[6]; 18 | char macbc[6]; 19 | struct in6_addr v4_address; 20 | struct in6_addr v6_address_global; 21 | struct in6_addr v6_address_link; 22 | int index; 23 | int link_type; 24 | unsigned int flags; 25 | unsigned int change_mask; 26 | unsigned int mtu; 27 | unsigned short device_type; 28 | unsigned char v6_prefixlen_global; 29 | unsigned char family; 30 | bool is_active:1; 31 | bool has_v4_address:1; 32 | bool has_v6_address_global:1; 33 | bool has_v6_address_link:1; 34 | }; 35 | 36 | struct NLSocket 37 | { 38 | struct netif_info interfaces[MAX_NL_INTERFACES]; 39 | int query_ifindex; 40 | int fd; 41 | uint32_t nlseq; 42 | bool got_newlink:1; 43 | }; 44 | 45 | void NLSocket_init(struct NLSocket *self); 46 | bool NLSocket_get_interface_addresses(struct NLSocket *self, int ifindex); 47 | 48 | void NLSocket_process_input(struct NLSocket *self); 49 | 50 | static inline int NLSocket_get_ifindex(const struct NLSocket *self, const char *name) { 51 | for (int i = 0; i < MAX_NL_INTERFACES; ++i) { 52 | if (!strcmp(name, self->interfaces[i].name)) return i; 53 | } 54 | return -1; 55 | } 56 | 57 | // The pointer that is returned is stable because the function is only 58 | // called after NLSocket is constructed. 59 | static inline struct netif_info *NLSocket_get_ifinfo(struct NLSocket *self, int ifindex) 60 | { 61 | if (ifindex < 0 || ifindex >= MAX_NL_INTERFACES) return NULL; 62 | return &self->interfaces[ifindex]; 63 | } 64 | static inline struct netif_info *NLSocket_get_ifinfo_by_name(struct NLSocket *self, const char *name) 65 | { 66 | for (size_t i = 0; i < MAX_NL_INTERFACES; ++i) { 67 | if (!strcmp(name, self->interfaces[i].name)) return &self->interfaces[i]; 68 | } 69 | return NULL; 70 | } 71 | 72 | #endif 73 | -------------------------------------------------------------------------------- /options.c: -------------------------------------------------------------------------------- 1 | // Copyright 2004-2018 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "nk/log.h" 8 | #include 9 | 10 | #include "options.h" 11 | 12 | static int do_overload_value(const uint8_t *buf, size_t blen, int overload) 13 | { 14 | size_t i = 0; 15 | while (i < blen) { 16 | if (buf[i] == DCODE_PADDING) { 17 | ++i; 18 | continue; 19 | } 20 | if (buf[i] == DCODE_END) 21 | break; 22 | if (i + 2 >= blen) 23 | break; 24 | if (buf[i] == DCODE_OVERLOAD) { 25 | if (buf[i+1] == 1) { 26 | overload |= buf[i+2]; 27 | i += 3; 28 | continue; 29 | } 30 | } 31 | i += buf[i+1] + 2; 32 | } 33 | return overload; 34 | } 35 | 36 | static int overload_value(const struct dhcpmsg * const packet) 37 | { 38 | int ol = do_overload_value(packet->options, sizeof packet->options, 0); 39 | if (ol & 1 && ol & 2) 40 | return ol; 41 | if (ol & 1) { 42 | ol |= do_overload_value(packet->file, sizeof packet->file, ol); 43 | return ol; 44 | } 45 | if (ol & 2) { 46 | ol |= do_overload_value(packet->sname, sizeof packet->sname, ol); 47 | return ol; 48 | } 49 | return ol; // ol == 0 50 | } 51 | 52 | static void do_get_dhcp_opt(const uint8_t *sbuf, size_t slen, uint8_t code, 53 | uint8_t *dbuf, size_t dlen, size_t *didx) 54 | { 55 | size_t i = 0; 56 | while (i < slen) { 57 | if (sbuf[i] == DCODE_PADDING) { 58 | ++i; 59 | continue; 60 | } 61 | if (sbuf[i] == DCODE_END) 62 | break; 63 | if (i >= slen - 2) 64 | break; 65 | size_t soptsiz = sbuf[i+1]; 66 | if (sbuf[i] == code) { 67 | if (dlen < soptsiz + *didx) 68 | return; 69 | if (slen < soptsiz + i + 2) 70 | return; 71 | memcpy(dbuf + *didx, sbuf+i+2, soptsiz); 72 | *didx += soptsiz; 73 | } 74 | i += soptsiz + 2; 75 | } 76 | } 77 | 78 | size_t get_dhcp_opt(const struct dhcpmsg * const packet, uint8_t code, 79 | uint8_t *dbuf, size_t dlen) 80 | { 81 | int ol = overload_value(packet); 82 | size_t didx = 0; 83 | do_get_dhcp_opt(packet->options, sizeof packet->options, code, 84 | dbuf, dlen, &didx); 85 | if (ol & 1) 86 | do_get_dhcp_opt(packet->file, sizeof packet->file, code, 87 | dbuf, dlen, &didx); 88 | if (ol & 2) 89 | do_get_dhcp_opt(packet->sname, sizeof packet->sname, code, 90 | dbuf, dlen, &didx); 91 | return didx; 92 | } 93 | 94 | // return the position of the 'end' option 95 | ssize_t get_end_option_idx(const struct dhcpmsg * const packet) 96 | { 97 | for (size_t i = 0; i < sizeof packet->options; ++i) { 98 | if (packet->options[i] == DCODE_END) 99 | return (ssize_t)i; 100 | if (packet->options[i] == DCODE_PADDING) 101 | continue; 102 | if (i + 1 >= sizeof packet->options) 103 | break; 104 | i += (size_t)packet->options[i+1] + 1; 105 | } 106 | log_line("get_end_option_idx: Did not find DCODE_END marker.\n"); 107 | return -1; 108 | } 109 | 110 | static inline size_t sizeof_option_str(uint8_t code, size_t datalen) 111 | { 112 | if (code == DCODE_PADDING || code == DCODE_END) 113 | return 1; 114 | return 2 + datalen; 115 | } 116 | 117 | // Add a raw data string to the options. It will take a binary string suitable 118 | // for use with eg memcpy() and will append it to the options[] field of 119 | // a dhcp packet with the requested option code and proper length specifier. 120 | size_t add_option_string(struct dhcpmsg *packet, uint8_t code, 121 | const char * const str, size_t slen) 122 | { 123 | size_t len = sizeof_option_str(code, slen); 124 | if (slen > 255 || len != slen + 2) { 125 | log_line("add_option_string: Length checks failed.\n"); 126 | return 0; 127 | } 128 | 129 | ssize_t end = get_end_option_idx(packet); 130 | if (end < 0) { 131 | log_line("add_option_string: Buffer has no DCODE_END marker.\n"); 132 | return 0; 133 | } 134 | if ((size_t)end + len >= sizeof packet->options) { 135 | log_line("add_option_string: No space for option 0x%02x.\n", code); 136 | return 0; 137 | } 138 | packet->options[end] = code; 139 | packet->options[end+1] = slen; 140 | memcpy(packet->options + end + 2, str, slen); 141 | packet->options[(size_t)end+len] = DCODE_END; 142 | return len; 143 | } 144 | 145 | static ssize_t add_option_check(struct dhcpmsg *packet, uint8_t code, 146 | uint8_t rlen) 147 | { 148 | ssize_t end = get_end_option_idx(packet); 149 | if (end < 0) { 150 | log_line("add_u%01u_option: Buffer has no DCODE_END marker.\n", rlen*8); 151 | return -1; 152 | } 153 | if ((size_t)end + 2 + rlen >= sizeof packet->options) { 154 | log_line("add_u%01u_option: No space for option 0x%02x.\n", 155 | rlen*8, code); 156 | return -1; 157 | } 158 | return end; 159 | } 160 | 161 | static size_t add_u8_option(struct dhcpmsg *packet, uint8_t code, uint8_t data) 162 | { 163 | ssize_t end = add_option_check(packet, code, 1); 164 | if (end < 0) 165 | return 0; 166 | packet->options[end] = code; 167 | packet->options[end+1] = 1; 168 | packet->options[end+2] = data; 169 | packet->options[end+3] = DCODE_END; 170 | return 3; 171 | } 172 | 173 | #ifndef NDHS_BUILD 174 | // Data should be in network byte order. 175 | static size_t add_u16_option(struct dhcpmsg *packet, uint8_t code, 176 | uint16_t data) 177 | { 178 | ssize_t end = add_option_check(packet, code, 2); 179 | if (end < 0) 180 | return 0; 181 | uint8_t *dp = (uint8_t *)&data; 182 | packet->options[end] = code; 183 | packet->options[end+1] = 2; 184 | packet->options[end+2] = dp[0]; 185 | packet->options[end+3] = dp[1]; 186 | packet->options[end+4] = DCODE_END; 187 | return 4; 188 | } 189 | #endif 190 | 191 | // Data should be in network byte order. 192 | size_t add_u32_option(struct dhcpmsg *packet, uint8_t code, uint32_t data) 193 | { 194 | ssize_t end = add_option_check(packet, code, 4); 195 | if (end < 0) 196 | return 0; 197 | uint8_t *dp = (uint8_t *)&data; 198 | packet->options[end] = code; 199 | packet->options[end+1] = 4; 200 | packet->options[end+2] = dp[0]; 201 | packet->options[end+3] = dp[1]; 202 | packet->options[end+4] = dp[2]; 203 | packet->options[end+5] = dp[3]; 204 | packet->options[end+6] = DCODE_END; 205 | return 6; 206 | } 207 | 208 | // Add a parameter request list for stubborn DHCP servers 209 | size_t add_option_request_list(struct dhcpmsg *packet) 210 | { 211 | static const char reqdata[] = { 212 | DCODE_SUBNET, DCODE_ROUTER, DCODE_DNS, DCODE_HOSTNAME, DCODE_DOMAIN, 213 | DCODE_BROADCAST, 214 | }; 215 | return add_option_string(packet, DCODE_PARAM_REQ, 216 | reqdata, sizeof reqdata); 217 | } 218 | 219 | #ifdef NDHS_BUILD 220 | size_t add_option_domain_name(struct dhcpmsg *packet, const char * const dom, 221 | size_t domlen) 222 | { 223 | return add_option_string(packet, DCODE_DOMAIN, dom, domlen); 224 | } 225 | 226 | void add_option_subnet_mask(struct dhcpmsg *packet, uint32_t subnet) 227 | { 228 | add_u32_option(packet, DCODE_SUBNET, subnet); 229 | } 230 | 231 | void add_option_broadcast(struct dhcpmsg *packet, uint32_t bc) 232 | { 233 | add_u32_option(packet, DCODE_BROADCAST, bc); 234 | } 235 | void add_option_router(struct dhcpmsg *packet, uint32_t router) 236 | { 237 | add_u32_option(packet, DCODE_ROUTER, router); 238 | } 239 | #endif 240 | 241 | void add_option_msgtype(struct dhcpmsg *packet, uint8_t type) 242 | { 243 | add_u8_option(packet, DCODE_MSGTYPE, type); 244 | } 245 | 246 | void add_option_reqip(struct dhcpmsg *packet, uint32_t ip) 247 | { 248 | add_u32_option(packet, DCODE_REQIP, ip); 249 | } 250 | 251 | void add_option_serverid(struct dhcpmsg *packet, uint32_t sid) 252 | { 253 | add_u32_option(packet, DCODE_SERVER_ID, sid); 254 | } 255 | 256 | void add_option_clientid(struct dhcpmsg *packet, const char * const clientid, 257 | size_t clen) 258 | { 259 | add_option_string(packet, DCODE_CLIENT_ID, clientid, clen); 260 | } 261 | 262 | #ifndef NDHS_BUILD 263 | void add_option_maxsize(struct dhcpmsg *packet) 264 | { 265 | add_u16_option(packet, DCODE_MAX_SIZE, 266 | htons(sizeof(struct ip_udp_dhcp_packet))); 267 | } 268 | 269 | void add_option_vendor(struct dhcpmsg *packet, const char * const vendor, 270 | size_t vsize) 271 | { 272 | if (vsize) 273 | add_option_string(packet, DCODE_VENDOR, vendor, vsize); 274 | } 275 | 276 | void add_option_hostname(struct dhcpmsg *packet, const char * const hostname, 277 | size_t hsize) 278 | { 279 | if (hsize) 280 | add_option_string(packet, DCODE_HOSTNAME, hostname, hsize); 281 | } 282 | #endif 283 | 284 | uint32_t get_option_router(const struct dhcpmsg * const packet) 285 | { 286 | uint32_t ret = 0; 287 | uint8_t buf[MAX_DOPT_SIZE]; 288 | const size_t ol = get_dhcp_opt(packet, DCODE_ROUTER, buf, sizeof buf); 289 | if (ol == sizeof ret) 290 | memcpy(&ret, buf, sizeof ret); 291 | return ret; 292 | } 293 | 294 | uint8_t get_option_msgtype(const struct dhcpmsg * const packet) 295 | { 296 | uint8_t ret = 0; 297 | uint8_t buf[MAX_DOPT_SIZE]; 298 | const size_t ol = get_dhcp_opt(packet, DCODE_MSGTYPE, buf, sizeof buf); 299 | if (ol == sizeof ret) 300 | ret = buf[0]; 301 | return ret; 302 | } 303 | 304 | uint32_t get_option_serverid(const struct dhcpmsg *const packet, int *found) 305 | { 306 | uint32_t ret = 0; 307 | uint8_t buf[MAX_DOPT_SIZE]; 308 | *found = 0; 309 | const size_t ol = get_dhcp_opt(packet, DCODE_SERVER_ID, buf, sizeof buf); 310 | if (ol == sizeof ret) { 311 | *found = 1; 312 | memcpy(&ret, buf, sizeof ret); 313 | } 314 | return ret; 315 | } 316 | 317 | uint32_t get_option_leasetime(const struct dhcpmsg * const packet) 318 | { 319 | uint32_t ret = 0; 320 | uint8_t buf[MAX_DOPT_SIZE]; 321 | const size_t ol = get_dhcp_opt(packet, DCODE_LEASET, buf, sizeof buf); 322 | if (ol == sizeof ret) { 323 | memcpy(&ret, buf, sizeof ret); 324 | ret = ntohl(ret); 325 | } 326 | return ret; 327 | } 328 | 329 | // Returned buffer is not nul-terminated. 330 | size_t get_option_clientid(const struct dhcpmsg * const packet, char *cbuf, 331 | size_t clen) 332 | { 333 | if (clen < 1) 334 | return 0; 335 | return get_dhcp_opt(packet, DCODE_CLIENT_ID, (uint8_t *)cbuf, clen); 336 | } 337 | 338 | -------------------------------------------------------------------------------- /options.h: -------------------------------------------------------------------------------- 1 | // Copyright 2004-2018 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef OPTIONS_H_ 4 | #define OPTIONS_H_ 5 | 6 | #include "dhcp.h" 7 | 8 | #define DCODE_PADDING 0x00 9 | #define DCODE_SUBNET 0x01 10 | #define DCODE_TIMEZONE 0x02 11 | #define DCODE_ROUTER 0x03 12 | #define DCODE_DNS 0x06 13 | #define DCODE_LPRSVR 0x09 14 | #define DCODE_HOSTNAME 0x0c 15 | #define DCODE_DOMAIN 0x0f 16 | #define DCODE_IPTTL 0x17 17 | #define DCODE_MTU 0x1a 18 | #define DCODE_BROADCAST 0x1c 19 | #define DCODE_NTPSVR 0x2a 20 | #define DCODE_WINS 0x2c 21 | #define DCODE_REQIP 0x32 22 | #define DCODE_LEASET 0x33 23 | #define DCODE_OVERLOAD 0x34 24 | #define DCODE_MSGTYPE 0x35 25 | #define DCODE_SERVER_ID 0x36 26 | #define DCODE_PARAM_REQ 0x37 27 | #define DCODE_MAX_SIZE 0x39 28 | #define DCODE_VENDOR 0x3c 29 | #define DCODE_CLIENT_ID 0x3d 30 | #define DCODE_END 0xff 31 | 32 | #define MAX_DOPT_SIZE 500 33 | 34 | size_t get_dhcp_opt(const struct dhcpmsg * const packet, uint8_t code, 35 | uint8_t *dbuf, size_t dlen); 36 | ssize_t get_end_option_idx(const struct dhcpmsg * const packet); 37 | 38 | size_t add_option_string(struct dhcpmsg *packet, uint8_t code, 39 | const char *str, size_t slen); 40 | size_t add_u32_option(struct dhcpmsg *packet, uint8_t code, uint32_t data); 41 | 42 | size_t add_option_request_list(struct dhcpmsg *packet); 43 | #ifdef NDHS_BUILD 44 | size_t add_option_domain_name(struct dhcpmsg *packet, 45 | const char * const dom, size_t domlen); 46 | void add_option_subnet_mask(struct dhcpmsg *packet, uint32_t subnet); 47 | void add_option_broadcast(struct dhcpmsg *packet, uint32_t bc); 48 | void add_option_router(struct dhcpmsg *packet, uint32_t router); 49 | #endif 50 | void add_option_msgtype(struct dhcpmsg *packet, uint8_t type); 51 | void add_option_reqip(struct dhcpmsg *packet, uint32_t ip); 52 | void add_option_serverid(struct dhcpmsg *packet, uint32_t sid); 53 | void add_option_clientid(struct dhcpmsg *packet, 54 | const char * const clientid, size_t clen); 55 | #ifndef NDHS_BUILD 56 | void add_option_maxsize(struct dhcpmsg *packet); 57 | void add_option_vendor(struct dhcpmsg *packet, const char * const vendor, 58 | size_t vsize); 59 | void add_option_hostname(struct dhcpmsg *packet, const char * const hostname, 60 | size_t hsize); 61 | #endif 62 | uint32_t get_option_router(const struct dhcpmsg * const packet); 63 | uint8_t get_option_msgtype(const struct dhcpmsg * const packet); 64 | uint32_t get_option_serverid(const struct dhcpmsg * const packet, int *found); 65 | uint32_t get_option_leasetime(const struct dhcpmsg *const packet); 66 | size_t get_option_clientid(const struct dhcpmsg * const packet, 67 | char *cbuf, size_t clen); 68 | 69 | #endif 70 | -------------------------------------------------------------------------------- /parsehelp.h: -------------------------------------------------------------------------------- 1 | #ifndef NDHS_PARSEHELP_H_ 2 | #define NDHS_PARSEHELP_H_ 3 | 4 | #include 5 | 6 | static inline char parsehelp_lc(char c) 7 | { 8 | if (c >= 'A' && c <= 'Z') return 'a' + (c - 'A'); 9 | return c; 10 | } 11 | 12 | static inline void lc_string_inplace(char *s, size_t len) 13 | { 14 | for (size_t i = 0; i < len; ++i) s[i] = parsehelp_lc(s[i]); 15 | } 16 | 17 | static void assign_strbuf(char *dest, size_t *destlen, size_t maxlen, const char *start, const char *end) 18 | { 19 | ptrdiff_t d = end - start; 20 | size_t l = (size_t)d; 21 | if (d < 0 || l >= maxlen) abort(); 22 | *(char *)mempcpy(dest, start, l) = 0; 23 | if (destlen) *destlen = l; 24 | } 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /radv6.c: -------------------------------------------------------------------------------- 1 | // Copyright 2014-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include "radv6.h" 13 | #include "nlsocket.h" 14 | #include "dhcp6.h" 15 | #include "multicast6.h" 16 | #include "attach_bpf.h" 17 | #include "sbufs.h" 18 | #include "dhcp_state.h" 19 | #include "nk/net_checksum16.h" 20 | #include "nk/log.h" 21 | #include "nk/io.h" 22 | #include "nk/random.h" 23 | 24 | extern struct nk_random_state g_rngstate; 25 | 26 | struct RA6Listener 27 | { 28 | struct timespec advert_ts; 29 | char ifname[IFNAMSIZ]; 30 | int ifindex; 31 | int fd; 32 | unsigned advi_s_max; 33 | bool using_bpf:1; 34 | }; 35 | 36 | static inline void toggle_bit(bool v, void *data, size_t arrayidx, unsigned char bitidx) 37 | { 38 | unsigned char *d = data; 39 | if (v) d[arrayidx] |= bitidx; 40 | else d[arrayidx] &= ~bitidx; 41 | } 42 | 43 | static inline bool sa6_from_string(struct sockaddr_in6 *sin, const char *str) 44 | { 45 | memset(sin, 0, sizeof(struct sockaddr_in6)); 46 | sin->sin6_family = AF_INET6; 47 | if (inet_pton(AF_INET6, str, &sin->sin6_addr) != 1) { 48 | log_line("inet_pton failed: %s\n", strerror(errno)); 49 | return false; 50 | } 51 | return true; 52 | } 53 | 54 | /* XXX: Configuration options: 55 | * 56 | * is_router = false :: Can we forward packets to/from the interface? 57 | * -> If true, we send periodic router advertisements. 58 | * Send times are randomized between interval/min_interval using 59 | * a UNIFORM distribution. 60 | * advert_interval_sec = 600 :: Maximum time between multicast router 61 | * adverts. min=4, max=1800 62 | * advert_min_interval_sec (NOT CONFIGURABLE) :: 63 | * min,max = [3, 0.75 * advert_interval_sec] 64 | * default = max(0.33 * advert_interval_sec, 3) 65 | * 66 | * is_managed = false :: Does the network use DHCPv6 for address assignment? 67 | * other_config = false :: Does the network use DHCPv6 for other net info? 68 | * mtu = 0 :: Advertise specified MTU if value >= IPv6 Min MTU (1280?) 69 | * reachable_time = 0 :: Value for the reachable time field. 70 | * 0 means unspecified. 71 | * Must be <= 3600000ms (1h) 72 | * retransmit_time = 0 :: Value for the retransmit time field. 73 | * 0 means unspecified. 74 | * curhoplimit = 0 :: Value for the Cur Hop Limit field. 75 | * 0 means unspecified. 76 | * default_lifetime = 3 * advert_interval_sec :: 77 | * Router lifetime field value. 78 | * 79 | * prefix_list = everything but link local :: 80 | * Prefix Information options. 81 | * Valid Lifetime should default to 2592000 seconds (30d) 82 | * On Link Flag (L-bit) : True 83 | * Preferred Lifetime should default to 604800 seconds (7d) 84 | * MUST be <= Valid Lifetime 85 | * Autonomous Flag: True 86 | */ 87 | 88 | #define ICMP_HEADER_SIZE 4 89 | struct icmp_header { uint8_t data_[4]; }; 90 | static uint8_t icmp_header_type(const struct icmp_header *self) { return self->data_[0]; } 91 | static uint8_t icmp_header_code(const struct icmp_header *self) { return self->data_[1]; } 92 | static void icmp_header_set_type(struct icmp_header *self, uint8_t v) { self->data_[0] = v; } 93 | static void icmp_header_set_checksum(struct icmp_header *self, uint8_t v) { encode16be(self->data_ + 2, v); } 94 | static bool icmp_header_read(struct icmp_header *self, struct sbufs *rbuf) 95 | { 96 | if (sbufs_brem(rbuf) < ICMP_HEADER_SIZE) return false; 97 | memcpy(&self->data_, rbuf->si, sizeof self->data_); 98 | rbuf->si += ICMP_HEADER_SIZE; 99 | return true; 100 | } 101 | static bool icmp_header_write(const struct icmp_header *self, struct sbufs *sbuf) 102 | { 103 | if (sbufs_brem(sbuf) < ICMP_HEADER_SIZE) return false; 104 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 105 | sbuf->si += ICMP_HEADER_SIZE; 106 | return true; 107 | } 108 | 109 | // Just a reserved 32-bit field. 110 | // Follow with MTU and Prefix Information options. 111 | #define RA6_SOLICIT_HEADER_SIZE 4 112 | struct ra6_solicit_header { uint8_t data_[4]; }; 113 | static bool ra6_solicit_header_read(struct ra6_solicit_header *self, struct sbufs *rbuf) 114 | { 115 | if (sbufs_brem(rbuf) < RA6_SOLICIT_HEADER_SIZE) return false; 116 | memcpy(&self->data_, rbuf->si, sizeof self->data_); 117 | rbuf->si += RA6_SOLICIT_HEADER_SIZE; 118 | return true; 119 | } 120 | 121 | #define RA6_ADVERT_HEADER_SIZE 12 122 | // Follow with MTU and Prefix Information options. 123 | struct ra6_advert_header { uint8_t data_[12]; }; 124 | static void ra6_advert_header_set_hoplimit(struct ra6_advert_header *self, uint8_t v) { self->data_[0] = v; } 125 | static void ra6_advert_header_set_managed_addresses(struct ra6_advert_header *self, bool v) 126 | { toggle_bit(v, self->data_, 1, 1 << 7); } 127 | static void ra6_advert_header_set_other_stateful(struct ra6_advert_header *self, bool v) 128 | { toggle_bit(v, self->data_, 1, 1 << 6); } 129 | static void ra6_advert_header_set_router_lifetime(struct ra6_advert_header *self, uint16_t v) 130 | { encode16be(self->data_ + 2, v); } 131 | static void ra6_advert_header_set_reachable_time(struct ra6_advert_header *self, uint32_t v) 132 | { encode32be(self->data_ + 4, v); } 133 | static void ra6_advert_header_set_retransmit_timer(struct ra6_advert_header *self, uint32_t v) 134 | { encode32be(self->data_ + 8, v); } 135 | static bool ra6_advert_header_write(const struct ra6_advert_header *self, struct sbufs *sbuf) 136 | { 137 | if (sbufs_brem(sbuf) < RA6_ADVERT_HEADER_SIZE) return false; 138 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 139 | sbuf->si += RA6_ADVERT_HEADER_SIZE; 140 | return true; 141 | } 142 | 143 | #define RA6_SOURCE_LLA_OPT_SIZE 8 144 | struct ra6_source_lla_opt { uint8_t data_[8]; }; 145 | static void ra6_source_lla_opt_set_macaddr(struct ra6_source_lla_opt *self, const char *mac) 146 | { memcpy(self->data_ + 2, mac, 6); } 147 | static bool ra6_source_lla_opt_write(const struct ra6_source_lla_opt *self, struct sbufs *sbuf) 148 | { 149 | if (sbufs_brem(sbuf) < RA6_SOURCE_LLA_OPT_SIZE) return false; 150 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 151 | sbuf->si += RA6_SOURCE_LLA_OPT_SIZE; 152 | return true; 153 | } 154 | 155 | #define RA6_MTU_OPT_SIZE 8 156 | struct ra6_mtu_opt { uint8_t data_[8]; }; 157 | static void ra6_mtu_opt_set_mtu(struct ra6_mtu_opt *self, uint32_t v) { encode32be(self->data_ + 4, v); } 158 | static bool ra6_mtu_opt_write(const struct ra6_mtu_opt *self, struct sbufs *sbuf) 159 | { 160 | if (sbufs_brem(sbuf) < RA6_MTU_OPT_SIZE) return false; 161 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 162 | sbuf->si += RA6_MTU_OPT_SIZE; 163 | return true; 164 | } 165 | 166 | #define RA6_PREFIX_INFO_OPT_SIZE 32 167 | struct ra6_prefix_info_opt { uint8_t data_[32]; }; 168 | static void ra6_prefix_info_opt_set_prefix(struct ra6_prefix_info_opt *self, const struct in6_addr *v, uint8_t pl) 169 | { 170 | self->data_[2] = pl; 171 | uint8_t a6[16]; 172 | memcpy(a6, v, sizeof a6); 173 | uint8_t keep_bytes = pl / 8; 174 | uint8_t keep_bits = pl % 8; 175 | if (keep_bits == 0) 176 | memset(a6 + keep_bytes, 0, 16 - keep_bytes); 177 | else { 178 | memset(a6 + keep_bytes + 1, 0, 16u - keep_bytes - 1u); 179 | uint8_t mask = 0xff; 180 | while (keep_bits--) 181 | mask >>= 1; 182 | a6[keep_bytes] &= ~mask; 183 | } 184 | memcpy(self->data_ + 16, a6, sizeof a6); 185 | } 186 | static void ra6_prefix_info_opt_set_on_link(struct ra6_prefix_info_opt *self, bool v) 187 | { toggle_bit(v, self->data_, 3, 1 << 7); } 188 | static void ra6_prefix_info_opt_set_auto_addr_cfg(struct ra6_prefix_info_opt *self, bool v) 189 | { toggle_bit(v, self->data_, 3, 1 << 6); } 190 | static void ra6_prefix_info_opt_set_router_addr_flag(struct ra6_prefix_info_opt *self, bool v) 191 | { toggle_bit(v, self->data_, 3, 1 << 5); } 192 | static void ra6_prefix_info_opt_set_valid_lifetime(struct ra6_prefix_info_opt *self, uint32_t v) 193 | { encode32be(self->data_ + 4, v); } 194 | static void ra6_prefix_info_opt_set_preferred_lifetime(struct ra6_prefix_info_opt *self, uint32_t v) 195 | { encode32be(self->data_ + 8, v); } 196 | static bool ra6_prefix_info_opt_write(const struct ra6_prefix_info_opt *self, struct sbufs *sbuf) 197 | { 198 | if (sbufs_brem(sbuf) < RA6_PREFIX_INFO_OPT_SIZE) return false; 199 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 200 | sbuf->si += RA6_PREFIX_INFO_OPT_SIZE; 201 | return true; 202 | } 203 | 204 | #define RA6_RDNS_OPT_SIZE 8 205 | struct ra6_rdns_opt { uint8_t data_[8]; }; 206 | static void ra6_rdns_opt_set_length(struct ra6_rdns_opt *self, uint8_t numdns) { self->data_[1] = 1 + 2 * numdns; } 207 | static void ra6_rdns_opt_set_lifetime(struct ra6_rdns_opt *self, uint32_t v) { encode32be(self->data_ + 4, v); } 208 | static bool ra6_rdns_opt_write(const struct ra6_rdns_opt *self, struct sbufs *sbuf) 209 | { 210 | if (sbufs_brem(sbuf) < RA6_RDNS_OPT_SIZE) return false; 211 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 212 | sbuf->si += RA6_RDNS_OPT_SIZE; 213 | return true; 214 | } 215 | 216 | #define RA6_DNS_SEARCH_OPT_SIZE 8 217 | struct ra6_dns_search_opt { uint8_t data_[8]; }; 218 | static size_t ra6_dns_search_opt_set_length(struct ra6_dns_search_opt *self, size_t sz) { 219 | self->data_[1] = 1 + sz / 8; 220 | size_t slack = sz % 8; 221 | self->data_[1] += slack > 0 ? 1 : 0; 222 | return 8 * self->data_[1] - (8 + sz); 223 | } 224 | static void ra6_dns_search_opt_set_lifetime(struct ra6_dns_search_opt *self, uint32_t v) { encode32be(self->data_ + 4, v); } 225 | static bool ra6_dns_search_opt_write(const struct ra6_dns_search_opt *self, struct sbufs *sbuf) 226 | { 227 | if (sbufs_brem(sbuf) < RA6_DNS_SEARCH_OPT_SIZE) return false; 228 | memcpy(sbuf->si, &self->data_, sizeof self->data_); 229 | sbuf->si += RA6_DNS_SEARCH_OPT_SIZE; 230 | return true; 231 | } 232 | 233 | static void attach_bpf(struct RA6Listener *self) 234 | { 235 | self->using_bpf = attach_bpf_icmp6_ra(self->fd, self->ifname); 236 | } 237 | 238 | static void set_next_advert_ts(struct RA6Listener *self) 239 | { 240 | unsigned advi_s_min = self->advi_s_max / 3 > 3u ? self->advi_s_max / 3 : 3u; 241 | // The extremely small distribution skew does not matter here. 242 | unsigned advi_s = (unsigned)(nk_random_u64(&g_rngstate) % (self->advi_s_max - advi_s_min)) + advi_s_min; 243 | clock_gettime(CLOCK_BOOTTIME, &self->advert_ts); 244 | self->advert_ts.tv_sec += advi_s; 245 | } 246 | 247 | static void set_advi_s_max(struct RA6Listener *self, unsigned v) 248 | { 249 | v = v > 4u ? v : 4u; 250 | v = v < 1800u ? v : 1800u; 251 | self->advi_s_max = v; 252 | } 253 | 254 | static bool send_advert(struct RA6Listener *self); 255 | 256 | /* 257 | * We will need to minimally support DHCPv6 for providing 258 | * DNS server information. We will support RFC6106, too, but 259 | * Windows needs DHCPv6 for DNS. 260 | */ 261 | extern struct NLSocket nl_socket; 262 | static bool init_addrs; 263 | static struct sockaddr_in6 ip6_any; 264 | static struct sockaddr_in6 mc6_allhosts; 265 | static struct sockaddr_in6 mc6_allrouters; 266 | static const uint8_t icmp_nexthdr = 58; // Assigned value 267 | 268 | struct RA6Listener *RA6Listener_create(const char *ifname, const struct netif_info *ifinfo) 269 | { 270 | if (!init_addrs) { 271 | if (!sa6_from_string(&ip6_any, "::")) return NULL; // XXX: All-zero is fine, no need to do this. 272 | if (!sa6_from_string(&mc6_allhosts, "ff02::1")) return NULL; 273 | if (!sa6_from_string(&mc6_allrouters, "ff02::2")) return NULL; 274 | init_addrs = true; 275 | } 276 | 277 | struct RA6Listener *self; 278 | struct sockaddr_in6 sai = { .sin6_family = AF_INET6, }; 279 | size_t ifname_src_size = strlen(ifname); 280 | if (ifname_src_size >= sizeof self->ifname) { 281 | log_line("RA6Listener: Interface name (%s) too long\n", ifname); 282 | return NULL; 283 | } 284 | if (!ifinfo->has_v6_address_global) { 285 | log_line("ra6: Failed to get global ipv6 address for %s\n", ifname); 286 | return NULL; 287 | } 288 | self = calloc(1, sizeof(struct RA6Listener)); 289 | if (!self) return NULL; 290 | 291 | self->ifindex = ifinfo->index; 292 | set_advi_s_max(self, 600); 293 | self->using_bpf = false; 294 | *(char *)(mempcpy(self->ifname, ifname, ifname_src_size)) = 0; 295 | 296 | if (self->fd >= 0) close(self->fd); 297 | self->fd = socket(AF_INET6, SOCK_RAW|SOCK_CLOEXEC, IPPROTO_ICMPV6); 298 | if (self->fd < 0) { 299 | log_line("ra6: Failed to create v6 ICMP socket on %s: %s\n", self->ifname, strerror(errno)); 300 | goto err0; 301 | } 302 | if (!attach_multicast_sockaddr_in6(self->fd, self->ifname, &mc6_allrouters)) { 303 | goto err1; 304 | } 305 | attach_bpf(self); 306 | 307 | if (bind(self->fd, (const struct sockaddr *)&sai, sizeof sai)) { 308 | log_line("ra6: Failed to bind ICMP route advertisement listener on %s: %s\n", self->ifname, strerror(errno)); 309 | goto err1; 310 | } 311 | 312 | if (!send_advert(self)) 313 | log_line("ra6: Failed to send initial router advertisement on %s\n", self->ifname); 314 | set_next_advert_ts(self); 315 | 316 | return self; 317 | err1: 318 | close(self->fd); 319 | self->fd = -1; 320 | err0: 321 | free(self); 322 | return NULL; 323 | } 324 | 325 | static bool send_advert(struct RA6Listener *self) 326 | { 327 | struct icmp_header icmp_hdr = {0}; 328 | struct ra6_advert_header ra6adv_hdr = {0}; 329 | struct ra6_source_lla_opt ra6_slla = {{ 1, 1 }}; 330 | struct ra6_mtu_opt ra6_mtu = {{ 5, 1 }}; 331 | struct ra6_prefix_info_opt ra6_pfxi = {{ 3, 4 }}; 332 | struct ra6_rdns_opt ra6_dns = {{ 25 }}; 333 | struct ra6_dns_search_opt ra6_dsrch = {{ 31 }}; 334 | uint32_t pktl = sizeof icmp_hdr + sizeof ra6adv_hdr + sizeof ra6_slla 335 | + sizeof ra6_mtu; 336 | 337 | struct netif_info *ifinfo = NLSocket_get_ifinfo(&nl_socket, self->ifindex); 338 | if (!ifinfo) { 339 | log_line("ra6: Failed to get interface info for %s\n", self->ifname); 340 | return false; 341 | } 342 | 343 | icmp_header_set_type(&icmp_hdr, 134); 344 | uint16_t csum = net_checksum16(&icmp_hdr, sizeof icmp_hdr); 345 | 346 | ra6_advert_header_set_hoplimit(&ra6adv_hdr, 0); 347 | ra6_advert_header_set_managed_addresses(&ra6adv_hdr, true); 348 | ra6_advert_header_set_other_stateful(&ra6adv_hdr, true); 349 | ra6_advert_header_set_router_lifetime(&ra6adv_hdr, 3 * self->advi_s_max); 350 | ra6_advert_header_set_reachable_time(&ra6adv_hdr, 0); 351 | ra6_advert_header_set_retransmit_timer(&ra6adv_hdr, 0); 352 | csum = net_checksum16_add 353 | (csum, net_checksum16(&ra6adv_hdr, sizeof ra6adv_hdr)); 354 | 355 | ra6_source_lla_opt_set_macaddr(&ra6_slla, ifinfo->macaddr); 356 | csum = net_checksum16_add 357 | (csum, net_checksum16(&ra6_slla, sizeof ra6_slla)); 358 | ra6_mtu_opt_set_mtu(&ra6_mtu, ifinfo->mtu); 359 | csum = net_checksum16_add 360 | (csum, net_checksum16(&ra6_mtu, sizeof ra6_mtu)); 361 | 362 | // Prefix Information 363 | ra6_prefix_info_opt_set_prefix(&ra6_pfxi, &ifinfo->v6_address_global, ifinfo->v6_prefixlen_global); 364 | ra6_prefix_info_opt_set_on_link(&ra6_pfxi, true); 365 | ra6_prefix_info_opt_set_auto_addr_cfg(&ra6_pfxi, false); 366 | ra6_prefix_info_opt_set_router_addr_flag(&ra6_pfxi, true); 367 | ra6_prefix_info_opt_set_valid_lifetime(&ra6_pfxi, 2592000); 368 | ra6_prefix_info_opt_set_preferred_lifetime(&ra6_pfxi, 604800); 369 | csum = net_checksum16_add(csum, net_checksum16(&ra6_pfxi, sizeof ra6_pfxi)); 370 | pktl += sizeof ra6_pfxi; 371 | 372 | struct addrlist dns_servers = query_dns_servers(ifinfo->index); 373 | struct blob d6b = query_dns6_search_blob(ifinfo->index); 374 | 375 | if (dns_servers.n) { 376 | ra6_rdns_opt_set_length(&ra6_dns, dns_servers.n); 377 | ra6_rdns_opt_set_lifetime(&ra6_dns, self->advi_s_max * 2); 378 | csum = net_checksum16_add(csum, net_checksum16(&ra6_dns, sizeof ra6_dns)); 379 | pktl += sizeof ra6_dns + 16 * dns_servers.n; 380 | } 381 | 382 | size_t dns_search_slack = 0; 383 | if (d6b.s && d6b.n) { 384 | dns_search_slack = ra6_dns_search_opt_set_length(&ra6_dsrch, d6b.n); 385 | ra6_dns_search_opt_set_lifetime(&ra6_dsrch, self->advi_s_max * 2); 386 | csum = net_checksum16_add(csum, net_checksum16(&ra6_dsrch, sizeof ra6_dsrch)); 387 | csum = net_checksum16_add(csum, net_checksum16(d6b.s, d6b.n)); 388 | pktl += sizeof ra6_dsrch + d6b.n + dns_search_slack; 389 | } 390 | 391 | csum = net_checksum16_add(csum, net_checksum16(&ip6_any.sin6_addr, sizeof ip6_any.sin6_addr)); 392 | csum = net_checksum16_add(csum, net_checksum16(&mc6_allhosts.sin6_addr, sizeof mc6_allhosts.sin6_addr)); 393 | csum = net_checksum16_add(csum, net_checksum16(&pktl, sizeof pktl)); 394 | csum = net_checksum16_add(csum, net_checksum16(&icmp_nexthdr, 1)); 395 | if (dns_servers.n) { 396 | for (size_t i = 0; i < dns_servers.n; ++i) { 397 | csum = net_checksum16_add(csum, net_checksum16(&dns_servers.addrs[i], sizeof dns_servers.addrs[i])); 398 | } 399 | } 400 | icmp_header_set_checksum(&icmp_hdr, csum); 401 | 402 | char sbuf[4096]; 403 | struct sbufs ss = { &sbuf[0], &sbuf[4096] }; 404 | if (!icmp_header_write(&icmp_hdr, &ss)) return false; 405 | if (!ra6_advert_header_write(&ra6adv_hdr, &ss)) return false; 406 | if (!ra6_source_lla_opt_write(&ra6_slla, &ss)) return false; 407 | if (!ra6_mtu_opt_write(&ra6_mtu, &ss)) return false; 408 | if (!ra6_prefix_info_opt_write(&ra6_pfxi, &ss)) return false; 409 | if (dns_servers.n) { 410 | if (!ra6_rdns_opt_write(&ra6_dns, &ss)) return false; 411 | size_t siz = 16 * dns_servers.n; 412 | if (ss.se - ss.si < (ptrdiff_t)siz) return false; 413 | memcpy(ss.si, dns_servers.addrs, siz); 414 | ss.si += siz; 415 | } 416 | if (d6b.s && d6b.n) { 417 | if (!ra6_dns_search_opt_write(&ra6_dsrch, &ss)) return false; 418 | if (ss.se - ss.si < (ptrdiff_t)d6b.n) return false; 419 | memcpy(ss.si, d6b.s, d6b.n); 420 | ss.si += d6b.n; 421 | for (size_t i = 0; i < dns_search_slack; ++i) { 422 | if (ss.si == ss.se) return false; 423 | *ss.si++ = 0; 424 | } 425 | } 426 | size_t slen = ss.si > sbuf ? (size_t)(ss.si - sbuf) : 0; 427 | 428 | if (safe_sendto(self->fd, sbuf, slen, 0, (const struct sockaddr *)&mc6_allhosts, sizeof mc6_allhosts) < 0) { 429 | log_line("ra6: sendto failed on %s: %s\n", self->ifname, strerror(errno)); 430 | return false; 431 | } 432 | return true; 433 | } 434 | 435 | int RA6Listener_send_periodic_advert(struct RA6Listener *self) 436 | { 437 | struct timespec now; 438 | clock_gettime(CLOCK_BOOTTIME, &now); 439 | if (now.tv_sec > self->advert_ts.tv_sec 440 | || (now.tv_sec == self->advert_ts.tv_sec && now.tv_nsec > self->advert_ts.tv_nsec)) { 441 | if (!send_advert(self)) 442 | log_line("ra6: Failed to send periodic router advertisement on %s\n", self->ifname); 443 | set_next_advert_ts(self); 444 | } 445 | return 1000 + (self->advert_ts.tv_sec - now.tv_sec) * 1000; // Always wait at least 1s 446 | } 447 | 448 | static bool ip6_is_unspecified(const struct sockaddr_in6 *sa) 449 | { 450 | struct sockaddr_in6 t = {0}; 451 | return memcmp(&sa->sin6_addr, &t.sin6_addr, sizeof t.sin6_addr) == 0; 452 | } 453 | 454 | static void process_receive(struct RA6Listener *self, char *buf, size_t buflen, 455 | const struct sockaddr_in6 *sai) 456 | { 457 | if (sai->sin6_family != AF_INET6) return; 458 | 459 | struct sbufs rs = { buf, buf + buflen }; 460 | // Discard if the ICMP length < 8 octets. 461 | if (buflen < ICMP_HEADER_SIZE + RA6_SOLICIT_HEADER_SIZE) { 462 | char ip[32]; 463 | ipaddr_to_string(ip, sizeof ip, &sai->sin6_addr); 464 | log_line("ra6: ICMP from %s is too short: %zu\n", ip, buflen); 465 | return; 466 | } 467 | 468 | struct icmp_header icmp_hdr; 469 | if (!icmp_header_read(&icmp_hdr, &rs)) return; 470 | 471 | if (!self->using_bpf) { 472 | // Discard if the ICMP code is not 0. 473 | if (icmp_header_code(&icmp_hdr) != 0) { 474 | log_line("ra6: ICMP code != 0 on %s\n", self->ifname); 475 | return; 476 | } 477 | 478 | if (icmp_header_type(&icmp_hdr) != 133) { 479 | log_line("ra6: ICMP type != 133 on %s\n", self->ifname); 480 | return; 481 | } 482 | } 483 | 484 | struct ra6_solicit_header ra6_solicit_hdr; 485 | if (!ra6_solicit_header_read(&ra6_solicit_hdr, &rs)) return; 486 | 487 | uint8_t macaddr[6]; 488 | bool got_macaddr = false; 489 | 490 | // Only the source link-layer address option is defined. 491 | while (rs.se >= 2 + rs.si) { 492 | uint8_t opt_type = (uint8_t)(*rs.si++); 493 | size_t opt_length = 8 * (size_t)(*rs.si++); 494 | // Discard if any included option has a length <= 0. 495 | if (opt_length <= 0) { 496 | log_line("ra6: Solicitation option length <= 0 on %s\n", self->ifname); 497 | return; 498 | } 499 | if (opt_type == 1) { 500 | if (got_macaddr) { 501 | log_line("ra6: More than one Source Link-Layer Address option on %s; dropping\n", self->ifname); 502 | return; 503 | } 504 | if (opt_length == 8) { 505 | if (rs.se - rs.si < (ptrdiff_t)sizeof macaddr) { 506 | log_line("ra6: Source Link-Layer Address is wrong size for ethernet on %s\n", self->ifname); 507 | return; 508 | } 509 | got_macaddr = true; 510 | for (size_t i = 0; i < sizeof macaddr; ++i) { 511 | macaddr[i] = (uint8_t)(*rs.si++); 512 | } 513 | } else { 514 | log_line("ra6: Source Link-Layer Address is wrong size for ethernet on %s\n", self->ifname); 515 | return; 516 | } 517 | } else { 518 | if (rs.se - rs.si < (ptrdiff_t)opt_length - 2) { 519 | log_line("ra6: Invalid length(%zu) for option type(%u) on %s\n", opt_length, +opt_type, self->ifname); 520 | return; 521 | } 522 | log_line("ra6: Ignoring unknown option type(%u) on %s\n", +opt_type, self->ifname); 523 | for (size_t i = 0; i < opt_length - 2; ++i) rs.si++; 524 | } 525 | } 526 | 527 | // Discard if the source address is unspecified and 528 | // there is no source link-layer address option included. 529 | if (!got_macaddr && ip6_is_unspecified(sai)) { 530 | log_line("ra6: Solicitation provides no specified source address or option on %s\n", self->ifname); 531 | return; 532 | } 533 | 534 | // Send a router advertisement in reply. 535 | if (!send_advert(self)) 536 | log_line("ra6: Failed to send router advertisement on %s\n", self->ifname); 537 | set_next_advert_ts(self); 538 | } 539 | 540 | void RA6Listener_process_input(struct RA6Listener *self) 541 | { 542 | char buf[8192]; 543 | for (;;) { 544 | struct sockaddr_in6 sai; 545 | socklen_t sailen = sizeof sai; 546 | ssize_t buflen = recvfrom(self->fd, buf, sizeof buf, MSG_DONTWAIT, (struct sockaddr *)&sai, &sailen); 547 | if (buflen < 0) { 548 | int err = errno; 549 | if (err == EINTR) continue; 550 | if (err == EAGAIN || err == EWOULDBLOCK) break; 551 | suicide("ra6: recvfrom failed on %s: %s\n", self->ifname, strerror(err)); 552 | } 553 | process_receive(self, buf, (size_t)buflen, &sai); 554 | } 555 | } 556 | 557 | int RA6Listener_fd(const struct RA6Listener *self) { return self->fd; } 558 | -------------------------------------------------------------------------------- /radv6.h: -------------------------------------------------------------------------------- 1 | // Copyright 2016-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_RADV6_H_ 4 | #define NDHS_RADV6_H_ 5 | 6 | struct netif_info; 7 | struct RA6Listener; 8 | struct RA6Listener *RA6Listener_create(const char *ifname, const struct netif_info *ifinfo); 9 | void RA6Listener_process_input(struct RA6Listener *self); 10 | int RA6Listener_send_periodic_advert(struct RA6Listener *self); 11 | int RA6Listener_fd(const struct RA6Listener *self); 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /sbufs.h: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Nicholas J. Kain 2 | // SPDX-License-Identifier: MIT 3 | #ifndef NDHS_SBUFS_H_ 4 | #define NDHS_SBUFS_H_ 5 | 6 | #include 7 | 8 | struct sbufs { 9 | char *si; 10 | char *se; 11 | }; 12 | 13 | static inline size_t sbufs_brem(const struct sbufs *self) 14 | { 15 | return self->se > self->si ? (size_t)(self->se - self->si) : 0; 16 | } 17 | 18 | #endif 19 | --------------------------------------------------------------------------------