Browse Source

[master] Merge branch 'trac3547'

Marcin Siodelski 10 years ago
parent
commit
803f1f0f14

+ 80 - 11
src/lib/dhcp/pkt_filter_bpf.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -50,36 +50,64 @@ const unsigned int BPF_LOCAL_LOOPBACK_HEADER_LEN = 4;
 ///   2 bytes  UDP destination port
 ///   4 bytes  Rest of UDP header
 ///
+/// Each instruction is preceded with the comment 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 bpf_insn ethernet_ip_udp_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 8
+    // 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),
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+    // #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),
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+    // #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),
-    BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+    // #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
@@ -87,18 +115,22 @@ struct bpf_insn ethernet_ip_udp_filter [] = {
     // 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 8 in the program.  If this is changed, openSocket() must be
+    // 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),
 };
 
@@ -107,33 +139,61 @@ struct bpf_insn ethernet_ip_udp_filter [] = {
 /// contain the regular link-layer header, but rather a 4-byte long pseudo
 /// header containing the address family. The reminder of the packet contains
 /// IP header, UDP header and a DHCP message.
+///
+/// Each instruction is preceded with the comment giving the instruction
+/// number within a BPF program, in the following format: #123.
 struct bpf_insn loopback_ip_udp_filter [] = {
     // Make sure this is an IP packet. The pseudo header comprises a 4-byte
     // long value identifying the address family, which should be set to
     // AF_INET. The default value used here (0xFFFFFFFF) must be overriden
     // with htonl(AF_INET) from within the openSocket function.
+    // #0
     BPF_STMT(BPF_LD + BPF_W + BPF_ABS, 0),
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xFFFFFFFF, 0, 8),
+    // #1
+    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0xFFFFFFFF, 0, 11),
 
     // Make sure it's a UDP packet.  The IP protocol is at offset
     // 9 in the IP header so, adding the pseudo header size 4 bytes
     // gives an absolute byte offset in the packet of 13.
+    // #2
     BPF_STMT(BPF_LD + BPF_B + BPF_ABS,
              BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+    // #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 10 (6 + size of
     // pseudo header) is loaded and an appropriate mask applied.
+    // #4
     BPF_STMT(BPF_LD + BPF_H + BPF_ABS,
              BPF_LOCAL_LOOPBACK_HEADER_LEN + IP_FLAGS_OFFSET),
-    BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+    // #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,
+             BPF_LOCAL_LOOPBACK_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 4) loads the IP header length.
+    // #9
     BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, BPF_LOCAL_LOOPBACK_HEADER_LEN),
 
     // Make sure it's to the right port.  The following instruction
@@ -141,19 +201,23 @@ struct bpf_insn loopback_ip_udp_filter [] = {
     // offset to locate the correct byte.  The given offset of 6
     // comprises the length of the pseudo header (4) plus the offset
     // of the UDP destination port (2) within the UDP header.
+    // #10
     BPF_STMT(BPF_LD + BPF_H + BPF_IND,
              BPF_LOCAL_LOOPBACK_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 8 in the program.  If this is changed, openSocket() must be
+    // 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),
 };
 
@@ -273,8 +337,13 @@ PktFilterBPF::openSocket(Iface& iface,
         prog.bf_len = sizeof(ethernet_ip_udp_filter) / sizeof(struct bpf_insn);
     }
 
+    // Configure the BPF 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.
+    prog.bf_insns[8].k = static_cast<uint32_t>(addr);
+
     // Configure the BPF program to receive packets on the specified port.
-    prog.bf_insns[8].k = port;
+    prog.bf_insns[11].k = port;
 
     // Actually set the filter program for the device.
     if (ioctl(sock, BIOCSETF, &prog) < 0) {

+ 48 - 9
src/lib/dhcp/pkt_filter_lpf.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -44,35 +44,64 @@ using namespace isc::dhcp;
 ///   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 8
+    // 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),
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 8),
+    // #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.
-    BPF_STMT(BPF_LD + BPF_B + BPF_ABS, ETHERNET_HEADER_LEN + IP_PROTO_TYPE_OFFSET),
-    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6),
+    // #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),
-    BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0),
+    // #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
@@ -80,18 +109,22 @@ struct sock_filter dhcp_sock_filter [] = {
     // 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 PktFilterLPF::openSocket().
+    // but the action port is actually set in PktFilterBPF::openSocket().
     // N.B. The code in that method assumes that this instruction is at
-    // offset 8 in the program.  If this is changed, openSocket() must be
+    // 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),
 };
 
@@ -129,8 +162,14 @@ PktFilterLPF::openSocket(Iface& iface,
 
     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 = static_cast<uint32_t>(addr);
+
     // Override the default port value.
-    dhcp_sock_filter[8].k = port;
+    dhcp_sock_filter[11].k = port;
     // Apply the filter.
     if (setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &filter_program,
                    sizeof(filter_program)) < 0) {

+ 2 - 0
src/lib/dhcp/protocol_util.h

@@ -56,6 +56,8 @@ static const size_t IP_FLAGS_OFFSET = 6;
 static const size_t IP_PROTO_TYPE_OFFSET = 9;
 /// Offset of source address in the IPv4 header.
 static const size_t IP_SRC_ADDR_OFFSET = 12;
+/// Offset of destination address in the IPv4 header.
+static const size_t IP_DEST_ADDR_OFFSET = 16;
 
 /// UDP header length.
 static const size_t UDP_HEADER_LEN = 8;

+ 38 - 1
src/lib/dhcp/tests/pkt_filter_bpf_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014, 2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -202,4 +202,41 @@ TEST_F(PktFilterBPFTest, DISABLED_receive) {
     testRcvdMessage(rcvd_pkt);
 }
 
+// This test verifies that if the packet is received over the raw
+// socket and its destination address doesn't match the address
+// to which the socket is "bound", the packet is dropped.
+TEST_F(PktFilterBPFTest, DISABLED_filterOutUnicast) {
+
+    // Packet will be received over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    iface.flag_loopback_ = true;
+    IOAddress addr("127.0.0.1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterBPF pkt_filter;
+    // Open socket. We don't check that the socket has appropriate
+    // options and family set because we have checked that in the
+    // openSocket test already.
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    ASSERT_GE(sock_info_.sockfd_, 0);
+
+    // The message sent to the local loopback interface will have an
+    // invalid (non-existing) destination address. The socket should
+    // drop this packet.
+    sendMessage(IOAddress("128.0.0.1"));
+
+    // Perform select on the socket to make sure that the packet has
+    // been dropped.
+
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(sock_info_.sockfd_, &readfds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+    int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+    ASSERT_LE(result, 0);
+}
+
 } // anonymous namespace

+ 38 - 1
src/lib/dhcp/tests/pkt_filter_lpf_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013, 2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -189,4 +189,41 @@ TEST_F(PktFilterLPFTest, DISABLED_receive) {
     testRcvdMessage(rcvd_pkt);
 }
 
+// This test verifies that if the packet is received over the raw
+// socket and its destination address doesn't match the address
+// to which the socket is "bound", the packet is dropped.
+TEST_F(PktFilterLPFTest, DISABLED_filterOutUnicast) {
+
+    // Packet will be received over loopback interface.
+    Iface iface(ifname_, ifindex_);
+    iface.flag_loopback_ = true;
+    IOAddress addr("127.0.0.1");
+
+    // Create an instance of the class which we are testing.
+    PktFilterLPF pkt_filter;
+    // Open socket. We don't check that the socket has appropriate
+    // options and family set because we have checked that in the
+    // openSocket test already.
+    sock_info_ = pkt_filter.openSocket(iface, addr, PORT, false, false);
+    ASSERT_GE(sock_info_.sockfd_, 0);
+
+    // The message sent to the local loopback interface will have an
+    // invalid (non-existing) destination address. The socket should
+    // drop this packet.
+    sendMessage(IOAddress("128.0.0.1"));
+
+    // Perform select on the socket to make sure that the packet has
+    // been dropped.
+
+    fd_set readfds;
+    FD_ZERO(&readfds);
+    FD_SET(sock_info_.sockfd_, &readfds);
+
+    struct timeval timeout;
+    timeout.tv_sec = 1;
+    timeout.tv_usec = 0;
+    int result = select(sock_info_.sockfd_ + 1, &readfds, NULL, NULL, &timeout);
+    ASSERT_LE(result, 0);
+}
+
 } // anonymous namespace

+ 3 - 2
src/lib/dhcp/tests/pkt_filter_test_utils.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -91,7 +91,7 @@ PktFilterTest::loInit() {
 }
 
 void
-PktFilterTest::sendMessage() {
+PktFilterTest::sendMessage(const IOAddress& dest) {
 
     // Packet will be sent over loopback interface.
     Iface iface(ifname_, ifindex_);
@@ -112,6 +112,7 @@ PktFilterTest::sendMessage() {
     memset(&dest_addr4, 0, sizeof(sockaddr));
     dest_addr4.sin_family = AF_INET;
     dest_addr4.sin_port = htons(port_);
+    dest_addr4.sin_addr.s_addr = htonl(dest);
     ASSERT_EQ(sendto(send_msg_sock_, test_message_->getBuffer().getData(),
                      test_message_->getBuffer().getLength(), 0,
                      reinterpret_cast<struct sockaddr*>(&dest_addr4),

+ 5 - 2
src/lib/dhcp/tests/pkt_filter_test_utils.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2014 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2015 Internet Systems Consortium, Inc. ("ISC")
 //
 // Permission to use, copy, modify, and/or distribute this software for any
 // purpose with or without fee is hereby granted, provided that the above
@@ -67,7 +67,10 @@ public:
     /// is closed automatically in the destructor. If the function succeeds to
     /// send a DHCPv4 message, the socket is closed so as the function can be
     /// called again within the same test.
-    void sendMessage();
+    ///
+    /// @param dest Destination address for the packet.
+    void sendMessage(const asiolink::IOAddress& dest =
+                     asiolink::IOAddress("127.0.0.1"));
 
     /// @brief Test that the datagram socket is opened correctly.
     ///