[kea-dev] [patch] Linux Packet Filter

Morten Brørup mb at smartsharesystems.com
Mon Aug 1 12:50:21 UTC 2022


Dear Kea developers,

Please find a patch for the Linux packet filter below, which provides the following modifications:

1. Feature: VLAN tagged packets are dropped. I think this addresses issues #1117 and #1738.

2. Optimization: The BPF program order has been reorganized to reduce the filter processing workload in the kernel.

3. Bugfix: The test for fragmented packets did not drop the first fragment. You might also want to fix this in pkt_filter_bpf.cc.

4. Bugfix: Drain the socket after the filter has been attached. You might also want to fix this in pkt_filter_bpf.cc.

5. Docfix: A @todo about filtering packets sent to the interface address was removed. This should have been removed with commit 7139039105680154602b5dd9eaf319c279be11c4.


PS: I liked your presentation at DKNOG12, Tomek.


Developer's Certificate of Origin 1.1

By making a contribution to this project, I certify that:

(a) The contribution was created in whole or in part by me and I
    have the right to submit it under the open source license
    indicated in the file; or

(b) The contribution is based upon previous work that, to the best
    of my knowledge, is covered under an appropriate open source
    license and I have the right under that license to submit that
    work with modifications, whether created in whole or in part
    by me, under the same open source license (unless I am
    permitted to submit under a different license), as indicated
    in the file; or

(c) The contribution was provided directly to me by some other
    person who certified (a), (b) or (c) and I have not modified
    it.

(d) I understand and agree that this project and the contribution
    are public and that a record of the contribution (including all
    personal information I submit with it, including my sign-off) is
    maintained indefinitely and may be redistributed consistent with
    this project or the open source license(s) involved.

Signed-off-by: Morten Brørup <mb at smartsharesystems.com>

(Email resent after signing up to the kea-dev mailing list.)

---

$ diff -u pkt_filter_lpf.cc.orig pkt_filter_lpf.cc
--- pkt_filter_lpf.cc.orig      2022-08-01 10:51:01.522404231 +0200
+++ pkt_filter_lpf.cc   2022-08-01 14:14:40.087803577 +0200
@@ -1,4 +1,5 @@
 // Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2022 SmartShare Systems
 //
 // 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
@@ -40,61 +41,36 @@
 /// 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.
+/// Most packets are not DHCP packets.  By designing the filter to detect
+/// non-DHCP packets as early as possible, we reduce the kernel's BPF
+/// processing workload.
 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
+    // prog.bf_insns[2].k in the network byte order.
+    // #0
     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
+    // #1
     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),
+    // The address is actually set in PktFilterBPF::openSocket().
+    // N.B. The code in that method assumes that this instruction is at
+    // offset 2 in the program.  If this is changed, openSocket() must be
+    // updated.
+    // #2 - Will be modified by PktFilterBPF::openSocket().
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x00000000, 0, 9),

     // 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
+    // #3
     BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, ETHERNET_HEADER_LEN),

     // Make sure it's to the right port.  The following instruction
@@ -102,22 +78,65 @@
     // 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
+    // #4
     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
+    // offset 5 in the program.  If this is changed, openSocket() must be
     // updated.
+    // #5 - Will be modified by PktFilterBPF::openSocket().
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 6),
+
+    // Make sure this is an IP packet: check the half-word (two bytes)
+    // at offset 12 in the packet (the Ethernet packet type).
+    // #6
+    BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_PACKET_TYPE_OFFSET),
+    // #7
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 4),
+
+    // 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.
+    // #8
+    BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+             ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
+    // #9
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 2),
+
+    // Make sure this isn't a fragment by checking that the more fragments
+    // bit and the fragment offset field in the IP header are zero.
+    // These fields are the least-significant 14 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.
+    // #10
+    BPF_STMT(BPF_LD + BPF_H + BPF_ABS, ETHERNET_HEADER_LEN + IP_FLAGS_OFFSET),
+    // If any of the bits are set, proceed to drop the packet; otherwise, skip
+    // to proceed.
     // #11
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, DHCP4_SERVER_PORT, 0, 1),
+    BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x3fff, 0, 1),

-    // If we passed all the tests, ask for the whole packet.
+    // We failed one of the tests above, so drop the packet.
     // #12
+    BPF_STMT(BPF_RET + BPF_K, 0),
+
+    // We passed the tests above, so proceed.
+#if defined(SKF_AD_VLAN_TAG_PRESENT)
+    // Make sure the packet is not VLAN tagged.
+    // #13
+    BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
+             SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT),
+    // If no VLAN tag is present, proceed; otherwise, skip to drop the packet.
+    // #14
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1),
+#endif
+
+    // If we passed all the tests, ask for the whole packet.
+    // #15 / #13
     BPF_STMT(BPF_RET + BPF_K, (u_int)-1),

     // Otherwise, drop it.
-    // #13
+    // #16 / #14
     BPF_STMT(BPF_RET + BPF_K, 0),
 };

@@ -133,6 +152,8 @@
                          const isc::asiolink::IOAddress& addr,
                          const uint16_t port, const bool,
                          const bool) {
+    uint8_t raw_buf[IfaceMgr::RCVBUFSIZE];
+    int datalen;

     // Open fallback socket first. If it fails, it will give us an indication
     // that there is another service (perhaps DHCP server) running.
@@ -167,10 +188,10 @@
     // 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();
+    dhcp_sock_filter[2].k = addr.toUint32();

     // Override the default port value.
-    dhcp_sock_filter[11].k = port;
+    dhcp_sock_filter[5].k = port;
     // Apply the filter.
     if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
                    sizeof(filter_program)) < 0) {
@@ -209,6 +230,12 @@
                   << iface.getName() << "', reason: " << errmsg);
     }

+    // Non-DHCP packets may have been received before the filter was attached,
+    // so drain the socket.
+    do {
+        datalen = recv(sock, raw_buf, sizeof(raw_buf), 0);
+    } while (datalen > 0);
+
     return (SocketInfo(addr, port, sock, fallback));

 }


More information about the kea-dev mailing list