Commit f202ec60 authored by Thomas Markwalder's avatar Thomas Markwalder

[5551] kea-dhcp4 now attempts to process packets with truncated VIVSO

src/lib/dhcp/option.h
    SkipRemainingOptionsError - new error to signal
    that unpacking skipped options
src/lib/dhcp/option_vendor.cc
    OptionVendor::unpack() - modified to throw
    SkipRemainingOptions on truncated length

src/lib/dhcp/option_definition.cc
    OptionDefinition::optionFactory()
    Added catch-rethrow of SkipRemainginOptionsError

src/bin/dhcp4/dhcp4_messages.mes
    Added DHCP4_PACKET_OPTIONS_SKIPPED log message

src/bin/dhcp4/dhcp4_srv.cc
    Dhcpv4Srv::processPacket() - added explicit catch
    of SkipRemainingOptionsError which logs the error
    but allows the processing to continue.

src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
    TEST_F(Dhcpv4SrvTest, truncatedVIVSOOption) -
    new test to verify skip-options logic for truncated
    vendor option

src/lib/dhcp/tests/pkt4_unittest.cc
    TEST_F(Pkt4Test, truncatedVendorLength) - new
    test that verifies Pkt4 unpacking of truncated VIVSO

src/lib/dhcp/test...
parent b4f3640c
......@@ -376,6 +376,11 @@ server is about to open sockets on the specified port.
A warning message issued when IfaceMgr fails to open and bind a socket. The reason
for the failure is appended as an argument of the log message.
% DHCP4_PACKET_OPTIONS_SKIPPED An error upacking an option, caused subsequent options to be skipped: %1
A debug message issued when an option failed to unpack correctly, making it
impossible to unpack the remaining options in the packet. The server will
server will still attempt to service the packet
% DHCP4_PACKET_DROP_0001 failed to parse packet from %1 to %2, received over interface %3, reason: %4
The DHCPv4 server has received a packet that it is unable to
interpret. The reason why the packet is invalid is included in the message.
......
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2018 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
......@@ -964,6 +964,12 @@ Dhcpv4Srv::processPacket(Pkt4Ptr& query, Pkt4Ptr& rsp) {
.arg(query->getLocalAddr().toText())
.arg(query->getIface());
query->unpack();
} catch (const SkipRemainingOptionsError& e) {
// An option failed to unpack but we are to attempt to process it
// anyway. Log it and let's hope for the best.
LOG_DEBUG(options4_logger, DBG_DHCP4_DETAIL,
DHCP4_PACKET_OPTIONS_SKIPPED)
.arg(e.what());
} catch (const std::exception& e) {
// Failed to parse the packet.
LOG_DEBUG(bad_packet4_logger, DBG_DHCP4_DETAIL,
......@@ -3107,7 +3113,7 @@ Dhcpv4Srv::deferredUnpack(Pkt4Ptr& query)
opt = def->optionFactory(Option::V4, code, buf.cbegin(), buf.cend());
query->addOption(opt);
}
}
}
void
Dhcpv4Srv::startD2() {
......
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2018 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
......@@ -2458,7 +2458,7 @@ TEST_F(Dhcpv4SrvTest, option43LastResort) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -2549,7 +2549,7 @@ TEST_F(Dhcpv4SrvTest, option43BadRaw) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -2713,7 +2713,7 @@ TEST_F(Dhcpv4SrvTest, option43RawGlobal) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -2808,7 +2808,7 @@ TEST_F(Dhcpv4SrvTest, option43RawClass) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -2920,7 +2920,7 @@ TEST_F(Dhcpv4SrvTest, option43Class) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -3054,7 +3054,7 @@ TEST_F(Dhcpv4SrvTest, option43ClassPriority) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -3194,7 +3194,7 @@ TEST_F(Dhcpv4SrvTest, option43Classes) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add a vendor-class-identifier (code 60)
OptionPtr opt = offer->getOption(DHO_VENDOR_CLASS_IDENTIFIER);
EXPECT_TRUE(opt);
......@@ -3305,7 +3305,7 @@ TEST_F(Dhcpv4SrvTest, privateOption) {
// Check if we get response at all
checkResponse(offer, DHCPOFFER, 1234);
// Processing should add an option with code 234
OptionPtr opt = offer->getOption(234);
EXPECT_TRUE(opt);
......@@ -4118,6 +4118,64 @@ TEST_F(Dhcpv4SrvTest, userContext) {
EXPECT_EQ("{ \"value\": 42 }", pools[0]->getContext()->str());
}
// Verifies that an a client query with a truncated length in
// vendor option (125) will still be processed by the server.
TEST_F(Dhcpv4SrvTest, truncatedVIVSOOption) {
IfaceMgrTestConfig test_config(true);
IfaceMgr::instance().openSockets4();
NakedDhcpv4Srv srv(0);
string config = "{ \"interfaces-config\": {"
" \"interfaces\": [ \"*\" ]"
"},"
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, "
"\"subnet4\": [ { "
" \"pools\": [ { \"pool\": \"10.206.80.0/25\" } ],"
" \"subnet\": \"10.206.80.0/24\", "
" \"rebind-timer\": 2000, "
" \"renew-timer\": 1000, "
" \"valid-lifetime\": 4000,"
" \"interface\": \"eth0\" "
" } ],"
"\"valid-lifetime\": 4000 }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
ConstElementPtr status;
// Configure the server and make sure the config is accepted
EXPECT_NO_THROW(status = configureDhcp4Server(srv, json));
ASSERT_TRUE(status);
comment_ = config::parseAnswer(rcode_, status);
ASSERT_EQ(0, rcode_);
CfgMgr::instance().commit();
// Create a DISCOVER with a VIVSO option whose length is
// too short.
Pkt4Ptr dis;
ASSERT_NO_THROW(dis = PktCaptures::discoverWithTruncatedVIVSO());
// Simulate that we have received that traffic
srv.fakeReceive(dis);
// Server will now process to run its normal loop, but instead of calling
// IfaceMgr::receive4(), it will read all packets from the list set by
// fakeReceive()
// In particular, it should call registered buffer4_receive callback.
srv.run();
// Check that the server did send a response
ASSERT_EQ(1, srv.fake_sent_.size());
// Make sure that we received an response and it was an offer
Pkt4Ptr offer = srv.fake_sent_.front();
ASSERT_TRUE(offer);
}
/// @todo: Implement proper tests for MySQL lease/host database,
/// see ticket #4214.
......
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2018 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
......@@ -42,6 +42,13 @@ typedef std::multimap<unsigned int, OptionPtr> OptionCollection;
/// A pointer to an OptionCollection
typedef boost::shared_ptr<OptionCollection> OptionCollectionPtr;
/// @brief Exception thrown during option unpacking
class SkipRemainingOptionsError : public Exception {
public:
SkipRemainingOptionsError (const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
class Option {
public:
/// length of the usual DHCPv4 option header (there are exceptions)
......
// Copyright (C) 2012-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2018 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
......@@ -221,6 +221,9 @@ OptionDefinition::optionFactory(Option::Universe u, uint16_t type,
;
}
return (OptionPtr(new OptionCustom(*this, u, begin, end)));
} catch (const SkipRemainingOptionsError& ex) {
// We need to throw this one as is.
throw ex;
} catch (const Exception& ex) {
isc_throw(InvalidOptionValue, ex.what());
}
......@@ -813,7 +816,7 @@ OptionPtr
OptionDefinition::factoryFqdnList(Option::Universe u,
OptionBufferConstIter begin,
OptionBufferConstIter end) const {
const std::vector<uint8_t> data(begin, end);
if (data.empty()) {
isc_throw(InvalidOptionValue, "FQDN list option has invalid length of 0");
......
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2018 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
......@@ -53,8 +53,12 @@ void OptionVendor::pack(isc::util::OutputBuffer& buf) const {
void OptionVendor::unpack(OptionBufferConstIter begin,
OptionBufferConstIter end) {
// We throw SkipRemainingOptionsError so callers can
// abandon further unpacking, if desired.
if (distance(begin, end) < sizeof(uint32_t)) {
isc_throw(OutOfRange, "Truncated vendor-specific information option"
isc_throw(SkipRemainingOptionsError,
"Truncated vendor-specific information option"
<< ", length=" << distance(begin, end));
}
......
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2018 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
......@@ -67,7 +67,8 @@ public:
/// @param begin iterator to first byte of option data
/// @param end iterator to end of option data (first byte after option end)
///
/// @throw isc::OutOfRange if provided buffer is shorter than data size.
/// @throw isc::SkipRemainingOptionsBuffer if provided buffer is
/// shorter than data size.
virtual void unpack(OptionBufferConstIter begin, OptionBufferConstIter end);
/// @brief Sets enterprise identifier
......
// Copyright (C) 2011-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-2018 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
......@@ -13,9 +13,12 @@
#include <dhcp/option_int.h>
#include <dhcp/option_string.h>
#include <dhcp/option4_addrlst.h>
#include <dhcp/option_vendor.h>
#include <dhcp/pkt4.h>
#include <exceptions/exceptions.h>
#include <util/buffer.h>
#include <util/encode/hex.h>
#include <pkt_captures.h>
#include <boost/shared_array.hpp>
#include <boost/shared_ptr.hpp>
......@@ -1131,4 +1134,76 @@ TEST_F(Pkt4Test, getType) {
EXPECT_TRUE(pkt.getName());
}
// Verifies that when the VIVSO option 125 has length that is too
// short (i.e. less than sizeof(uint8_t), unpack throws a
// SkipRemainingOptionsError exception
TEST_F(Pkt4Test, truncatedVendorLength) {
// Raw data for a valid DISCOVER packet. Its option 125 has a valid length
// of 133 (x85). Look for 303a7d: next two digits = 85
const char* good_discover =
"010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000063825363350"
"10137070102030407067d3c0a646f63736973332e303a7d850000118b80010102057b"
"01010102010303010104010105010106010107010f0801100901030a01010b01180c0"
"1010d0201000e0201000f010110040000000211010113010114010015013f16010117"
"01011801041901041a01041b01201c01021d01081e01201f011020011021010222010"
"1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552"
"4f5554455208030020400418333936373739343234343335353037373031303134303"
"035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f"
"5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727"
"06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005"
"03f802067896845ef7af090b0000118b06010401020300ff";
// Same DISCOVER as above but with the option 125 length changed to 01.
const char* bad_discover =
"010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000063825363350"
"10137070102030407067d3c0a646f63736973332e303a7d010000118b80010102057b"
"01010102010303010104010105010106010107010f0801100901030a01010b01180c0"
"1010d0201000e0201000f010110040000000211010113010114010015013f16010117"
"01011801041901041a01041b01201c01021d01081e01201f011020011021010222010"
"1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552"
"4f5554455208030020400418333936373739343234343335353037373031303134303"
"035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f"
"5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727"
"06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005"
"03f802067896845ef7af090b0000118b06010401020300ff";
// Build a good discover packet
Pkt4Ptr pkt = test::PktCaptures::discoverWithValidVIVSO();
// Unpacking should not throw
ASSERT_NO_THROW(pkt->unpack());
ASSERT_EQ(DHCPDISCOVER, pkt->getType());
// VIVSO option should be there
OptionPtr x = pkt->getOption(DHO_VIVSO_SUBOPTIONS);
ASSERT_TRUE(x);
ASSERT_EQ(DHO_VIVSO_SUBOPTIONS, x->getType());
OptionVendorPtr vivso = boost::dynamic_pointer_cast<OptionVendor>(x);
ASSERT_TRUE(vivso);
EXPECT_EQ(133+2, vivso->len()); // data + opt code + len
// Build a bad discover packet
pkt = test::PktCaptures::discoverWithTruncatedVIVSO();
// Unpack should throw Skip exception
ASSERT_THROW(pkt->unpack(), SkipRemainingOptionsError);
ASSERT_EQ(DHCPDISCOVER, pkt->getType());
// VIVSO option should not be there
x = pkt->getOption(DHO_VIVSO_SUBOPTIONS);
ASSERT_FALSE(x);
}
} // end of anonymous namespace
// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2018 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
......@@ -33,6 +33,20 @@ public:
/// @return relayed DISCOVER
static isc::dhcp::Pkt4Ptr captureRelayedDiscover2();
/// @brief returns captured DISCOVER that contains a valid VIVSO option
///
/// See method code for a detailed explanation.
///
/// @return relayed DISCOVER
static isc::dhcp::Pkt4Ptr discoverWithValidVIVSO();
/// @brief returns captured DISCOVER that contains a truncated VIVSO option
///
/// See method code for a detailed explanation.
///
/// @return relayed DISCOVER
static isc::dhcp::Pkt4Ptr discoverWithTruncatedVIVSO();
// see pkt_captures6.cc for descriptions
// The descriptions are too large and too closely related to the
// code, so it is kept in .cc rather than traditionally in .h
......
// Copyright (C) 2013-2017 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2018 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
......@@ -178,6 +178,110 @@ Bootstrap Protocol
return (packetFromCapture(hex_string));
}
Pkt4Ptr PktCaptures::discoverWithValidVIVSO() {
/* DISCOVER that contains a valid VIVSO option 125
User Datagram Protocol, Src Port: 67, Dst Port: 67
Bootstrap Protocol (Discover)
Message type: Boot Request (1)
Hardware type: Ethernet (0x01)
Hardware address length: 6
Hops: 1
Transaction ID: 0x2d5d43cb
Seconds elapsed: 0
Bootp flags: 0x8000, Broadcast flag (Broadcast)
Client IP address: 0.0.0.0
Your (client) IP address: 0.0.0.0
Next server IP address: 0.0.0.0
Relay agent IP address: 10.206.80.1
Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af)
Client hardware address padding: 00000000000000000000
Server host name not given
Boot file name not given
Magic cookie: DHCP
Option: (53) DHCP Message Type (Discover)
Option: (55) Parameter Request List
Option: (60) Vendor class identifier
Option: (125) V-I Vendor-specific Information
Option: (43) Vendor-Specific Information (CableLabs)
Option: (61) Client identifier
Option: (57) Maximum DHCP Message Size
Option: (82) Agent Information Option
Option: (255) End
*/
string hex_string =
"010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000063825363350"
"10137070102030407067d3c0a646f63736973332e303a7d850000118b80010102057b"
"01010102010303010104010105010106010107010f0801100901030a01010b01180c0"
"1010d0201000e0201000f010110040000000211010113010114010015013f16010117"
"01011801041901041a01041b01201c01021d01081e01201f011020011021010222010"
"1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552"
"4f5554455208030020400418333936373739343234343335353037373031303134303"
"035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f"
"5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727"
"06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005"
"03f802067896845ef7af090b0000118b06010401020300ff";
return (packetFromCapture(hex_string));
}
Pkt4Ptr PktCaptures::discoverWithTruncatedVIVSO() {
/* DISCOVER that contains VIVSO option 125 with an INVALID length of 01
User Datagram Protocol, Src Port: 67, Dst Port: 67
Bootstrap Protocol (Discover)
Message type: Boot Request (1)
Hardware type: Ethernet (0x01)
Hardware address length: 6
Hops: 1
Transaction ID: 0x2d5d43cb
Seconds elapsed: 0
Bootp flags: 0x8000, Broadcast flag (Broadcast)
Client IP address: 0.0.0.0
Your (client) IP address: 0.0.0.0
Next server IP address: 0.0.0.0
Relay agent IP address: 10.206.80.1
Client MAC address: ArrisGro_5e:f7:af (78:96:84:5e:f7:af)
Client hardware address padding: 00000000000000000000
Server host name not given
Boot file name not given
Magic cookie: DHCP
Option: (53) DHCP Message Type (Discover)
Option: (55) Parameter Request List
Option: (60) Vendor class identifier
Option: (125) V-I Vendor-specific Information
Option: (43) Vendor-Specific Information (CableLabs)
Option: (61) Client identifier
Option: (57) Maximum DHCP Message Size
Option: (82) Agent Information Option
Option: (255) End
*/
string hex_string =
"010106012d5d43cb000080000000000000000000000000000ace50017896845ef7af0"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000000000000000"
"000000000000000000000000000000000000000000000000000000000063825363350"
"10137070102030407067d3c0a646f63736973332e303a7d010000118b80010102057b"
"01010102010303010104010105010106010107010f0801100901030a01010b01180c0"
"1010d0201000e0201000f010110040000000211010113010114010015013f16010117"
"01011801041901041a01041b01201c01021d01081e01201f011020011021010222010"
"1230100240100250101260200ff2701012801d82b7c020345434d030b45434d3a4552"
"4f5554455208030020400418333936373739343234343335353037373031303134303"
"035050131061e534247365838322d382e362e302e302d47412d30312d3937312d4e4f"
"5348070432343030090a534247363738322d41430a144d6f746f726f6c6120436f727"
"06f726174696f6e3d0fff845ef7af000300017896845ef7af390205dc521b01048005"
"03f802067896845ef7af090b0000118b06010401020300ff";
return (packetFromCapture(hex_string));
}
}; // end of isc::dhcp::test namespace
}; // end of isc::dhcp namespace
}; // end of isc namespace
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment