1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410 | // Copyright (C) 2013-2024 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <dhcp/dhcp4.h>
#include <dhcp/iface_mgr.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt_filter_lpf.h>
#include <dhcp/protocol_util.h>
#include <exceptions/exceptions.h>
#include <fcntl.h><--- Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <net/ethernet.h><--- Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <linux/filter.h><--- Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <linux/if_ether.h><--- Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.
#include <linux/if_packet.h><--- Include file: not found. Please note: Cppcheck does not need standard library headers to get proper results.
namespace {
using namespace isc::dhcp;
/// The following structure defines a Berkeley Packet Filter program to perform
/// packet filtering. The program operates on Ethernet packets. To help with
/// interpretation of the program, for the types of Ethernet packets we are
/// interested in, the header layout is:
///
/// 6 bytes Destination Ethernet Address
/// 6 bytes Source Ethernet Address
/// 2 bytes Ethernet packet type
///
/// 20 bytes Fixed part of IP header
/// variable Variable part of IP header
///
/// 2 bytes UDP Source port
/// 2 bytes UDP destination port
/// 4 bytes Rest of UDP header
///
/// Each instruction is preceded with the comments giving the instruction
/// number within a BPF program, in the following format: #123.
///
/// @todo We may want to extend the filter to receive packets sent
/// to the particular IP address assigned to the interface or
/// broadcast address.
struct sock_filter dhcp_sock_filter [] = {
// Make sure this is an IP packet: check the half-word (two bytes)
// at offset 12 in the packet (the Ethernet packet type). If it
// is, advance to the next instruction. If not, advance 11
// instructions (which takes execution to the last instruction in
// the sequence: "drop it").
// #0
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
// #1
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 11),
// Make sure it's a UDP packet. The IP protocol is at offset
// 9 in the IP header so, adding the Ethernet packet header size
// of 14 bytes gives an absolute byte offset in the packet of 23.
// #2
BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
// #3
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 9),
// Make sure this isn't a fragment by checking that the fragment
// offset field in the IP header is zero. This field is the
// least-significant 13 bits in the bytes at offsets 6 and 7 in
// the IP header, so the half-word at offset 20 (6 + size of
// Ethernet header) is loaded and an appropriate mask applied.
// #4
BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
// #5
BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 7, 0),
// Check the packet's destination address. The program will only
// allow the packets sent to the broadcast address or unicast
// to the specific address on the interface. By default, this
// address is set to 0 and must be set to the specific value
// when the raw socket is created and the program is attached
// to it. The caller must assign the address to the
// prog.bf_insns[8].k in the network byte order.
// #6
BPF_STMT(BPF_LD + BPF_W + BPF_ABS,
ETHERNET_HEADER_LEN + IP_DEST_ADDR_OFFSET),
// If this is a broadcast address, skip the next check.
// #7
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xffffffff, 1, 0),
// If this is not broadcast address, compare it with the unicast
// address specified for the interface.
// #8
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 4),
// Get the IP header length. This is achieved by the following
// (special) instruction that, given the offset of the start
// of the IP header (offset 14) loads the IP header length.
// #9
BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),
// Make sure it's to the right port. The following instruction
// adds the previously extracted IP header length to the given
// offset to locate the correct byte. The given offset of 16
// comprises the length of the Ethernet header (14) plus the offset
// of the UDP destination port (2) within the UDP header.
// #10
BPF_STMT(BPF_LD + BPF_H + BPF_IND, ETHERNET_HEADER_LEN + UDP_DEST_PORT),
// The following instruction tests against the default DHCP server port,
// but the action port is actually set in PktFilterBPF::openSocket().
// N.B. The code in that method assumes that this instruction is at
// offset 11 in the program. If this is changed, openSocket() must be
// updated.
// #11
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
// If we passed all the tests, ask for the whole packet.
// #12
BPF_STMT(BPF_RET + BPF_K, (u_int)-1),
// Otherwise, drop it.
// #13
BPF_STMT(BPF_RET + BPF_K, 0),
};
}
using namespace isc::util;
namespace isc {
namespace dhcp {
bool
PktFilterLPF::isSocketReceivedTimeSupported() const {
#ifdef SO_TIMESTAMP
return (true);
#else
return (false);
#endif
}
SocketInfo
PktFilterLPF::openSocket(Iface& iface,
const isc::asiolink::IOAddress& addr,
const uint16_t port, const bool,
const bool) {
// Open fallback socket first. If it fails, it will give us an indication
// that there is another service (perhaps DHCP server) running.
// The function will throw an exception and effectively cease opening
// raw socket below.
int fallback = openFallbackSocket(addr, port);
// The fallback is open, so we are good to open primary socket.
int sock = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
if (sock < 0) {
close(fallback);
isc_throw(SocketConfigError, "Failed to create raw LPF socket");
}
// Set the close-on-exec flag.
if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
close(sock);
close(fallback);
isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
<< " on the socket " << sock);
}
// Create socket filter program. This program will only allow incoming UDP
// traffic which arrives on the specific (DHCP) port). It will also filter
// out all fragmented packets.
struct sock_fprog filter_program;
memset(&filter_program, 0, sizeof(filter_program));
filter_program.filter = dhcp_sock_filter;
filter_program.len = sizeof(dhcp_sock_filter) / sizeof(struct sock_filter);
// Configure the filter program to receive unicast packets sent to the
// specified address. The program will also allow packets sent to the
// 255.255.255.255 broadcast address.
dhcp_sock_filter[8].k = addr.toUint32();
// Override the default port value.
dhcp_sock_filter[11].k = port;
// Apply the filter.
if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
sizeof(filter_program)) < 0) {
close(sock);
close(fallback);
isc_throw(SocketConfigError, "Failed to install packet filtering program"
<< " on the socket " << sock);
}
#ifdef SO_TIMESTAMP
int enable = 1;
if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
const char* errmsg = strerror(errno);
isc_throw(SocketConfigError, "Could not enable SO_TIMESTAMP for " << addr.toText()
<< ", error: " << errmsg);
}
#endif
struct sockaddr_ll sa;
memset(&sa, 0, sizeof(sockaddr_ll));
sa.sll_family = AF_PACKET;
sa.sll_ifindex = iface.getIndex();
// For raw sockets we construct IP headers on our own, so we don't bind
// socket to IP address but to the interface. We will later use the
// Linux Packet Filtering to filter out these packets that we are
// interested in.
if (bind(sock, reinterpret_cast<const struct sockaddr*>(&sa),
sizeof(sa)) < 0) {
close(sock);
close(fallback);
isc_throw(SocketConfigError, "Failed to bind LPF socket '" << sock
<< "' to interface '" << iface.getName() << "'");
}
// Set socket to non-blocking mode.
if (fcntl(sock, F_SETFL, O_NONBLOCK) != 0) {
// Get the error message immediately after the bind because the
// invocation to close() below would override the errno.
char* errmsg = strerror(errno);<--- Variable 'errmsg' can be declared as pointer to const
close(sock);
close(fallback);
isc_throw(SocketConfigError, "failed to set SO_NONBLOCK option on the"
" LPF socket '" << sock << "' to interface '"
<< iface.getName() << "', reason: " << errmsg);
}
return (SocketInfo(addr, port, sock, fallback));
}
Pkt4Ptr
PktFilterLPF::receive(Iface& iface, const SocketInfo& socket_info) {
uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
// First let's get some data from the fallback socket. The data will be
// discarded but we don't want the socket buffer to bloat. We get the
// packets from the socket in loop but most of the time the loop will
// end after receiving one packet. The call to recv returns immediately
// when there is no data left on the socket because the socket is
// non-blocking.
// @todo In the normal conditions, both the primary socket and the fallback
// socket are in sync as they are set to receive packets on the same
// address and port. The reception of packets on the fallback socket
// shouldn't cause significant lags in packet reception. If we find in the
// future that it does, the sort of threshold could be set for the maximum
// bytes received on the fallback socket in a single round. Further
// optimizations would include an asynchronous read from the fallback socket
// when the DHCP server is idle.
int datalen;
do {
datalen = recv(socket_info.fallbackfd_, raw_buf, sizeof(raw_buf), 0);
} while (datalen > 0);
#ifndef SO_TIMESTAMP
// Now that we finished getting data from the fallback socket, we
// have to get the data from the raw socket too.
int data_len = read(socket_info.sockfd_, raw_buf, sizeof(raw_buf));
// If negative value is returned by read(), it indicates that an
// error occurred. If returned value is 0, no data was read from the
// socket. In both cases something has gone wrong, because we expect
// that a chunk of data is there. We signal the lack of data by
// returning an empty packet.
if (data_len <= 0) {
return Pkt4Ptr();
}
InputBuffer buf(raw_buf, data_len);
#else
const size_t CONTROL_BUF_LEN = 512;
uint8_t msg_buf[IfaceMgr::RCVBUFSIZE];
uint8_t control_buf[CONTROL_BUF_LEN];
memset(&control_buf[0], 0, CONTROL_BUF_LEN);
// Initialize our message header structure.
struct msghdr m;
memset(&m, 0, sizeof(m));
struct iovec v;
v.iov_base = static_cast<void*>(msg_buf);
v.iov_len = IfaceMgr::RCVBUFSIZE;
m.msg_iov = &v;
m.msg_iovlen = 1;
// Getting the interface is a bit more involved.
//
// We set up some space for a "control message". We have
// previously asked the kernel to give us packet
// information (when we initialized the interface), so we
// should get the destination address from that.
m.msg_control = &control_buf[0];
m.msg_controllen = CONTROL_BUF_LEN;
int result = recvmsg(socket_info.sockfd_, &m, 0);
if (result < 0) {
isc_throw(SocketReadError, "Pkt4FilterLpf to receive UDP4 data");
}
InputBuffer buf(msg_buf, result);
#endif
// @todo: This is awkward way to solve the chicken and egg problem
// whereby we don't know the offset where DHCP data start in the
// received buffer when we create the packet object. In general case,
// the IP header has variable length. The information about its length
// is stored in one of its fields. Therefore, we have to decode the
// packet to get the offset of the DHCP data. The dummy object is
// created so as we can pass it to the functions which decode IP stack
// and find actual offset of the DHCP data.
// Once we find the offset we can create another Pkt4 object from
// the reminder of the input buffer and set the IP addresses and
// ports from the dummy packet. We should consider doing it
// in some more elegant way.
Pkt4Ptr dummy_pkt = Pkt4Ptr(new Pkt4(DHCPDISCOVER, 0));
// Decode ethernet, ip and udp headers.
decodeEthernetHeader(buf, dummy_pkt);
decodeIpUdpHeader(buf, dummy_pkt);
// Read the DHCP data.
std::vector<uint8_t> dhcp_buf;
buf.readVector(dhcp_buf, buf.getLength() - buf.getPosition());
// Decode DHCP data into the Pkt4 object.
Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(&dhcp_buf[0], dhcp_buf.size()));
// Set the appropriate packet members using data collected from
// the decoded headers.
pkt->setIndex(iface.getIndex());
pkt->setIface(iface.getName());
pkt->setLocalAddr(dummy_pkt->getLocalAddr());
pkt->setRemoteAddr(dummy_pkt->getRemoteAddr());
pkt->setLocalPort(dummy_pkt->getLocalPort());
pkt->setRemotePort(dummy_pkt->getRemotePort());
pkt->setLocalHWAddr(dummy_pkt->getLocalHWAddr());
pkt->setRemoteHWAddr(dummy_pkt->getRemoteHWAddr());
#ifdef SO_TIMESTAMP
struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
while (cmsg != NULL) {
if ((cmsg->cmsg_level == SOL_SOCKET) &&
(cmsg->cmsg_type == SCM_TIMESTAMP)) {
struct timeval cmsg_time;
memcpy(&cmsg_time, CMSG_DATA(cmsg), sizeof(cmsg_time));
pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, cmsg_time);
break;
}
cmsg = CMSG_NXTHDR(&m, cmsg);
}
#endif
// Set time packet was read from the buffer.
pkt->addPktEvent(PktEvent::BUFFER_READ);
return (pkt);
}
int
PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
OutputBuffer buf(14);
// Some interfaces may have no HW address - e.g. loopback interface.
// For these interfaces the HW address length is 0. If this is the case,
// then we will rely on the functions which construct the IP/UDP headers
// to provide a default HW addres. Otherwise, create the HW address
// object using the HW address of the interface.
if (iface.getMacLen() > 0) {
HWAddrPtr hwaddr(new HWAddr(iface.getMac(), iface.getMacLen(),
iface.getHWType()));
pkt->setLocalHWAddr(hwaddr);
}
// Ethernet frame header.
// Note that we don't validate whether HW addresses in 'pkt'
// are valid because they are checked by the function called.
writeEthernetHeader(pkt, buf);
// IP and UDP header
writeIpUdpHeader(pkt, buf);
// DHCPv4 message
buf.writeData(pkt->getBuffer().getData(), pkt->getBuffer().getLength());
sockaddr_ll sa;
memset(&sa, 0x0, sizeof(sa));
sa.sll_family = AF_PACKET;
sa.sll_ifindex = iface.getIndex();
sa.sll_protocol = htons(ETH_P_IP);
sa.sll_halen = 6;
pkt->addPktEvent(PktEvent::RESPONSE_SENT);
int result = sendto(sockfd, buf.getData(), buf.getLength(), 0,
reinterpret_cast<const struct sockaddr*>(&sa),
sizeof(sockaddr_ll));
if (result < 0) {
isc_throw(SocketWriteError, "failed to send DHCPv4 packet, errno="
<< errno << " (check errno.h)");
}
return (0);
}
} // end of isc::dhcp namespace
} // end of isc namespace
|