]>
Commit | Line | Data |
---|---|---|
1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ | |
2 | /*** | |
3 | Copyright © 2013 Intel Corporation. All rights reserved. | |
4 | ***/ | |
5 | ||
6 | #include <linux/filter.h> | |
7 | #include <linux/if_infiniband.h> | |
8 | #include <linux/if_packet.h> | |
9 | #include <net/if_arp.h> | |
10 | #include <stdio.h> | |
11 | #include <string.h> | |
12 | ||
13 | #include "dhcp-network.h" | |
14 | #include "dhcp-protocol.h" | |
15 | #include "ether-addr-util.h" | |
16 | #include "fd-util.h" | |
17 | #include "socket-util.h" | |
18 | #include "unaligned.h" | |
19 | ||
20 | static int _bind_raw_socket( | |
21 | int ifindex, | |
22 | union sockaddr_union *link, | |
23 | uint32_t xid, | |
24 | const struct hw_addr_data *hw_addr, | |
25 | const struct hw_addr_data *bcast_addr, | |
26 | uint16_t arp_type, | |
27 | uint16_t port, | |
28 | bool so_priority_set, | |
29 | int so_priority) { | |
30 | ||
31 | assert(ifindex > 0); | |
32 | assert(link); | |
33 | assert(hw_addr); | |
34 | assert(bcast_addr); | |
35 | assert(IN_SET(arp_type, ARPHRD_ETHER, ARPHRD_INFINIBAND)); | |
36 | ||
37 | switch (arp_type) { | |
38 | case ARPHRD_ETHER: | |
39 | assert(hw_addr->length == ETH_ALEN); | |
40 | assert(bcast_addr->length == ETH_ALEN); | |
41 | break; | |
42 | case ARPHRD_INFINIBAND: | |
43 | assert(hw_addr->length == 0); | |
44 | assert(bcast_addr->length == INFINIBAND_ALEN); | |
45 | break; | |
46 | default: | |
47 | assert_not_reached(); | |
48 | } | |
49 | ||
50 | struct sock_filter filter[] = { | |
51 | BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */ | |
52 | BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(DHCPPacket), 1, 0), /* packet >= DHCPPacket ? */ | |
53 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
54 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.protocol)), /* A <- IP protocol */ | |
55 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 1, 0), /* IP protocol == UDP ? */ | |
56 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
57 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags */ | |
58 | BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x20), /* A <- A & 0x20 (More Fragments bit) */ | |
59 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ | |
60 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
61 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, ip.frag_off)), /* A <- Flags + Fragment offset */ | |
62 | BPF_STMT(BPF_ALU + BPF_AND + BPF_K, 0x1fff), /* A <- A & 0x1fff (Fragment offset) */ | |
63 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 1, 0), /* A == 0 ? */ | |
64 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
65 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, udp.dest)), /* A <- UDP destination port */ | |
66 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, port, 1, 0), /* UDP destination port == DHCP client port ? */ | |
67 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
68 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.op)), /* A <- DHCP op */ | |
69 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, BOOTREPLY, 1, 0), /* op == BOOTREPLY ? */ | |
70 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
71 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.htype)), /* A <- DHCP header type */ | |
72 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, arp_type, 1, 0), /* header type == arp_type ? */ | |
73 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
74 | BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.xid)), /* A <- client identifier */ | |
75 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, xid, 1, 0), /* client identifier == xid ? */ | |
76 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
77 | BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(DHCPPacket, dhcp.hlen)), /* A <- MAC address length */ | |
78 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (uint8_t) hw_addr->length, 1, 0), /* address length == hw_addr->length ? */ | |
79 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
80 | ||
81 | /* We only support MAC address length to be either 0 or 6 (ETH_ALEN). Optionally | |
82 | * compare chaddr for ETH_ALEN bytes. */ | |
83 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETH_ALEN, 0, 8), /* A (the MAC address length) == ETH_ALEN ? */ | |
84 | BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be32(hw_addr->bytes)), /* X <- 4 bytes of client's MAC */ | |
85 | BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr)), /* A <- 4 bytes of MAC from dhcp.chaddr */ | |
86 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ | |
87 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
88 | BPF_STMT(BPF_LDX + BPF_IMM, unaligned_read_be16(hw_addr->bytes + 4)), /* X <- remainder of client's MAC */ | |
89 | BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(DHCPPacket, dhcp.chaddr) + 4), /* A <- remainder of MAC from dhcp.chaddr */ | |
90 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_X, 0, 1, 0), /* A == X ? */ | |
91 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
92 | ||
93 | BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(DHCPPacket, dhcp.magic)), /* A <- DHCP magic cookie */ | |
94 | BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP_MAGIC_COOKIE, 1, 0), /* cookie == DHCP magic cookie ? */ | |
95 | BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */ | |
96 | BPF_STMT(BPF_RET + BPF_K, UINT32_MAX), /* accept */ | |
97 | }; | |
98 | struct sock_fprog fprog = { | |
99 | .len = ELEMENTSOF(filter), | |
100 | .filter = filter | |
101 | }; | |
102 | _cleanup_close_ int s = -EBADF; | |
103 | int r; | |
104 | ||
105 | s = socket(AF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); | |
106 | if (s < 0) | |
107 | return -errno; | |
108 | ||
109 | r = setsockopt_int(s, SOL_PACKET, PACKET_AUXDATA, true); | |
110 | if (r < 0) | |
111 | return r; | |
112 | ||
113 | r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog)); | |
114 | if (r < 0) | |
115 | return -errno; | |
116 | ||
117 | r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); | |
118 | if (r < 0) | |
119 | return r; | |
120 | ||
121 | if (so_priority_set) { | |
122 | r = setsockopt_int(s, SOL_SOCKET, SO_PRIORITY, so_priority); | |
123 | if (r < 0) | |
124 | return r; | |
125 | } | |
126 | ||
127 | link->ll = (struct sockaddr_ll) { | |
128 | .sll_family = AF_PACKET, | |
129 | .sll_protocol = htobe16(ETH_P_IP), | |
130 | .sll_ifindex = ifindex, | |
131 | .sll_hatype = htobe16(arp_type), | |
132 | .sll_halen = bcast_addr->length, | |
133 | }; | |
134 | /* We may overflow link->ll. link->ll_buffer ensures we have enough space. */ | |
135 | memcpy(link->ll.sll_addr, bcast_addr->bytes, bcast_addr->length); | |
136 | ||
137 | r = bind(s, &link->sa, sockaddr_ll_len(&link->ll)); | |
138 | if (r < 0) | |
139 | return -errno; | |
140 | ||
141 | return TAKE_FD(s); | |
142 | } | |
143 | ||
144 | int dhcp_network_bind_raw_socket( | |
145 | int ifindex, | |
146 | union sockaddr_union *link, | |
147 | uint32_t xid, | |
148 | const struct hw_addr_data *hw_addr, | |
149 | const struct hw_addr_data *bcast_addr, | |
150 | uint16_t arp_type, | |
151 | uint16_t port, | |
152 | bool so_priority_set, | |
153 | int so_priority) { | |
154 | ||
155 | static struct hw_addr_data default_eth_bcast = { | |
156 | .length = ETH_ALEN, | |
157 | .ether = {{ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }}, | |
158 | }, default_ib_bcast = { | |
159 | .length = INFINIBAND_ALEN, | |
160 | .infiniband = { | |
161 | 0x00, 0xff, 0xff, 0xff, 0xff, 0x12, 0x40, 0x1b, | |
162 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, | |
163 | 0xff, 0xff, 0xff, 0xff | |
164 | }, | |
165 | }; | |
166 | ||
167 | assert(ifindex > 0); | |
168 | assert(link); | |
169 | assert(hw_addr); | |
170 | ||
171 | switch (arp_type) { | |
172 | case ARPHRD_ETHER: | |
173 | return _bind_raw_socket(ifindex, link, xid, | |
174 | hw_addr, | |
175 | (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_eth_bcast, | |
176 | arp_type, port, so_priority_set, so_priority); | |
177 | ||
178 | case ARPHRD_INFINIBAND: | |
179 | return _bind_raw_socket(ifindex, link, xid, | |
180 | &HW_ADDR_NULL, | |
181 | (bcast_addr && !hw_addr_is_null(bcast_addr)) ? bcast_addr : &default_ib_bcast, | |
182 | arp_type, port, so_priority_set, so_priority); | |
183 | default: | |
184 | return -EINVAL; | |
185 | } | |
186 | } | |
187 | ||
188 | int dhcp_network_bind_udp_socket(int ifindex, be32_t address, uint16_t port, int ip_service_type) { | |
189 | union sockaddr_union src = { | |
190 | .in.sin_family = AF_INET, | |
191 | .in.sin_port = htobe16(port), | |
192 | .in.sin_addr.s_addr = address, | |
193 | }; | |
194 | _cleanup_close_ int s = -EBADF; | |
195 | int r; | |
196 | ||
197 | s = socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0); | |
198 | if (s < 0) | |
199 | return -errno; | |
200 | ||
201 | if (ip_service_type >= 0) | |
202 | r = setsockopt_int(s, IPPROTO_IP, IP_TOS, ip_service_type); | |
203 | else | |
204 | r = setsockopt_int(s, IPPROTO_IP, IP_TOS, IPTOS_CLASS_CS6); | |
205 | if (r < 0) | |
206 | return r; | |
207 | ||
208 | r = setsockopt_int(s, SOL_SOCKET, SO_REUSEADDR, true); | |
209 | if (r < 0) | |
210 | return r; | |
211 | ||
212 | r = setsockopt_int(s, SOL_SOCKET, SO_TIMESTAMP, true); | |
213 | if (r < 0) | |
214 | return r; | |
215 | ||
216 | if (ifindex > 0) { | |
217 | r = socket_bind_to_ifindex(s, ifindex); | |
218 | if (r < 0) | |
219 | return r; | |
220 | } | |
221 | ||
222 | if (port == DHCP_PORT_SERVER) { | |
223 | r = setsockopt_int(s, SOL_SOCKET, SO_BROADCAST, true); | |
224 | if (r < 0) | |
225 | return r; | |
226 | if (address == INADDR_ANY) { | |
227 | /* IP_PKTINFO filter should not be applied when packets are | |
228 | allowed to enter/leave through the interface other than | |
229 | DHCP server sits on(BindToInterface option). */ | |
230 | r = setsockopt_int(s, IPPROTO_IP, IP_PKTINFO, true); | |
231 | if (r < 0) | |
232 | return r; | |
233 | } | |
234 | } else { | |
235 | r = setsockopt_int(s, IPPROTO_IP, IP_FREEBIND, true); | |
236 | if (r < 0) | |
237 | return r; | |
238 | } | |
239 | ||
240 | if (bind(s, &src.sa, sizeof(src.in)) < 0) | |
241 | return -errno; | |
242 | ||
243 | return TAKE_FD(s); | |
244 | } | |
245 | ||
246 | int dhcp_network_send_raw_socket( | |
247 | int s, | |
248 | const union sockaddr_union *link, | |
249 | const void *packet, | |
250 | size_t len) { | |
251 | ||
252 | /* Do not add assert(s >= 0) here, as this is called in fuzz-dhcp-server, and in that case this | |
253 | * function should fail with negative errno. */ | |
254 | ||
255 | assert(link); | |
256 | assert(packet); | |
257 | assert(len > 0); | |
258 | ||
259 | if (sendto(s, packet, len, 0, &link->sa, sockaddr_ll_len(&link->ll)) < 0) | |
260 | return -errno; | |
261 | ||
262 | return 0; | |
263 | } | |
264 | ||
265 | int dhcp_network_send_udp_socket( | |
266 | int s, | |
267 | be32_t address, | |
268 | uint16_t port, | |
269 | const void *packet, | |
270 | size_t len) { | |
271 | ||
272 | union sockaddr_union dest = { | |
273 | .in.sin_family = AF_INET, | |
274 | .in.sin_port = htobe16(port), | |
275 | .in.sin_addr.s_addr = address, | |
276 | }; | |
277 | ||
278 | assert(s >= 0); | |
279 | assert(packet); | |
280 | assert(len > 0); | |
281 | ||
282 | if (sendto(s, packet, len, 0, &dest.sa, sizeof(dest.in)) < 0) | |
283 | return -errno; | |
284 | ||
285 | return 0; | |
286 | } |