Browse Source

[2827] Relay structures, message parsing/transmission implemented

Tomek Mrugalski 12 years ago
parent
commit
3511c6e651
4 changed files with 273 additions and 8 deletions
  1. 9 2
      src/lib/dhcp/libdhcp++.cc
  2. 5 1
      src/lib/dhcp/libdhcp++.h
  3. 213 3
      src/lib/dhcp/pkt6.cc
  4. 46 2
      src/lib/dhcp/pkt6.h

+ 9 - 2
src/lib/dhcp/libdhcp++.cc

@@ -128,7 +128,8 @@ LibDHCP::optionFactory(Option::Universe u,
 
 
 size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
-                               isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::Option::OptionCollection& options,
+                               size_t* relay_msg_offset /* = 0 */) {
     size_t offset = 0;
     size_t length = buf.size();
 
@@ -143,6 +144,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
     while (offset + 4 <= length) {
         uint16_t opt_type = isc::util::readUint16(&buf[offset]);
         offset += 2;
+
         uint16_t opt_len = isc::util::readUint16(&buf[offset]);
         offset += 2;
 
@@ -151,6 +153,11 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
             return (offset);
         }
 
+        if (opt_type == D6O_RELAY_MSG && relay_msg_offset) {
+            // remember offset of the beginning of the relay-msg option
+            *relay_msg_offset = offset;
+        }
+
         // Get all definitions with the particular option code. Note that option
         // code is non-unique within this container however at this point we
         // expect to get one option definition with the particular code. If more
@@ -193,7 +200,7 @@ size_t LibDHCP::unpackOptions6(const OptionBuffer& buf,
 }
 
 size_t LibDHCP::unpackOptions4(const OptionBuffer& buf,
-                                 isc::dhcp::Option::OptionCollection& options) {
+                               isc::dhcp::Option::OptionCollection& options) {
     size_t offset = 0;
 
     // Get the list of stdandard option definitions.

+ 5 - 1
src/lib/dhcp/libdhcp++.h

@@ -121,8 +121,12 @@ public:
     /// @param buf Buffer to be parsed.
     /// @param options Reference to option container. Options will be
     ///        put here.
+    /// @param relay_msg_offset reference to a size_t structure. If specified,
+    ///        offset to beginning of relay_msg option will be store here.
+    /// @return offset to the first byte after last parsed option
     static size_t unpackOptions6(const OptionBuffer& buf,
-                                 isc::dhcp::Option::OptionCollection& options);
+                                 isc::dhcp::Option::OptionCollection& options,
+                                 size_t* relay_msg_offset = 0);
 
     /// Registers factory method that produces options of specific option types.
     ///

+ 213 - 3
src/lib/dhcp/pkt6.cc

@@ -21,10 +21,17 @@
 #include <sstream>
 
 using namespace std;
+using namespace isc::asiolink;
 
 namespace isc {
 namespace dhcp {
 
+Pkt6::RelayInfo::RelayInfo()
+    :msg_type_(0), hop_count_(0), linkaddr_("::"), peeraddr_(""), relay_msg_len_(0) {
+    // interface_id_, subscriber_id_, remote_id_ initialized to NULL
+    // echo_options_ initialized to empty collection
+}
+
 Pkt6::Pkt6(const uint8_t* buf, uint32_t buf_len, DHCPv6Proto proto /* = UDP */) :
     proto_(proto),
     msg_type_(0),
@@ -54,6 +61,52 @@ Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) :
 }
 
 uint16_t Pkt6::len() {
+    if (relay_info_.empty()) {
+        return (directLen());
+    } else {
+        calculateRelaySizes();
+        return (relay_info_[0].relay_msg_len_ + getRelayOverhead(relay_info_[0]));
+    }
+}
+
+uint16_t Pkt6::getRelayOverhead(const RelayInfo& relay) {
+    uint16_t len = DHCPV6_RELAY_HDR_LEN // fixed header
+        + Option::OPTION6_HDR_LEN; // header of the relay-msg option
+
+    if (relay.interface_id_) {
+        len += relay.interface_id_->len();
+    }
+    if (relay.subscriber_id_) {
+        len += relay.subscriber_id_->len();
+    }
+    if (relay.remote_id_) {
+        len += relay.remote_id_->len();
+    }
+
+    for (Option::OptionCollection::const_iterator opt = relay.echo_options_.begin();
+         opt != relay.echo_options_.end(); ++opt) {
+        len += (opt->second)->len();
+    }
+
+    return (len);
+}
+
+uint16_t Pkt6::calculateRelaySizes() {
+
+    uint16_t len = directLen(); // start with length of all options
+
+    int relay_index = relay_info_.size();
+
+    while (relay_index) {
+        relay_info_[relay_index - 1].relay_msg_len_ = len;
+        len += getRelayOverhead(relay_info_[relay_index - 1]);
+        --relay_index;
+    }
+
+    return (len);
+}
+
+uint16_t Pkt6::directLen() {
     uint16_t length = DHCPV6_PKT_HDR_LEN; // DHCPv6 header
 
     for (Option::OptionCollection::iterator it = options_.begin();
@@ -82,6 +135,41 @@ Pkt6::pack() {
 bool
 Pkt6::packUDP() {
     try {
+
+        if (!relay_info_.empty()) {
+            calculateRelaySizes();
+
+            for (vector<RelayInfo>::iterator relay = relay_info_.begin();
+                 relay != relay_info_.end(); ++relay) {
+                bufferOut_.writeUint8(relay->msg_type_);
+                bufferOut_.writeUint8(relay->hop_count_);
+                bufferOut_.writeData(&(relay->linkaddr_.toBytes()[0]),
+                                     isc::asiolink::V6ADDRESS_LEN);
+                bufferOut_.writeData(&relay->peeraddr_.toBytes()[0],
+                                     isc::asiolink::V6ADDRESS_LEN);
+
+                if (relay->interface_id_) {
+                    relay->interface_id_->pack(bufferOut_);
+                }
+                if (relay->subscriber_id_) {
+                    relay->subscriber_id_->pack(bufferOut_);
+                }
+                if (relay->remote_id_) {
+                    relay->remote_id_->pack(bufferOut_);
+                }
+
+                for (Option::OptionCollection::const_iterator opt =
+                         relay->echo_options_.begin();
+                     opt != relay->echo_options_.end(); ++opt) {
+                    (opt->second)->pack(bufferOut_);
+                }
+
+                bufferOut_.writeUint16(D6O_RELAY_MSG);
+                bufferOut_.writeUint16(relay->relay_msg_len_);
+            }
+
+        }
+
         // DHCPv6 header: message-type (1 octect) + transaction id (3 octets)
         bufferOut_.writeUint8(msg_type_);
         // store 3-octet transaction-id
@@ -127,12 +215,43 @@ Pkt6::unpackUDP() {
         return (false);
     }
     msg_type_ = data_[0];
-    transid_ = ( (data_[1]) << 16 ) +
-        ((data_[2]) << 8) + (data_[3]);
+    switch (msg_type_) {
+    case DHCPV6_SOLICIT:
+    case DHCPV6_ADVERTISE:
+    case DHCPV6_REQUEST:
+    case DHCPV6_CONFIRM:
+    case DHCPV6_RENEW:
+    case DHCPV6_REBIND:
+    case DHCPV6_REPLY:
+    case DHCPV6_DECLINE:
+    case DHCPV6_RECONFIGURE:
+    case DHCPV6_INFORMATION_REQUEST:
+    default: // assume that uknown messages are not using relay format
+        {
+            return (unpackMsg(data_.begin(), data_.end()));
+        }
+    case DHCPV6_RELAY_FORW:
+    case DHCPV6_RELAY_REPL:
+        return (unpackRelayMsg());
+    }
+}
+
+bool
+Pkt6::unpackMsg(OptionBuffer::const_iterator begin,
+                OptionBuffer::const_iterator end) {
+    if (std::distance(begin, end) < 4) {
+        // truncated message (less than 4 bytes)
+        return (false);
+    }
+
+    msg_type_ = *begin++;
+
+    transid_ = ( (*begin++) << 16 ) +
+        ((*begin++) << 8) + (*begin++);
     transid_ = transid_ & 0xffffff;
 
     try {
-        OptionBuffer opt_buffer(data_.begin() + 4, data_.end());
+        OptionBuffer opt_buffer(begin, end);
 
         LibDHCP::unpackOptions6(opt_buffer, options_);
     } catch (const Exception& e) {
@@ -143,6 +262,97 @@ Pkt6::unpackUDP() {
 }
 
 bool
+Pkt6::unpackRelayMsg() {
+
+    // we use offset + bufsize, because we want to avoid creating unnecessary
+    // copies. There may be up to 32 relays. While using InputBuffer would
+    // be probably a bit cleaner, copying data up to 32 times is unacceptable
+    // price here. Hence a single buffer with offets and lengths.
+    size_t bufsize = data_.size();
+    size_t offset = 0;
+
+    size_t relay_msg_offset = 0;
+
+    while (bufsize >= DHCPV6_RELAY_HDR_LEN) {
+
+        RelayInfo relay;
+
+        // parse fixed header first (first 34 bytes)
+        relay.msg_type_ = data_[offset++];
+        relay.hop_count_ = data_[offset++];
+        relay.linkaddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+        offset += isc::asiolink::V6ADDRESS_LEN;
+        relay.peeraddr_ = IOAddress::fromBytes(AF_INET6, &data_[offset]);
+        offset += isc::asiolink::V6ADDRESS_LEN;
+        bufsize -= DHCPV6_RELAY_HDR_LEN; // 34 bytes (1+1+16+16)
+
+        try {
+            Option::OptionCollection options;
+
+            // parse the rest as options
+            OptionBuffer opt_buffer(&data_[offset], &data_[offset+bufsize]);
+            LibDHCP::unpackOptions6(opt_buffer, options, &relay_msg_offset);
+
+            /// @todo: check that each option appears at most once
+            //relay.interface_id_ = options->getOption(D6O_INTERFACE_ID);
+            //relay.subscriber_id_ = options->getOption(D6O_SUBSCRIBER_ID);
+            //relay.remote_id_ = options->getOption(D6O_REMOTE_ID);
+
+            Option::OptionCollection::const_iterator relay_iter = options.find(D6O_RELAY_MSG);
+            if (relay_iter == options.end()) {
+                // there's nothing to decapsulate. We give up.
+                isc_throw(InvalidOperation, "Mandatory relay_msg missing");
+            }
+            OptionPtr relay_msg = relay_iter->second;
+
+            // store relay information parsed so far
+            addRelayInfo(relay);
+
+            /// @todo: implement ERO here
+
+            size_t inner_len = relay_msg->len() - relay_msg->getHeaderLen();
+            if (inner_len >= bufsize) {
+                // length of the relay_msg option extends beyond end of the message
+                isc_throw(Unexpected, "Relay-msg option is truncated.");
+                return false;
+            }
+            uint8_t inner_type = relay_msg->getUint8();
+            offset += relay_msg_offset;
+            bufsize = inner_len;
+
+            if ( (inner_type != DHCPV6_RELAY_FORW) && (inner_type != DHCPV6_RELAY_REPL)) {
+                // Ok, the inner message is not encapsulated, let's decode it directly
+                return (unpackMsg(data_.begin() + offset, data_.begin() + offset + inner_len));
+            }
+
+            // Oh well, there's inner relay-forw or relay-repl inside. Let's unpack it as well
+
+        } catch (const Exception& e) {
+            /// @todo: throw exception here once we turn this function to void.
+            return (false);
+        }
+    }
+
+    if ( (offset == data_.size()) && (bufsize == 0) ) {
+        // message has been parsed completely
+        return (true);
+    }
+
+    /// @todo: log here that there are additional unparsed bytes
+    return (true);
+}
+
+void
+Pkt6::addRelayInfo(const RelayInfo& relay) {
+    if (relay_info_.size() > 32) {
+        isc_throw(BadValue, "Massage cannot be encapsulated more than 32 times");
+    }
+
+    /// @todo: Implement type checks here (e.g. we could receive relay-forw in relay-repl)
+    relay_info_.push_back(relay);
+}
+
+bool
 Pkt6::unpackTCP() {
     isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover) "
               "not implemented yet.");

+ 46 - 2
src/lib/dhcp/pkt6.h

@@ -32,15 +32,35 @@ namespace dhcp {
 
 class Pkt6 {
 public:
-    /// specifes DHCPv6 packet header length
+    /// specifies non-relayed DHCPv6 packet header length (over UDP)
     const static size_t DHCPV6_PKT_HDR_LEN = 4;
 
+    /// specifies relay DHCPv6 packet header length (over UDP)
+    const static size_t DHCPV6_RELAY_HDR_LEN = 34;
+
     /// DHCPv6 transport protocol
     enum DHCPv6Proto {
         UDP = 0, // most packets are UDP
         TCP = 1  // there are TCP DHCPv6 packets (bulk leasequery, failover)
     };
 
+    struct RelayInfo {
+
+        RelayInfo();
+
+        uint8_t   msg_type_;      ///< message type (RELAY-FORW oro RELAY-REPL)
+        uint8_t   hop_count_;     ///< number of traversed relays (up to 32)
+        isc::asiolink::IOAddress linkaddr_;      ///< fixed field in relay-forw/relay-reply
+        isc::asiolink::IOAddress peeraddr_;      ///< fixed field in relay-forw/relay-reply
+        OptionPtr interface_id_;  ///< interface-id option (optional)
+        OptionPtr subscriber_id_; ///< subscriber-id (RFC4580)
+        OptionPtr remote_id_;     ///< remote-id (RFC4649)
+        uint16_t  relay_msg_len_; ///< length of the relay_msg_len
+
+        /// used for ERO (Echo Request Option, RFC 4994)
+        isc::dhcp::Option::OptionCollection echo_options_;
+    };
+
     /// Constructor, used in replying to a message
     ///
     /// @param msg_type type of message (SOLICIT=1, ADVERTISE=2, ...)
@@ -246,7 +266,7 @@ public:
     /// @brief Returns packet timestamp.
     ///
     /// Returns packet timestamp value updated when
-    /// packet is received or send.
+    /// packet is received or sent.
     ///
     /// @return packet timestamp.
     const boost::posix_time::ptime& getTimestamp() const { return timestamp_; }
@@ -259,8 +279,18 @@ public:
     /// @return interface name
     void setIface(const std::string& iface ) { iface_ = iface; };
 
+    /// @brief add information about one traversed relay
+    ///
+    /// This adds information about one traversed relay, i.e.
+    /// one relay-forw or relay-repl level of encapsulation.
+    ///
+    /// @param relay structure with necessary relay information
+    void addRelayInfo(const RelayInfo& relay);
+
     /// collection of options present in this message
     ///
+    /// @todo: Text mentions protected, but this is really public
+    ///
     /// @warning This protected member is accessed by derived
     /// classes directly. One of such derived classes is
     /// @ref perfdhcp::PerfPkt6. The impact on derived clasess'
@@ -340,6 +370,17 @@ protected:
     /// @return true, if build was successful
     bool unpackUDP();
 
+    bool unpackMsg(OptionBuffer::const_iterator begin,
+                   OptionBuffer::const_iterator end);
+
+    bool unpackRelayMsg();
+
+    uint16_t getRelayOverhead(const RelayInfo& relay);
+
+    uint16_t calculateRelaySizes();
+
+    uint16_t directLen();
+
     /// UDP (usually) or TCP (bulk leasequery or failover)
     DHCPv6Proto proto_;
 
@@ -394,6 +435,9 @@ protected:
 
     /// packet timestamp
     boost::posix_time::ptime timestamp_;
+
+    /// relay information
+    std::vector<RelayInfo> relay_info_;
 }; // Pkt6 class
 
 typedef boost::shared_ptr<Pkt6> Pkt6Ptr;