// Copyright (C) 2011-2012 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 #include #include #include #include #include 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), transid_(rand()%0xffffff), iface_(""), ifindex_(-1), local_addr_("::"), remote_addr_("::"), local_port_(0), remote_port_(0), bufferOut_(0) { data_.resize(buf_len); memcpy(&data_[0], buf, buf_len); } Pkt6::Pkt6(uint8_t msg_type, uint32_t transid, DHCPv6Proto proto /*= UDP*/) : proto_(proto), msg_type_(msg_type), transid_(transid), iface_(""), ifindex_(-1), local_addr_("::"), remote_addr_("::"), local_port_(0), remote_port_(0), bufferOut_(0) { } 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(); it != options_.end(); ++it) { length += (*it).second->len(); } return (length); } bool Pkt6::pack() { switch (proto_) { case UDP: return packUDP(); case TCP: return packTCP(); default: isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)"); } return (false); // never happens } bool Pkt6::packUDP() { try { if (!relay_info_.empty()) { calculateRelaySizes(); for (vector::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 bufferOut_.writeUint8( (transid_ >> 16) & 0xff ); bufferOut_.writeUint8( (transid_ >> 8) & 0xff ); bufferOut_.writeUint8( (transid_) & 0xff ); // the rest are options LibDHCP::packOptions(bufferOut_, options_); } catch (const Exception& e) { /// @todo: throw exception here once we turn this function to void. return (false); } return (true); } bool Pkt6::packTCP() { /// TODO Implement this function. isc_throw(Unexpected, "DHCPv6 over TCP (bulk leasequery and failover)" "not implemented yet."); } bool Pkt6::unpack() { switch (proto_) { case UDP: return unpackUDP(); case TCP: return unpackTCP(); default: isc_throw(BadValue, "Invalid protocol specified (non-TCP, non-UDP)"); } return (false); // never happens } bool Pkt6::unpackUDP() { if (data_.size() < 4) { // @todo: throw exception here informing that packet is truncated // once we turn this function to void. return (false); } msg_type_ = data_[0]; 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(begin, end); LibDHCP::unpackOptions6(opt_buffer, options_); } catch (const Exception& e) { // @todo: throw exception here once we turn this function to void. return (false); } return (true); } 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."); } std::string Pkt6::toText() { stringstream tmp; tmp << "localAddr=[" << local_addr_.toText() << "]:" << local_port_ << " remoteAddr=[" << remote_addr_.toText() << "]:" << remote_port_ << endl; tmp << "msgtype=" << static_cast(msg_type_) << ", transid=0x" << hex << transid_ << dec << endl; for (isc::dhcp::Option::OptionCollection::iterator opt=options_.begin(); opt != options_.end(); ++opt) { tmp << opt->second->toText() << std::endl; } return tmp.str(); } OptionPtr Pkt6::getOption(uint16_t opt_type) { isc::dhcp::Option::OptionCollection::const_iterator x = options_.find(opt_type); if (x!=options_.end()) { return (*x).second; } return OptionPtr(); // NULL } isc::dhcp::Option::OptionCollection Pkt6::getOptions(uint16_t opt_type) { isc::dhcp::Option::OptionCollection found; for (Option::OptionCollection::const_iterator x = options_.begin(); x != options_.end(); ++x) { if (x->first == opt_type) { found.insert(make_pair(opt_type, x->second)); } } return (found); } void Pkt6::addOption(const OptionPtr& opt) { options_.insert(pair >(opt->getType(), opt)); } bool Pkt6::delOption(uint16_t type) { isc::dhcp::Option::OptionCollection::iterator x = options_.find(type); if (x!=options_.end()) { options_.erase(x); return (true); // delete successful } return (false); // can't find option to be deleted } void Pkt6::repack() { bufferOut_.writeData(&data_[0], data_.size()); } void Pkt6::updateTimestamp() { timestamp_ = boost::posix_time::microsec_clock::universal_time(); } const char* Pkt6::getName(uint8_t type) { static const char* CONFIRM = "CONFIRM"; static const char* DECLINE = "DECLINE"; static const char* INFORMATION_REQUEST = "INFORMATION_REQUEST"; static const char* REBIND = "REBIND"; static const char* RELEASE = "RELEASE"; static const char* RENEW = "RENEW"; static const char* REQUEST = "REQUEST"; static const char* SOLICIT = "SOLICIT"; static const char* UNKNOWN = "UNKNOWN"; switch (type) { case DHCPV6_CONFIRM: return (CONFIRM); case DHCPV6_DECLINE: return (DECLINE); case DHCPV6_INFORMATION_REQUEST: return (INFORMATION_REQUEST); case DHCPV6_REBIND: return (REBIND); case DHCPV6_RELEASE: return (RELEASE); case DHCPV6_RENEW: return (RENEW); case DHCPV6_REQUEST: return (REQUEST); case DHCPV6_SOLICIT: return (SOLICIT); default: ; } return (UNKNOWN); } const char* Pkt6::getName() const { return (getName(getType())); } } // end of isc::dhcp namespace } // end of isc namespace