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