Parcourir la source

[2902] Implemented utility functions encoding UDP packets.

Marcin Siodelski il y a 12 ans
Parent
commit
d8595ab6bd

+ 8 - 4
src/lib/dhcp/pkt_filter_lpf.cc

@@ -54,9 +54,9 @@ PktFilterLPF::openSocket(const Iface& iface, const isc::asiolink::IOAddress&,
 }
 
 Pkt4Ptr
-PktFilterLPF::receive(const Iface&, const SocketInfo& socket_info) {
+PktFilterLPF::receive(const Iface& iface, const SocketInfo& socket_info) {
     // @todo: implement this function
-    unsigned char buf[1536];
+    uint8_t buf[IfaceMgr::RCVBUFSIZE];
     int data_len = read(socket_info.sockfd_, buf, sizeof(buf));
     if (data_len <= 0) {
         return Pkt4Ptr();
@@ -66,6 +66,10 @@ PktFilterLPF::receive(const Iface&, const SocketInfo& socket_info) {
     int data_offset = 42;
     Pkt4Ptr pkt = Pkt4Ptr(new Pkt4(buf + data_offset,
                                    data_len - data_offset));
+
+    pkt->setIndex(iface.getIndex());
+    pkt->setIface(iface.getName());
+
     return (pkt);
 }
 
@@ -99,9 +103,9 @@ PktFilterLPF::send(const Iface& iface, uint16_t sockfd, const Pkt4Ptr& pkt) {
     if (result < 0) {
         isc_throw(SocketWriteError, "pkt4 send failed");
     }
-    
+
     return (0);
-    
+
 }
 
 

+ 52 - 39
src/lib/dhcp/protocol_util.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013 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
@@ -13,16 +13,14 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <protocol_util.h>
-
 #include <netinet/ip.h>
-#include <netinet/udp.h>
 
 namespace isc {
 namespace dhcp {
 
 void
 writeEthernetHeader(const uint8_t* src_hw_addr, const uint8_t* dest_hw_addr,
-                         util::OutputBuffer& out_buf) {
+                    util::OutputBuffer& out_buf) {
     // Write destination and source address.
     out_buf.writeData(dest_hw_addr, HWAddr::ETHERNET_HWADDR_LEN);
     out_buf.writeData(src_hw_addr, HWAddr::ETHERNET_HWADDR_LEN);
@@ -33,38 +31,58 @@ writeEthernetHeader(const uint8_t* src_hw_addr, const uint8_t* dest_hw_addr,
 void
 writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf) {
 
-    struct ip ip_hdr;
-    memset(&ip_hdr, 0, sizeof(ip_hdr));
-    ip_hdr.ip_hl = (ip_hdr.ip_hl | 5) & 0xF;
-    ip_hdr.ip_v = (ip_hdr.ip_v | 4) & 0xF;
-    ip_hdr.ip_tos = IPTOS_LOWDELAY;
-    ip_hdr.ip_len = htons(sizeof(ip) + sizeof(udphdr) +
-                          pkt->getBuffer().getLength());
-    ip_hdr.ip_id = 0;
-    ip_hdr.ip_off = 0;
-    ip_hdr.ip_ttl = 128;
-    ip_hdr.ip_p = IPPROTO_UDP;
-    ip_hdr.ip_src.s_addr = htonl(pkt->getLocalAddr());
-    ip_hdr.ip_dst.s_addr = htonl(pkt->getRemoteAddr());
-    ip_hdr.ip_sum =
-        wrapChecksum(calculateChecksum(reinterpret_cast<const char*>(&ip_hdr),
-                                       sizeof(ip_hdr)));
-
-    out_buf.writeData(static_cast<void*>(&ip_hdr), sizeof(ip_hdr));
-
-    struct udphdr udp_hdr;
-    memset(&udp_hdr, 0, sizeof(udp_hdr));
-    udp_hdr.source = htons(pkt->getLocalPort());
-    udp_hdr.dest = htons(pkt->getRemotePort());
-    udp_hdr.len = htons(sizeof(udp_hdr) + pkt->getBuffer().getLength());
-    udp_hdr.check = 0;
-
-    out_buf.writeData(static_cast<void*>(&udp_hdr), sizeof(udp_hdr));
-
+    out_buf.writeUint8(0x45); // IP version 4, IP header length 5
+    out_buf.writeUint8(IPTOS_LOWDELAY); // DSCP and ECN
+    out_buf.writeUint16(28 + pkt->getBuffer().getLength()); // Total length.
+    out_buf.writeUint16(0); // Identification
+    out_buf.writeUint16(0x4000); // Disable fragmentation.
+    out_buf.writeUint8(128); // TTL
+    out_buf.writeUint8(IPPROTO_UDP); // Protocol UDP.
+    out_buf.writeUint16(0); // Temporarily set checksum to 0.
+    out_buf.writeUint32(pkt->getLocalAddr()); // Source address.
+    out_buf.writeUint32(pkt->getRemoteAddr()); // Destination address.
+
+    // Calculate pseudo header checksum. It will be necessary to compute
+    // UDP checksum.
+    // Get the UDP length. This includes udp header's and data length.
+    uint32_t udp_len = 8 + pkt->getBuffer().getLength();
+    // The magic number "8" indicates the offset where the source address
+    // is stored in the buffer. This offset is counted here from the
+    // current tail of the buffer. Starting from this offset we calculate
+    // the checksum using 8 following bytes of data. This will include
+    // 4 bytes of source address and 4 bytes of destination address.
+    // The IPPROTO_UDP and udp_len are also added up to the checksum.
+    uint16_t pseudo_hdr_checksum =
+        calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 8,
+                     8, IPPROTO_UDP + udp_len);
+
+    // Calculate IP header checksum.
+    uint16_t ip_checksum = ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData())
+                                         + out_buf.getLength() - 20, 20);
+    // Write checksum in the IP header. The offset of the checksum is 10 bytes
+    // back from the tail of the current buffer.
+    out_buf.writeUint16At(ip_checksum, out_buf.getLength() - 10);
+
+    // Start UDP header.
+    out_buf.writeUint16(pkt->getLocalPort()); // Source port.
+    out_buf.writeUint16(pkt->getRemotePort()); // Destination port.
+    out_buf.writeUint16(udp_len); // Length of the header and data.
+
+    // Checksum is calculated from the contents of UDP header, data and pseudo ip header.
+    // The magic number "6" indicates that the UDP header starts at offset 6 from the
+    // tail of the current buffer. These 6 bytes contain source and destination port
+    // as well as the length of the header.
+    uint16_t udp_checksum =
+        ~calcChecksum(static_cast<const uint8_t*>(out_buf.getData()) + out_buf.getLength() - 6, 6,
+                      calcChecksum(static_cast<const uint8_t*>(pkt->getBuffer().getData()),
+                                   pkt->getBuffer().getLength(),
+                                   pseudo_hdr_checksum));
+    // Write UDP checksum.
+    out_buf.writeUint16(udp_checksum);
 }
 
 uint16_t
-calculateChecksum(const char* buf, const uint32_t buf_size, uint32_t sum) {
+calcChecksum(const uint8_t* buf, const uint32_t buf_size, uint32_t sum) {
     uint32_t i;
     for (i = 0; i < (buf_size & ~1U); i += 2) {
         uint16_t chunk = buf[i] << 8 | buf[i+1];
@@ -73,7 +91,7 @@ calculateChecksum(const char* buf, const uint32_t buf_size, uint32_t sum) {
             sum -= 0xFFFF;
         }
     }
-
+    // If one byte has left, we also need to add it to the checksum.
     if (i < buf_size) {
         sum += buf[i] << 8;
         if (sum > 0xFFFF) {
@@ -85,10 +103,5 @@ calculateChecksum(const char* buf, const uint32_t buf_size, uint32_t sum) {
 
 }
 
-uint16_t
-wrapChecksum(uint16_t sum) {
-    return (htons(~sum));
-}
-
 }
 }

+ 12 - 10
src/lib/dhcp/protocol_util.h

@@ -45,20 +45,22 @@ void writeIpUdpHeader(const Pkt4Ptr& pkt, util::OutputBuffer& out_buf);
 
 /// @brief Calculates checksum for provided buffer
 ///
+/// This function returns the sum of 16-bit values from the provided
+/// buffer. If the third parameter is specified, it indicates the
+/// initial checksum value. This parameter can be a result of
+/// calcChecksum function's invocation on different data buffer.
+/// The IP or UDP checksum value is a complement of the result returned
+/// by this function. However, this function does not compute complement
+/// of the summed values. It must be calculated outside of this function
+/// before writing the value to the packet buffer.
+///
 /// @param buf buffer for which the checksum is calculated.
 /// @param buf_size size of the buffer for which checksum is calculated.
-/// @param sum initial checksum value.
+/// @param sum initial checksum value, other values will be added to it.
 ///
 /// @return calculated checksum.
-uint16_t calculateChecksum(const char* buf, const uint32_t buf_size,
-                           uint32_t sum = 0);
-
-/// @brief Wraps the calculated checksum and stores it in network byte order.
-///
-/// @param sum calculated checksum
-///
-/// @return wrapped checksum value.
-uint16_t wrapChecksum(uint16_t sum);
+uint16_t calcChecksum(const uint8_t* buf, const uint32_t buf_size,
+                      uint32_t sum = 0);
 
 }
 }

+ 1 - 0
src/lib/dhcp/tests/Makefile.am

@@ -43,6 +43,7 @@ libdhcp___unittests_SOURCES += option_unittest.cc
 libdhcp___unittests_SOURCES += option_space_unittest.cc
 libdhcp___unittests_SOURCES += pkt4_unittest.cc
 libdhcp___unittests_SOURCES += pkt6_unittest.cc
+libdhcp___unittests_SOURCES += protocol_util_unittest.cc
 libdhcp___unittests_SOURCES += duid_unittest.cc
 
 libdhcp___unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)

+ 237 - 0
src/lib/dhcp/tests/protocol_util_unittest.cc

@@ -0,0 +1,237 @@
+// Copyright (C) 2013 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
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+#include <asiolink/io_address.h>
+#include <dhcp/protocol_util.h>
+#include <util/buffer.h>
+
+#include <gtest/gtest.h>
+
+#include <netinet/ip.h>
+
+using namespace isc;
+using namespace isc::asiolink;
+using namespace isc::dhcp;
+using namespace isc::util;
+
+namespace {
+
+    /*/// @brief OptionCustomTest test class.
+class OptionCustomTest : public ::testing::Test {
+public:
+};*/
+
+/// The purpose of this test is to verify that the IP header checksum
+/// is calculated correctly.
+TEST(ProtocolUtilTest, checksum) {
+    // IPv4 header to be used to calculate checksum.
+    const uint8_t hdr[] = {
+        0x45,                      // IP version and header length
+        0x00,                      // TOS
+        0x00, 0x3c,                // Total length of the IP packet.
+        0x1c, 0x46,                // Identification field.
+        0x40, 0x00,                // Fragmentation.
+        0x40,                      // TTL
+        0x06,                      // Protocol
+        0x00, 0x00,                // Checksum (reset to 0x00).
+        0xac, 0x10, 0x0a, 0x63,    // Source IP address.
+        0xac, 0x10, 0x0a, 0x0c     // Destination IP address.
+    };
+    // Calculate size of the header array.
+    const uint32_t hdr_size = sizeof(hdr) / sizeof(hdr[0]);
+    // Get the actual checksum.
+    uint16_t chksum = ~calcChecksum(hdr, hdr_size);
+    // The 0xb1e6 value has been calculated by other means.
+    EXPECT_EQ(0xb1e6, chksum);
+    // Tested function may also take the initial value of the sum.
+    // Let's set it to 2 and see whether it is included in the
+    // calculation.
+    chksum = ~calcChecksum(hdr, hdr_size, 2);
+    // The checkum value should change.
+    EXPECT_EQ(0xb1e4, chksum);
+}
+
+/// The purpose of this test is to verify that the ethernet
+/// header is correctly constructed from the destination and
+/// hardware addresses.
+TEST(ProtocolUtilTest, writeEthernetHeader) {
+    // Source HW address, 6 bytes.
+    const uint8_t src_hw_addr[6] = {
+        0x10, 0x11, 0x12, 0x13, 0x14, 0x15
+    };
+    // Destination HW address, 6 bytes.
+    const uint8_t dest_hw_addr[6] = {
+        0x20, 0x31, 0x42, 0x53, 0x64, 0x75
+    };
+    // Create output buffer.
+    OutputBuffer buf(1);
+
+    // Construct the ethernet header using HW addresses.
+    writeEthernetHeader(src_hw_addr, dest_hw_addr, buf);
+
+    // The resulting ethernet header consists of destination
+    // and src HW address (each 6 bytes long) and two bytes
+    // of encapsulated protocol type.
+    ASSERT_EQ(14, buf.getLength());
+
+    // Verify that first 6 bytes comprise valid destination
+    // HW address. Instead of using memory comparison functions
+    // we check bytes one-by-one. In case of mismatch we will
+    // get exact values that are mismatched. If memcmp was used
+    // the error message would not indicate the values of
+    // mismatched bytes.
+    for (int i = 0; i < 6; ++i) {
+        EXPECT_EQ(dest_hw_addr[i], buf[i]);
+    }
+    // Verify that following 6 bytes comprise the valid source
+    // HW address.
+    for (int i = 0; i < 6; ++i) {
+        EXPECT_EQ(src_hw_addr[i], buf[i + 6]);
+    }
+
+    // The last two bytes comprise the encapsulated protocol type.
+    // We expect IPv4 protocol type which is specified by 0x0800.
+    EXPECT_EQ(0x08, buf[12]);
+    EXPECT_EQ(0x0, buf[13]);
+}
+
+TEST(ProtocolUtilTest, writeIpUdpHeader) {
+    // Create DHCPv4 packet. Some values held by this object are
+    // used by the utility function under test to figure out the
+    // contents of the IP and UDP headers, e.g. source and
+    // destination IP address or port number.
+    Pkt4Ptr pkt(new Pkt4(DHCPOFFER, 0));
+    ASSERT_TRUE(pkt);
+
+    // Set local and remote address and port.
+    pkt->setLocalAddr(IOAddress("192.0.2.1"));
+    pkt->setRemoteAddr(IOAddress("192.0.2.111"));
+    pkt->setLocalPort(DHCP4_SERVER_PORT);
+    pkt->setRemotePort(DHCP4_CLIENT_PORT);
+
+    // Pack the contents of the packet.
+    ASSERT_NO_THROW(pkt->pack());
+
+    // Create output buffer. The headers will be written to it.
+    OutputBuffer buf(1);
+    // Write some dummy data to the buffer. We will check that the
+    // function under test appends to this data, not overrides it.
+    buf.writeUint16(0x0102);
+
+    // Write both IP and UDP headers.
+    writeIpUdpHeader(pkt, buf);
+
+    // The resulting size of the buffer must be 30. The 28 bytes are
+    // consumed by the IP and UDP headers. The other 2 bytes are dummy
+    // data at the beginning of the buffer.
+    ASSERT_EQ(30, buf.getLength());
+
+    // Make sure that the existing data in the buffer was not corrupted
+    // by the function under test.
+    EXPECT_EQ(0x01, buf[0]);
+    EXPECT_EQ(0x02, buf[1]);
+
+    // Copy the contents of the buffer to InputBuffer object. This object
+    // exposes convenient functions for reading.
+    InputBuffer in_buf(buf.getData(), buf.getLength());
+
+    // Check dummy data.
+    uint16_t dummy_data = in_buf.readUint16();
+    EXPECT_EQ(0x0102, dummy_data);
+
+    // The IP version and IHL are stored in the same octet (4 bits each).
+    uint8_t ver_len = in_buf.readUint8();
+    // The most significant bits define IP version.
+    uint8_t ip_ver = ver_len >> 4;
+    EXPECT_EQ(4, ip_ver);
+    // The least significant bits define header length (in 32-bits chunks).
+    uint8_t ip_len = ver_len & 0x0F;
+    EXPECT_EQ(5, ip_len);
+
+    // Get Differentiated Services Codepoint and Explicit Congestion
+    // Notification field.
+    uint8_t dscp_ecn = in_buf.readUint8();
+    EXPECT_EQ(IPTOS_LOWDELAY, dscp_ecn);
+
+    // Total length of IP packet. Includes UDP header and payload.
+    uint16_t total_len = in_buf.readUint16();
+    EXPECT_EQ(28 + pkt->getBuffer().getLength(), total_len);
+
+    // Identification field.
+    uint16_t ident = in_buf.readUint16();
+    EXPECT_EQ(0, ident);
+
+    // Fragmentation.
+    uint16_t fragment = in_buf.readUint16();
+    // Setting second most significant bit means no fragmentation.
+    EXPECT_EQ(0x4000, fragment);
+
+    // Get TTL
+    uint8_t ttl = in_buf.readUint8();
+    // Expect non-zero TTL.
+    EXPECT_GE(ttl, 1);
+
+    // Protocol type is UDP.
+    uint8_t proto = in_buf.readUint8();
+    EXPECT_EQ(IPPROTO_UDP, proto);
+
+    // Check that the checksum is correct. The reference checksum value
+    // has been calculated manually.
+    uint16_t ip_checksum = in_buf.readUint16();
+    EXPECT_EQ(0x755c, ip_checksum);
+
+    // Validate source address.
+    // Initializing it to IPv6 address guarantees that it is not initialized
+    // to the value that we expect to be read from a header since the value
+    // read from a header will be IPv4.
+    IOAddress src_addr("::1");
+    // Read src address as an array of bytes because it is easely convertible
+    // to IOAddress object.
+    uint8_t src_addr_data[4];
+    ASSERT_NO_THROW(
+        in_buf.readData(src_addr_data, 4);
+        src_addr = IOAddress::fromBytes(AF_INET, src_addr_data);
+    );
+    EXPECT_EQ(IOAddress("192.0.2.1").toText(), src_addr.toText());
+
+    // Validate destination address.
+    IOAddress dest_addr("::1");
+    uint8_t dest_addr_data[4];
+    ASSERT_NO_THROW(
+        in_buf.readData(dest_addr_data, 4);
+        dest_addr = IOAddress::fromBytes(AF_INET, dest_addr_data);
+    );
+    EXPECT_EQ(IOAddress("192.0.2.111").toText(), dest_addr.toText());
+
+    // UDP header starts here.
+
+    // Check source port.
+    uint16_t src_port = in_buf.readUint16();
+    EXPECT_EQ(pkt->getLocalPort(), src_port);
+
+    // Check destination port.
+    uint16_t dest_port = in_buf.readUint16();
+    EXPECT_EQ(pkt->getRemotePort(), dest_port);
+
+    // UDP header and data length.
+    uint16_t udp_len = in_buf.readUint16();
+    EXPECT_EQ(8 + pkt->getBuffer().getLength(), udp_len);
+
+    // Verify UDP checksum. The reference checksum has been calculated manually.
+    uint16_t udp_checksum = in_buf.readUint16();
+    EXPECT_EQ(0x8817, udp_checksum);
+}
+
+} // anonymous namespace