Kea 2.5.8
pkt_filter_inet.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2024 Internet Systems Consortium, Inc. ("ISC")
2//
3// This Source Code Form is subject to the terms of the Mozilla Public
4// License, v. 2.0. If a copy of the MPL was not distributed with this
5// file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7#include <config.h>
8#include <dhcp/iface_mgr.h>
9#include <dhcp/pkt4.h>
11#include <errno.h>
12#include <cstring>
13#include <fcntl.h>
14
15using namespace isc::asiolink;
16
17namespace isc {
18namespace dhcp {
19
20const size_t PktFilterInet::CONTROL_BUF_LEN = 512;
21
22bool
24#ifdef SO_TIMESTAMP
25 return (true);
26#else
27 return (false);
28#endif
29}
30
33 const isc::asiolink::IOAddress& addr,
34 const uint16_t port,
35 const bool receive_bcast,
36 const bool send_bcast) {
37 struct sockaddr_in addr4;
38 memset(&addr4, 0, sizeof(sockaddr));
39 addr4.sin_family = AF_INET;
40 addr4.sin_port = htons(port);
41
42 // If we are to receive broadcast messages we have to bind
43 // to "ANY" address.
44 if (receive_bcast && iface.flag_broadcast_) {
45 addr4.sin_addr.s_addr = INADDR_ANY;
46 } else {
47 addr4.sin_addr.s_addr = htonl(addr.toUint32());
48 }
49
50 int sock = socket(AF_INET, SOCK_DGRAM, 0);
51 if (sock < 0) {
52 isc_throw(SocketConfigError, "Failed to create UDP4 socket.");
53 }
54
55 // Set the close-on-exec flag.
56 if (fcntl(sock, F_SETFD, FD_CLOEXEC) < 0) {
57 close(sock);
58 isc_throw(SocketConfigError, "Failed to set close-on-exec flag"
59 << " on socket " << sock);
60 }
61
62#ifdef SO_TIMESTAMP
63 int enable = 1;
64 if (setsockopt(sock, SOL_SOCKET, SO_TIMESTAMP, &enable, sizeof(enable))) {
65 const char* errmsg = strerror(errno);
66 isc_throw(SocketConfigError, "Could not enable SO_TIMESTAMP for " << addr.toText()
67 << ", error: " << errmsg);
68 }
69#endif
70
71#ifdef SO_BINDTODEVICE
72 if (receive_bcast && iface.flag_broadcast_) {
73 // Bind to device so as we receive traffic on a specific interface.
74 if (setsockopt(sock, SOL_SOCKET, SO_BINDTODEVICE, iface.getName().c_str(),
75 iface.getName().length() + 1) < 0) {
76 close(sock);
77 isc_throw(SocketConfigError, "Failed to set SO_BINDTODEVICE option"
78 << " on socket " << sock);
79 }
80 }
81#endif
82
83 if (send_bcast && iface.flag_broadcast_) {
84 // Enable sending to broadcast address.
85 int flag = 1;
86 if (setsockopt(sock, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag)) < 0) {
87 close(sock);
88 isc_throw(SocketConfigError, "Failed to set SO_BROADCAST option"
89 << " on socket " << sock);
90 }
91 }
92
93 if (bind(sock, (struct sockaddr *)&addr4, sizeof(addr4)) < 0) {
94 close(sock);
95 isc_throw(SocketConfigError, "Failed to bind socket " << sock
96 << " to " << addr
97 << "/port=" << port);
98 }
99
100 // On Linux systems IP_PKTINFO socket option is supported. This
101 // option is used to retrieve destination address of the packet.
102#if defined (IP_PKTINFO) && defined (OS_LINUX)
103 int flag = 1;
104 if (setsockopt(sock, IPPROTO_IP, IP_PKTINFO, &flag, sizeof(flag)) != 0) {
105 close(sock);
106 isc_throw(SocketConfigError, "setsockopt: IP_PKTINFO: failed.");
107 }
108
109 // On BSD systems IP_RECVDSTADDR is used instead of IP_PKTINFO.
110#elif defined (IP_RECVDSTADDR) && defined (OS_BSD)
111 int flag = 1;
112 if (setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR, &flag, sizeof(flag)) != 0) {
113 close(sock);
114 isc_throw(SocketConfigError, "setsockopt: IP_RECVDSTADDR: failed.");
115 }
116#endif
117
118 SocketInfo sock_desc(addr, port, sock);
119 return (sock_desc);
120
121}
122
124PktFilterInet::receive(Iface& iface, const SocketInfo& socket_info) {
125 struct sockaddr_in from_addr;
126 uint8_t buf[IfaceMgr::RCVBUFSIZE];
127 uint8_t control_buf[CONTROL_BUF_LEN];
128
129 memset(&control_buf[0], 0, CONTROL_BUF_LEN);
130 memset(&from_addr, 0, sizeof(from_addr));
131
132 // Initialize our message header structure.
133 struct msghdr m;
134 memset(&m, 0, sizeof(m));
135
136 // Point so we can get the from address.
137 m.msg_name = &from_addr;
138 m.msg_namelen = sizeof(from_addr);
139
140 struct iovec v;
141 v.iov_base = static_cast<void*>(buf);
142 v.iov_len = IfaceMgr::RCVBUFSIZE;
143 m.msg_iov = &v;
144 m.msg_iovlen = 1;
145
146 // Getting the interface is a bit more involved.
147 //
148 // We set up some space for a "control message". We have
149 // previously asked the kernel to give us packet
150 // information (when we initialized the interface), so we
151 // should get the destination address from that.
152 m.msg_control = &control_buf[0];
153 m.msg_controllen = CONTROL_BUF_LEN;
154
155 int result = recvmsg(socket_info.sockfd_, &m, 0);
156 if (result < 0) {
157 isc_throw(SocketReadError, "failed to receive UDP4 data");
158 }
159
160 // We have all data let's create Pkt4 object.
161 Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf, result));
162
163 pkt->updateTimestamp();
164
165 unsigned int ifindex = iface.getIndex();
166
167 IOAddress from(htonl(from_addr.sin_addr.s_addr));
168 uint16_t from_port = htons(from_addr.sin_port);
169
170 // Set receiving interface based on information, which socket was used to
171 // receive data. OS-specific info (see os_receive4()) may be more reliable,
172 // so this value may be overwritten.
173 pkt->setIndex(ifindex);
174 pkt->setIface(iface.getName());
175 pkt->setRemoteAddr(from);
176 pkt->setRemotePort(from_port);
177 pkt->setLocalPort(socket_info.port_);
178
179// Linux systems support IP_PKTINFO option which is used to retrieve the
180// destination address of the received packet. On BSD systems IP_RECVDSTADDR
181// is used instead.
182#if defined (IP_PKTINFO) && defined (OS_LINUX)
183 struct in_pktinfo* pktinfo;
184 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
185
186 while (cmsg != NULL) {
187 if ((cmsg->cmsg_level == IPPROTO_IP) &&
188 (cmsg->cmsg_type == IP_PKTINFO)) {
189 pktinfo = reinterpret_cast<struct in_pktinfo*>(CMSG_DATA(cmsg));
190
191 pkt->setIndex(pktinfo->ipi_ifindex);
192 pkt->setLocalAddr(IOAddress(htonl(pktinfo->ipi_addr.s_addr)));
193
194 // This field is useful, when we are bound to unicast
195 // address e.g. 192.0.2.1 and the packet was sent to
196 // broadcast. This will return broadcast address, not
197 // the address we are bound to.
198
199 // XXX: Perhaps we should uncomment this:
200 // to_addr = pktinfo->ipi_spec_dst;
201#ifndef SO_TIMESTAMP
202 break;
203 }
204#else
205 } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
206 (cmsg->cmsg_type == SCM_TIMESTAMP)) {
207
208 struct timeval cmsg_time;
209 memcpy(&cmsg_time, CMSG_DATA(cmsg), sizeof(cmsg_time));
210 pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, cmsg_time);
211 }
212#endif
213
214 cmsg = CMSG_NXTHDR(&m, cmsg);
215 }
216
217#elif defined (IP_RECVDSTADDR) && defined (OS_BSD)
218 struct in_addr* to_addr;
219 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
220
221 while (cmsg != NULL) {
222 if ((cmsg->cmsg_level == IPPROTO_IP) &&
223 (cmsg->cmsg_type == IP_RECVDSTADDR)) {
224 to_addr = reinterpret_cast<struct in_addr*>(CMSG_DATA(cmsg));
225 pkt->setLocalAddr(IOAddress(htonl(to_addr->s_addr)));
226#ifndef SO_TIMESTAMP
227 break;
228 }
229#else
230 } else if ((cmsg->cmsg_level == SOL_SOCKET) &&
231 (cmsg->cmsg_type == SCM_TIMESTAMP)) {
232
233 struct timeval cmsg_time;
234 memcpy(&cmsg_time, CMSG_DATA(cmsg), sizeof(cmsg_time));
235 pkt->addPktEvent(PktEvent::SOCKET_RECEIVED, cmsg_time);
236 }
237#endif
238 cmsg = CMSG_NXTHDR(&m, cmsg);
239 }
240
241#endif
242 pkt->addPktEvent(PktEvent::BUFFER_READ);
243
244 return (pkt);
245}
246
247int
248PktFilterInet::send(const Iface&, uint16_t sockfd, const Pkt4Ptr& pkt) {
249 uint8_t control_buf[CONTROL_BUF_LEN];
250 memset(&control_buf[0], 0, CONTROL_BUF_LEN);
251
252 // Set the target address we're sending to.
253 sockaddr_in to;
254 memset(&to, 0, sizeof(to));
255 to.sin_family = AF_INET;
256 to.sin_port = htons(pkt->getRemotePort());
257 to.sin_addr.s_addr = htonl(pkt->getRemoteAddr().toUint32());
258
259 struct msghdr m;
260 // Initialize our message header structure.
261 memset(&m, 0, sizeof(m));
262 m.msg_name = &to;
263 m.msg_namelen = sizeof(to);
264
265 // Set the data buffer we're sending. (Using this wacky
266 // "scatter-gather" stuff... we only have a single chunk
267 // of data to send, so we declare a single vector entry.)
268 struct iovec v;
269 memset(&v, 0, sizeof(v));
270 // iov_base field is of void * type. We use it for packet
271 // transmission, so this buffer will not be modified.
272 v.iov_base = const_cast<void *>(pkt->getBuffer().getDataAsVoidPtr());
273 v.iov_len = pkt->getBuffer().getLength();
274 m.msg_iov = &v;
275 m.msg_iovlen = 1;
276
277// In the future the OS-specific code may be abstracted to a different
278// file but for now we keep it here because there is no code yet, which
279// is specific to non-Linux systems.
280#if defined (IP_PKTINFO) && defined (OS_LINUX)
281 // Setting the interface is a bit more involved.
282 //
283 // We have to create a "control message", and set that to
284 // define the IPv4 packet information. We set the source address
285 // to handle correctly interfaces with multiple addresses.
286 m.msg_control = &control_buf[0];
287 m.msg_controllen = CONTROL_BUF_LEN;
288 struct cmsghdr* cmsg = CMSG_FIRSTHDR(&m);
289 cmsg->cmsg_level = IPPROTO_IP;
290 cmsg->cmsg_type = IP_PKTINFO;
291 cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
292 struct in_pktinfo* pktinfo =(struct in_pktinfo *)CMSG_DATA(cmsg);
293 memset(pktinfo, 0, sizeof(struct in_pktinfo));
294
295 // In some cases the index of the outbound interface is not set. This
296 // is a matter of configuration. When the server is configured to
297 // determine the outbound interface based on routing information,
298 // the index is left unset (negative).
299 if (pkt->indexSet()) {
300 pktinfo->ipi_ifindex = pkt->getIndex();
301 }
302
303 // When the DHCP server is using routing to determine the outbound
304 // interface, the local address is also left unset.
305 if (!pkt->getLocalAddr().isV4Zero()) {
306 pktinfo->ipi_spec_dst.s_addr = htonl(pkt->getLocalAddr().toUint32());
307 }
308
309 m.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo));
310#endif
311
312 pkt->updateTimestamp();
313
314 pkt->addPktEvent(PktEvent::RESPONSE_SENT);
315
316 int result = sendmsg(sockfd, &m, 0);
317 if (result < 0) {
318 isc_throw(SocketWriteError, "pkt4 send failed: sendmsg() returned "
319 " with an error: " << strerror(errno));
320 }
321
322 return (0);
323}
324
325} // end of isc::dhcp namespace
326} // end of isc namespace
static const uint32_t RCVBUFSIZE
Packet reception buffer size.
Definition: iface_mgr.h:691
Represents a single network interface.
Definition: iface_mgr.h:118
std::string getName() const
Returns interface name.
Definition: iface_mgr.h:224
unsigned int getIndex() const
Returns interface index.
Definition: iface_mgr.h:219
bool flag_broadcast_
Flag specifies if selected interface is broadcast capable.
Definition: iface_mgr.h:454
Represents DHCPv4 packet.
Definition: pkt4.h:37
static const std::string SOCKET_RECEIVED
Event that marks when a packet is placed in the socket buffer by the kernel.
Definition: pkt.h:93
virtual bool isSocketReceivedTimeSupported() const
Check if the socket received time is supported.
virtual SocketInfo openSocket(Iface &iface, const isc::asiolink::IOAddress &addr, const uint16_t port, const bool receive_bcast, const bool send_bcast)
Open primary and fallback socket.
virtual Pkt4Ptr receive(Iface &iface, const SocketInfo &socket_info)
Receive packet over specified socket.
IfaceMgr exception thrown thrown when socket opening or configuration failed.
Definition: iface_mgr.h:63
IfaceMgr exception thrown thrown when error occurred during reading data from socket.
Definition: iface_mgr.h:71
IfaceMgr exception thrown thrown when error occurred during sending data through socket.
Definition: iface_mgr.h:79
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition: pkt4.h:555
Defines the logger used by the top-level component of kea-lfc.
Holds information about socket.
Definition: socket_info.h:19
int sockfd_
IPv4 or IPv6.
Definition: socket_info.h:26
uint16_t port_
bound address
Definition: socket_info.h:22