| // |
| // Copyright (C) 2015 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| // |
| |
| #include "dhcp_client/dhcpv4.h" |
| |
| #include <arpa/inet.h> |
| #include <linux/if_packet.h> |
| #include <net/ethernet.h> |
| #include <net/if.h> |
| #include <netinet/ip.h> |
| #include <netinet/udp.h> |
| |
| #include <random> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/logging.h> |
| #include <brillo/variant_dictionary.h> |
| |
| #include "dhcp_client/dhcp_message.h" |
| #include "dhcp_client/dhcp_options.h" |
| #include "dhcp_client/file_io.h" |
| #include "dhcp_client/service_adaptor_interface.h" |
| #include "dhcp_client/socket_util.h" |
| #include "shill/net/arp_packet.h" |
| #include "shill/net/ip_address.h" |
| |
| using base::Bind; |
| using base::Unretained; |
| using shill::ByteString; |
| using shill::IOHandlerFactoryContainer; |
| |
| namespace dhcp_client { |
| |
| namespace { |
| // Max length of a DHCP message. |
| const size_t kDHCPMessageMaxLength = 548; |
| // Renewal time in terms of lease duration percentage. |
| const uint32_t kRenewalTimePercentage = 50; |
| // Rebinding time in terms of lease duration percentage. |
| const uint32_t kRebindTimePercentage = 87; |
| const int kInvalidSocketDescriptor = shill::Sockets::kInvalidFileDescriptor; |
| // RFC 791: the minimum value for a correct header is 20 octets. |
| // The maximum value is 60 octets. |
| const size_t kIPHeaderMinLength = 20; |
| const size_t kIPHeaderMaxLength = 60; |
| // DHCP parameters we request from server. |
| const uint8_t kDefaultParameterRequestList[] = { |
| kDHCPOptionSubnetMask, |
| kDHCPOptionInterfaceMTU, |
| kDHCPOptionBroadcastAddr, |
| kDHCPOptionRouter, |
| kDHCPOptionDNSServer, |
| kDHCPOptionDomainName, |
| kDHCPOptionLeaseTime, |
| kDHCPOptionRenewalTime, |
| kDHCPOptionRebindingTime |
| }; |
| const uint8_t kZeroHardwareAddress[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
| const int kArpProbeReplyTimeoutSeconds = 1; |
| |
| // TODO(nywang): find a place for the lease file. |
| const char kIPV4LeaseFilePathFormat[] = |
| "/tmp/lease-ipv4-%s.conf"; |
| |
| // TODO(nywang): These constant will be moved to: |
| // <dbus/dhcp_client/dbus-constants.h> |
| // In this way shill can include this header and parse |
| // the messages. |
| |
| const char kConfigurationKeyBroadcastAddress[] = |
| "BroadcastAddress"; |
| const char kConfigurationKeyDNS[] = "DomainNameServers"; |
| const char kConfigurationKeyDomainName[] = "DomainName"; |
| const char kConfigurationKeyIPAddress[] = "IPAddress"; |
| const char kConfigurationKeyMTU[] = "InterfaceMTU"; |
| const char kConfigurationKeyRouters[] = "Routers"; |
| const char kConfigurationKeyVendorEncapsulatedOptions[] = |
| "VendorEncapsulatedOptions"; |
| const char kConfigurationKeySubnetCIDR[] = "SubnetCIDR"; |
| const char kReasonBound[] = "BOUND"; |
| const char kReasonFail[] = "FAIL"; |
| const char kReasonNak[] = "NAK"; |
| } // namespace |
| |
| DHCPV4::DHCPV4(ServiceAdaptorInterface* adaptor, |
| const std::string& interface_name, |
| const ByteString& hardware_address, |
| unsigned int interface_index, |
| const std::string& network_id, |
| bool request_hostname, |
| bool arp_gateway, |
| bool unicast_arp, |
| EventDispatcherInterface* event_dispatcher) |
| : adaptor_(adaptor), |
| interface_name_(interface_name), |
| hardware_address_(hardware_address), |
| interface_index_(interface_index), |
| network_id_(network_id), |
| request_hostname_(request_hostname), |
| arp_gateway_(arp_gateway), |
| unicast_arp_(unicast_arp), |
| arp_client_(new shill::ArpClient(interface_index)), |
| event_dispatcher_(event_dispatcher), |
| io_handler_factory_( |
| IOHandlerFactoryContainer::GetInstance()->GetIOHandlerFactory()), |
| weak_ptr_factory_(this), |
| state_(State::INIT), |
| raw_socket_(kInvalidSocketDescriptor), |
| udp_socket_(kInvalidSocketDescriptor), |
| sockets_(new shill::Sockets()), |
| socket_util_(new SocketUtil(interface_name_, interface_index_)), |
| random_engine_(time(nullptr)) { |
| ResetState(); |
| } |
| |
| DHCPV4::~DHCPV4() { |
| Stop(); |
| } |
| |
| void DHCPV4::ResetState() { |
| state_ = State::INIT; |
| server_identifier_ = 0; |
| transaction_id_ = 0; |
| offered_ip_address_ = 0; |
| client_ip_ = INADDR_ANY; |
| server_ip_ = INADDR_BROADCAST; |
| renewal_task_callback_.Cancel(); |
| rebind_task_callback_.Cancel(); |
| arp_probe_reply_timeout_task_callback_.Cancel(); |
| } |
| |
| void DHCPV4::ParseRawPacket(shill::InputData* data) { |
| LOG(INFO) << __func__; |
| if (data->len < sizeof(iphdr)) { |
| LOG(ERROR) << "Invalid packet length from buffer"; |
| return; |
| } |
| // The socket filter has finished part the header validation. |
| // This function will perform the remaining part. |
| int header_len = ValidatePacketHeader(data->buf, data->len); |
| if (header_len == -1) { |
| return; |
| } |
| unsigned char* buffer = data->buf + header_len; |
| DHCPMessage msg; |
| if (!DHCPMessage::InitFromBuffer(buffer, data->len - header_len, &msg)) { |
| LOG(ERROR) << "Failed to initialize DHCP message from buffer"; |
| return; |
| } |
| // In INIT state the client ignores all messages from server. |
| if (state_ == State::INIT) { |
| return; |
| } |
| // Check transaction id with the existing one. |
| if (msg.transaction_id() != transaction_id_) { |
| LOG(ERROR) << "Transaction id(xid) doesn't match"; |
| return; |
| } |
| uint8_t message_type = msg.message_type(); |
| switch (message_type) { |
| case kDHCPMessageTypeOffer: |
| HandleOffer(msg); |
| break; |
| case kDHCPMessageTypeAck: |
| HandleAck(msg); |
| break; |
| case kDHCPMessageTypeNak: |
| HandleNak(msg); |
| break; |
| default: |
| LOG(ERROR) << "Invalid message type: " |
| << static_cast<int>(message_type); |
| } |
| } |
| |
| bool DHCPV4::WriteLease() { |
| std::string lease_file = |
| base::StringPrintf(kIPV4LeaseFilePathFormat, network_id_.c_str()); |
| LOG(INFO) << "Write lease to file"; |
| shill::ByteString lease = |
| ByteString(reinterpret_cast<unsigned char*>(&offered_ip_address_), |
| sizeof(uint32_t)); |
| if (!FileIO::GetInstance()->Write(lease_file, lease)) { |
| LOG(ERROR) << "Failed to write lease to file"; |
| return false; |
| } |
| return true; |
| } |
| |
| void DHCPV4::OnReadError(const std::string& error_msg) { |
| LOG(INFO) << __func__; |
| } |
| |
| bool DHCPV4::Start() { |
| if (!socket_util_->CreateRawSocket(&raw_socket_)) { |
| return false; |
| } |
| input_handler_.reset(io_handler_factory_->CreateIOInputHandler( |
| raw_socket_, |
| Bind(&DHCPV4::ParseRawPacket, Unretained(this)), |
| Bind(&DHCPV4::OnReadError, Unretained(this)))); |
| if (!network_id_.empty() && ReadLease()) { |
| state_ = State::INIT_REBOOT; |
| LOG(INFO) << "Start from INIT_REBOOT state"; |
| if (!SendRequest()) { |
| ResetState(); |
| EmitEvent(kReasonFail); |
| return false; |
| } |
| state_ = State::REBOOT; |
| return true; |
| } |
| if (!SendDiscover()) { |
| return false; |
| } |
| return true; |
| } |
| |
| bool DHCPV4::ReadLease() { |
| std::string lease_file = |
| base::StringPrintf(kIPV4LeaseFilePathFormat, network_id_.c_str()); |
| if (!FileIO::GetInstance()->PathExists(lease_file)) { |
| LOG(INFO) << "Lease file does not exist"; |
| return false; |
| } |
| ByteString config; |
| if (!FileIO::GetInstance()->Read(lease_file, &config)) { |
| LOG(ERROR) << "Failed to read lease file"; |
| } |
| transaction_id_ = static_cast<uint32_t>( |
| std::uniform_int_distribution<unsigned int>() |
| (random_engine_) % UINT32_MAX + 1); |
| offered_ip_address_ = |
| *reinterpret_cast<const uint32_t*>(config.GetConstData()); |
| return true; |
| } |
| |
| void DHCPV4::Stop() { |
| input_handler_.reset(); |
| if (HasALease()) { |
| if (!SendRelease()) { |
| LOG(ERROR) << "Failed to send DHCP release message"; |
| } |
| ResetState(); |
| } |
| if (raw_socket_ != kInvalidSocketDescriptor) { |
| sockets_->Close(raw_socket_); |
| raw_socket_ = kInvalidSocketDescriptor; |
| } |
| if (udp_socket_ != kInvalidSocketDescriptor) { |
| sockets_->Close(udp_socket_); |
| udp_socket_ = kInvalidSocketDescriptor; |
| } |
| arp_client_->Stop(); |
| } |
| |
| void DHCPV4::HandleOffer(const DHCPMessage& msg) { |
| LOG(INFO) << __func__; |
| if (state_ != State::SELECT) { |
| LOG(WARNING) << "Ignore DHCP Offer message"; |
| return; |
| } |
| if (msg.server_identifier() == 0) { |
| LOG(ERROR) << "Option server identifier is missing"; |
| return; |
| } |
| LOG(INFO) << "Server IP: " << IPtoString(msg.server_identifier()); |
| |
| uint32_t your_ip_address = msg.your_ip_address(); |
| if (your_ip_address == INADDR_ANY || |
| your_ip_address == INADDR_BROADCAST) { |
| LOG(ERROR) << "Reject invalid IP address from server"; |
| return; |
| } |
| LOG(INFO) << "Lease IP: " << IPtoString(your_ip_address); |
| |
| if (!ValidateOptions(msg)) { |
| return; |
| } |
| |
| // Accept this offer. |
| LOG(INFO) << "Accept offer with ip: " << IPtoString(your_ip_address); |
| // Set states variables |
| offered_ip_address_ = your_ip_address; |
| server_identifier_ = msg.server_identifier(); |
| transaction_id_ = msg.transaction_id(); |
| |
| if (!SendRequest()) { |
| return; |
| } |
| // Set state upon success. |
| state_ = State::REQUEST; |
| } |
| |
| void DHCPV4::HandleAck(const DHCPMessage& msg) { |
| LOG(INFO) << __func__; |
| if (state_ != State::REQUEST && |
| state_ != State::RENEW && |
| state_ != State::REBIND && |
| state_ != State::REBOOT) { |
| LOG(WARNING) << "Ignore DHCP Ack message"; |
| return; |
| } |
| if (msg.server_identifier() == 0) { |
| LOG(ERROR) << "Option server identifier is missing"; |
| return; |
| } |
| if (state_ == State::REBOOT) { |
| server_identifier_ = msg.server_identifier(); |
| } |
| if (msg.server_identifier() != server_identifier_) { |
| LOG(ERROR) << "Option server doesn't match"; |
| return; |
| } |
| LOG(INFO) << "Server IP: " << IPtoString(server_identifier_); |
| |
| uint32_t your_ip_address = msg.your_ip_address(); |
| // In case that this offered ip is different from we got from DHCP offer. |
| if (offered_ip_address_ != msg.your_ip_address()) { |
| LOG(ERROR) << "Reject invalid IP address from server"; |
| return; |
| } |
| LOG(INFO) << "Lease IP: " << IPtoString(your_ip_address); |
| |
| if (!ValidateOptions(msg)) { |
| return; |
| } |
| |
| CheckIpCollision(); |
| |
| // TODO(nywang): Validate the gateway address using ARP. |
| // Accept the ACK. |
| uint32_t lease_time = msg.lease_time(); |
| uint32_t renewal_time = lease_time * kRenewalTimePercentage / 100; |
| uint32_t rebinding_time = lease_time * kRebindTimePercentage / 100; |
| // Override the renewal time or rebinding time if ACK specifies the value. |
| if (msg.renewal_time() != 0) { |
| renewal_time = msg.renewal_time(); |
| } |
| if (msg.rebinding_time() != 0) { |
| rebinding_time = msg.rebinding_time(); |
| } |
| renewal_task_callback_.Reset( |
| Bind(&DHCPV4::RenewalTask, weak_ptr_factory_.GetWeakPtr())); |
| rebind_task_callback_.Reset( |
| Bind(&DHCPV4::RebindTask, weak_ptr_factory_.GetWeakPtr())); |
| |
| event_dispatcher_->PostDelayedTask(renewal_task_callback_.callback(), |
| 1000 * renewal_time); |
| event_dispatcher_->PostDelayedTask(rebind_task_callback_.callback(), |
| 1000 * rebinding_time); |
| // Set state variables upon a valid Ack. |
| state_ = State::BOUND; |
| client_ip_ = offered_ip_address_; |
| server_ip_ = server_identifier_; |
| // Set the option parameters. |
| subnet_mask_ = msg.subnet_mask(); |
| interface_mtu_ = msg.interface_mtu(); |
| broadcast_address_ = msg.broadcast_address(); |
| router_ = msg.router(); |
| dns_server_ = msg.dns_server(); |
| vendor_specific_info_ = msg.vendor_specific_info(); |
| domain_name_ = msg.domain_name(); |
| // Write lease to persistent stotrage. |
| if (!network_id_.empty()) { |
| WriteLease(); |
| } |
| // Send the DHCP configuration to Shill. |
| EmitEvent(kReasonBound); |
| socket_util_->CreateUdpSocket(&udp_socket_); |
| } |
| |
| void DHCPV4::HandleNak(const DHCPMessage& msg) { |
| LOG(INFO) << __func__; |
| if (state_ != State::REBIND && |
| state_ != State::RENEW && |
| state_ != State::REQUEST && |
| state_ != State::REBOOT) { |
| LOG(WARNING) << "Ignore DHCP Nak message"; |
| return; |
| } |
| if (msg.server_identifier() == 0) { |
| LOG(ERROR) << "Option server identifier is missing"; |
| return; |
| } |
| if (msg.server_identifier() != server_identifier_) { |
| LOG(ERROR) << "Option server doesn't match"; |
| return; |
| } |
| if (msg.transaction_id() != transaction_id_) { |
| LOG(ERROR) << "transaction id doesn't match"; |
| return; |
| } |
| // A Nak message should contain the error message. |
| if (msg.error_message().size()) { |
| LOG(INFO) << "Received DHCP NAK message with the following error message: " |
| << msg.error_message(); |
| } |
| |
| // Set state variables upon receiving a valid Nak. |
| ResetState(); |
| |
| EmitEvent(kReasonNak); |
| } |
| |
| bool DHCPV4::SendDiscover() { |
| LOG(INFO) << __func__; |
| DHCPMessage message; |
| DHCPMessage::InitRequest(&message); |
| message.SetMessageType(kDHCPMessageTypeDiscover); |
| message.SetClientHardwareAddress(hardware_address_); |
| uint32_t transaction_id = |
| static_cast<uint32_t>( |
| std::uniform_int_distribution<unsigned int>()( |
| random_engine_) % UINT32_MAX + 1); |
| |
| message.SetTransactionID(transaction_id); |
| message.SetParameterRequestList( |
| std::vector<uint8_t>(kDefaultParameterRequestList, |
| kDefaultParameterRequestList + |
| sizeof(kDefaultParameterRequestList))); |
| ByteString packet; |
| if (!MakeRawPacket(message, &packet)) { |
| LOG(ERROR) << "Failed to serialize a DHCP discover message"; |
| return false; |
| } |
| if (!socket_util_->SendBroadcastPacket(raw_socket_, packet)) { |
| LOG(ERROR) << "Failed to send a DHCP discover packet"; |
| return false; |
| } |
| |
| // Set state variables upon success. |
| state_ = State::SELECT; |
| transaction_id_ = transaction_id; |
| return true; |
| } |
| |
| void DHCPV4::CheckIpCollision() { |
| if (!arp_client_->StartReplyListener()) { |
| LOG(ERROR) << "Failed to start ARP client"; |
| return; |
| } |
| receive_arp_response_handler_.reset( |
| io_handler_factory_->CreateIOReadyHandler( |
| arp_client_->socket(), |
| shill::IOHandler::kModeInput, |
| Bind(&DHCPV4::ArpProbeReplyReceivedTask, |
| weak_ptr_factory_.GetWeakPtr()))); |
| |
| // According to RFC 5227, ARP Probe uses all-zero 'sender address' to avoid |
| // polluting ARP caches. The 'target address' should be set to address being |
| // probed. |
| // RFC 5227 also states that target MAC address should be all-zero. |
| shill::ArpPacket request(shill::IPAddress(IPtoString(INADDR_ANY)), |
| shill::IPAddress(IPtoString(offered_ip_address_)), |
| hardware_address_, |
| ByteString(kZeroHardwareAddress, IFHWADDRLEN)); |
| LOG(INFO) << "Probing expected ip address: " |
| << IPtoString(offered_ip_address_); |
| if (!arp_client_->TransmitRequest(request)) { |
| LOG(ERROR) << "Failed to send ARP Probe request"; |
| arp_client_->Stop(); |
| receive_arp_response_handler_.reset(); |
| return; |
| } |
| // We succeeded to send ARP probe: |
| arp_probe_reply_timeout_task_callback_.Reset( |
| Bind(&DHCPV4::ArpProbeReplyTimeoutTask, weak_ptr_factory_.GetWeakPtr())); |
| event_dispatcher_->PostDelayedTask( |
| arp_probe_reply_timeout_task_callback_.callback(), |
| 1000 * kArpProbeReplyTimeoutSeconds); |
| } |
| |
| void DHCPV4::ArpProbeReplyReceivedTask(int fd) { |
| shill::ArpPacket packet; |
| shill::ByteString sender; |
| if (!arp_client_->ReceivePacket(&packet, &sender)) { |
| return; |
| } |
| // According to RFC 5227, we only check the sender's ip address. |
| shill::IPAddress offered_ip_address = |
| shill::IPAddress(IPtoString(offered_ip_address_)); |
| if (!offered_ip_address.Equals(packet.local_ip_address())) { |
| LOG(ERROR) << "Response is not for the expecte IP address."; |
| return; |
| } |
| |
| // Collision is found: |
| LOG(ERROR) << "IP Collision found. Discard the lease."; |
| arp_client_->Stop(); |
| receive_arp_response_handler_.reset(); |
| arp_probe_reply_timeout_task_callback_.Cancel(); |
| // TODO(nywang): send DHCP_DECLINE to server. |
| ResetState(); |
| } |
| |
| void DHCPV4::ArpProbeReplyTimeoutTask() { |
| // TODO(nywang): Broadcast ARP announcement. |
| arp_client_->Stop(); |
| receive_arp_response_handler_.reset(); |
| arp_probe_reply_timeout_task_callback_.Cancel(); |
| } |
| |
| bool DHCPV4::SendRequest() { |
| LOG(INFO) << __func__; |
| DHCPMessage message; |
| DHCPMessage::InitRequest(&message); |
| message.SetMessageType(kDHCPMessageTypeRequest); |
| message.SetClientHardwareAddress(hardware_address_); |
| if (HasALease()) { |
| message.SetClientIPAddress(client_ip_); |
| } |
| if (state_ == State::SELECT || state_ == State::INIT_REBOOT) { |
| message.SetRequestedIpAddress(offered_ip_address_); |
| } |
| message.SetTransactionID(transaction_id_); |
| if (state_ == State::SELECT) { |
| message.SetServerIdentifier(server_identifier_); |
| } |
| message.SetParameterRequestList( |
| std::vector<uint8_t>(kDefaultParameterRequestList, |
| kDefaultParameterRequestList + |
| sizeof(kDefaultParameterRequestList))); |
| ByteString packet; |
| |
| // Use unicast for renewal. |
| if (state_ == State::BOUND && udp_socket_ != kInvalidSocketDescriptor) { |
| if (!MakePacket(message, &packet)) { |
| LOG(ERROR) << "Failed to make a DHCP request packet"; |
| return false; |
| } |
| if (!socket_util_->SendUnicastPacket(udp_socket_, packet, server_ip_)) { |
| LOG(ERROR) << "Failed to send a DHCP request packet"; |
| return false; |
| } |
| } else { |
| // Use broadcast for other cases. |
| if (!MakeRawPacket(message, &packet)) { |
| LOG(ERROR) << "Failed to make a DHCP request raw packet"; |
| return false; |
| } |
| if (!socket_util_->SendBroadcastPacket(raw_socket_, packet)) { |
| LOG(ERROR) << "Failed to send a DHCP request packet"; |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool DHCPV4::SendRelease() { |
| LOG(INFO) << __func__; |
| DHCPMessage message; |
| DHCPMessage::InitRequest(&message); |
| message.SetMessageType(kDHCPMessageTypeRelease); |
| message.SetClientHardwareAddress(hardware_address_); |
| uint32_t transaction_id = |
| static_cast<uint32_t>( |
| std::uniform_int_distribution<unsigned int>()( |
| random_engine_) % UINT32_MAX + 1); |
| |
| message.SetTransactionID(transaction_id); |
| message.SetServerIdentifier(server_identifier_); |
| ByteString packet; |
| if (!MakePacket(message, &packet)) { |
| LOG(ERROR) << "Failed to make a DHCP release packet"; |
| return false; |
| } |
| // Use unicast for release. |
| if (udp_socket_ == kInvalidSocketDescriptor) { |
| LOG(ERROR) << "Failed to send a DHCP release message becasuse " |
| "there is no socket for unicast"; |
| return false; |
| } |
| if (!socket_util_->SendUnicastPacket(udp_socket_, packet, server_ip_)) { |
| LOG(ERROR) << "Failed to send a DHCP release packet"; |
| return false; |
| } |
| // Set state variables upon success. |
| ResetState(); |
| return true; |
| } |
| |
| bool DHCPV4::MakePacket(const DHCPMessage& message, ByteString* output) { |
| ByteString payload; |
| if (!message.Serialize(&payload)) { |
| LOG(ERROR) << "Failed to serialzie dhcp message"; |
| return false; |
| } |
| *output = payload; |
| return true; |
| } |
| |
| bool DHCPV4::MakeRawPacket(const DHCPMessage& message, ByteString* output) { |
| ByteString payload; |
| if (!message.Serialize(&payload)) { |
| LOG(ERROR) << "Failed to serialzie dhcp message"; |
| return false; |
| } |
| const size_t header_len = sizeof(struct iphdr) + sizeof(struct udphdr); |
| const size_t payload_len = payload.GetLength(); |
| |
| char buffer[header_len + payload_len]; |
| memset(buffer, 0, header_len + payload_len); |
| struct iphdr* ip = reinterpret_cast<struct iphdr*>(buffer); |
| struct udphdr* udp = reinterpret_cast<struct udphdr*>(buffer + sizeof(*ip)); |
| |
| if (!payload.CopyData(payload_len, buffer + header_len)) { |
| LOG(ERROR) << "Failed to copy data from payload"; |
| return false; |
| } |
| udp->uh_sport = htons(kDHCPClientPort); |
| udp->uh_dport = htons(kDHCPServerPort); |
| udp->uh_ulen = |
| htons(static_cast<uint16_t>(sizeof(*udp) + payload.GetLength())); |
| |
| // Fill pseudo header (for UDP checksum computing): |
| // Protocol. |
| ip->protocol = IPPROTO_UDP; |
| // Source IP address. |
| ip->saddr = htonl(client_ip_); |
| // Destination IP address. |
| ip->daddr = htonl(server_ip_); |
| // Total length, use udp packet length for pseudo header. |
| ip->tot_len = udp->uh_ulen; |
| // Calculate udp checksum based on: |
| // IPV4 pseudo header, UDP header, and payload. |
| udp->uh_sum = htons(DHCPMessage::ComputeChecksum( |
| reinterpret_cast<const uint8_t*>(buffer), |
| header_len + payload_len)); |
| |
| // IP version. |
| ip->version = IPVERSION; |
| // IP header length. |
| ip->ihl = sizeof(*ip) >> 2; |
| // Fragment offset field. |
| // The DHCP packet is always smaller than MTU, |
| // so fragmentation is not needed. |
| ip->frag_off = 0; |
| // Identification. |
| ip->id = static_cast<uint16_t>( |
| std::uniform_int_distribution<unsigned int>()( |
| random_engine_) % UINT16_MAX + 1); |
| // Time to live. |
| ip->ttl = IPDEFTTL; |
| // Total length. |
| ip->tot_len = htons(static_cast<uint16_t>(header_len+ payload.GetLength())); |
| // Calculate IP Checksum only based on IP header. |
| ip->check = htons(DHCPMessage::ComputeChecksum( |
| reinterpret_cast<const uint8_t*>(ip), |
| sizeof(*ip))); |
| |
| *output = ByteString(buffer, header_len + payload_len); |
| return true; |
| } |
| |
| |
| bool DHCPV4::ValidateOptions(const DHCPMessage& msg) { |
| uint32_t lease_time = msg.lease_time(); |
| if (lease_time == 0) { |
| LOG(ERROR) << "Reject invalid lease time"; |
| return false; |
| } |
| LOG(INFO) << "Lease Time: " << lease_time; |
| |
| if (msg.subnet_mask() == 0) { |
| LOG(ERROR) << "Reject invalid subnet mask"; |
| return false; |
| } |
| LOG(INFO) << "Subnet Mask: " << IPtoString(msg.subnet_mask()); |
| |
| if (msg.interface_mtu() == 0) { |
| LOG(WARNING) << "Failed to get a valid Interface MTU"; |
| // Shill will use a default MTU |
| // in case no MTU is provided by DHCP. |
| } |
| DLOG(INFO) << "Interface MTU: " << msg.interface_mtu(); |
| |
| if (msg.broadcast_address() == 0) { |
| LOG(WARNING) << "Failed to get a valid Broadcast Address"; |
| // Shill will use a default broadcast address |
| // in case no broadcast address is provided by DHCP. |
| } |
| DLOG(INFO) << "Broadcast Address: " << IPtoString(msg.broadcast_address()); |
| |
| std::vector<uint32_t> router = msg.router(); |
| if (router.size() == 0) { |
| LOG(ERROR) << "Failed to get default gateway address"; |
| return false; |
| } |
| DLOG(INFO) << "Routers:"; |
| for (uint32_t ip : router) { |
| DLOG(INFO) << IPtoString(ip); |
| } |
| |
| std::vector<uint32_t> dns_server = msg.dns_server(); |
| if (dns_server.size() == 0) { |
| LOG(WARNING) << "Failed to get DNS server address"; |
| // Shill will use Google DNS server |
| // in case no DNS server is provided by DHCP. |
| } else { |
| DLOG(INFO) << "DNS Server:"; |
| for (uint32_t ip : dns_server) { |
| DLOG(INFO) << IPtoString(ip); |
| } |
| } |
| return true; |
| } |
| |
| int DHCPV4::ValidatePacketHeader(const unsigned char* buffer, size_t len) { |
| const struct iphdr* ip = |
| reinterpret_cast<const struct iphdr*>(buffer); |
| const size_t ip_header_len = static_cast<size_t>(ip->ihl) << 2; |
| if (ip_header_len < kIPHeaderMinLength || |
| ip_header_len > kIPHeaderMaxLength) { |
| LOG(ERROR) << "Invalid internet header length: " |
| << ip_header_len << " bytes"; |
| return -1; |
| } |
| if (ntohs(ip->tot_len) != len) { |
| LOG(ERROR) << "Invalid IP total length: " << ntohs(ip->tot_len) |
| << " Real packet length: " << len; |
| return -1; |
| } |
| // TODO(nywang): Validate other ip header fields. |
| |
| const struct udphdr* udp = |
| reinterpret_cast<const struct udphdr*>(buffer + ip_header_len); |
| if (ntohs(udp->uh_sport) != kDHCPServerPort || |
| ntohs(udp->uh_dport) != kDHCPClientPort) { |
| LOG(ERROR) << "Invlaid UDP ports"; |
| return -1; |
| } |
| if (ntohs(udp->uh_ulen) != len - ip_header_len) { |
| LOG(ERROR) << "Invalid UDP total length"; |
| return -1; |
| } |
| // TODO(nywang): Validate UDP checksum. |
| |
| return ip_header_len + sizeof(*udp); |
| } |
| |
| void DHCPV4::RenewalTask() { |
| LOG(INFO) << __func__; |
| if (state_ != State::BOUND) { |
| LOG(ERROR) << "Ingore renewal task"; |
| } |
| SendRequest(); |
| state_ = State::RENEW; |
| } |
| |
| void DHCPV4::RebindTask() { |
| LOG(INFO) << __func__; |
| if (state_ != State::RENEW) { |
| LOG(ERROR) << "Ingore rebind task"; |
| } |
| SendRequest(); |
| state_ = State::REBIND; |
| } |
| |
| void DHCPV4::EmitEvent(const std::string& reason) { |
| brillo::VariantDictionary configs; |
| if (reason == kReasonBound) { |
| configs.emplace(kConfigurationKeyIPAddress, client_ip_); |
| configs.emplace(kConfigurationKeyMTU, interface_mtu_); |
| configs.emplace(kConfigurationKeyBroadcastAddress, broadcast_address_); |
| configs.emplace(kConfigurationKeyRouters, router_); |
| configs.emplace(kConfigurationKeyDNS, dns_server_); |
| configs.emplace(kConfigurationKeyVendorEncapsulatedOptions, |
| vendor_specific_info_); |
| configs.emplace(kConfigurationKeyDomainName, domain_name_); |
| uint32_t subnet_cidr = MasktoCIDR(subnet_mask_); |
| configs.emplace(kConfigurationKeySubnetCIDR, subnet_cidr); |
| } |
| adaptor_->EmitEvent(reason, configs); |
| } |
| |
| bool DHCPV4::HasALease() { |
| if (state_ == State::BOUND || |
| state_ == State::RENEW || |
| state_ == State::REBIND) { |
| return true; |
| } |
| return false; |
| } |
| |
| uint32_t DHCPV4::MasktoCIDR(uint32_t subnet_mask) { |
| subnet_mask = ~subnet_mask; |
| uint32_t count = 0; |
| while (subnet_mask & 1) { |
| count++; |
| subnet_mask = subnet_mask >> 1; |
| } |
| return 32 - count; |
| } |
| |
| const std::string DHCPV4::IPtoString(uint32_t ip) { |
| char buffer[INET_ADDRSTRLEN]; |
| ip = htonl(ip); |
| const char* ip_str = inet_ntop(AF_INET, &ip, buffer, sizeof(buffer)); |
| if (ip_str == NULL) { |
| return std::string("invalid ip address"); |
| } |
| return std::string(ip_str); |
| } |
| |
| } // namespace dhcp_client |