| // |
| // 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/filter.h> |
| #include <linux/if_packet.h> |
| #include <net/ethernet.h> |
| #include <net/if.h> |
| #include <net/if_arp.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" |
| |
| using base::Bind; |
| using base::Unretained; |
| using shill::ByteString; |
| using shill::IOHandlerFactoryContainer; |
| |
| namespace dhcp_client { |
| |
| namespace { |
| // UDP port numbers for DHCP. |
| const uint16_t kDHCPServerPort = 67; |
| const uint16_t kDHCPClientPort = 68; |
| // 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 = -1; |
| // 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, |
| kDHCPOptionRouter, |
| kDHCPOptionDNSServer, |
| kDHCPOptionDomainName, |
| kDHCPOptionLeaseTime, |
| kDHCPOptionRenewalTime, |
| kDHCPOptionRebindingTime |
| }; |
| |
| // Socket filter for dhcp packet. |
| const sock_filter dhcp_bpf_filter[] = { |
| BPF_STMT(BPF_LD + BPF_B + BPF_ABS, 23 - ETH_HLEN), |
| BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 6), |
| BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20 - ETH_HLEN), |
| BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 4, 0), |
| BPF_STMT(BPF_LDX + BPF_B + BPF_MSH, 14 - ETH_HLEN), |
| BPF_STMT(BPF_LD + BPF_H + BPF_IND, 16 - ETH_HLEN), |
| BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, kDHCPClientPort, 0, 1), |
| BPF_STMT(BPF_RET + BPF_K, 0x0fffffff), |
| BPF_STMT(BPF_RET + BPF_K, 0), |
| }; |
| |
| const int dhcp_bpf_filter_len = |
| sizeof(dhcp_bpf_filter) / sizeof(dhcp_bpf_filter[0]); |
| |
| // 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 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), |
| event_dispatcher_(event_dispatcher), |
| io_handler_factory_( |
| IOHandlerFactoryContainer::GetInstance()->GetIOHandlerFactory()), |
| weak_ptr_factory_(this), |
| state_(State::INIT), |
| socket_(kInvalidSocketDescriptor), |
| sockets_(new shill::Sockets()), |
| random_engine_(time(nullptr)) { |
| ResetStateVariables(); |
| } |
| |
| DHCPV4::~DHCPV4() { |
| if (socket_ != kInvalidSocketDescriptor) { |
| Stop(); |
| } |
| } |
| |
| void DHCPV4::ResetStateVariables() { |
| state_ = State::INIT; |
| server_identifier_ = 0; |
| transaction_id_ = 0; |
| offered_ip_address_ = 0; |
| client_ip_ = INADDR_ANY; |
| server_ip_ = INADDR_BROADCAST; |
| } |
| |
| 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 (!CreateRawSocket()) { |
| return false; |
| } |
| input_handler_.reset(io_handler_factory_->CreateIOInputHandler( |
| 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()) { |
| ResetStateVariables(); |
| 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 (!SendRelease()) { |
| LOG(ERROR) << "Failed to send DHCP release message"; |
| } |
| if (socket_ != kInvalidSocketDescriptor) { |
| sockets_->Close(socket_); |
| socket_ = kInvalidSocketDescriptor; |
| } |
| } |
| |
| bool DHCPV4::CreateRawSocket() { |
| int fd = sockets_->Socket(PF_PACKET, |
| SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, |
| htons(ETHERTYPE_IP)); |
| if (fd == kInvalidSocketDescriptor) { |
| PLOG(ERROR) << "Failed to create socket"; |
| return false; |
| } |
| shill::ScopedSocketCloser socket_closer(sockets_.get(), fd); |
| |
| // Apply the socket filter. |
| sock_fprog pf; |
| memset(&pf, 0, sizeof(pf)); |
| pf.filter = const_cast<sock_filter*>(dhcp_bpf_filter); |
| pf.len = dhcp_bpf_filter_len; |
| |
| if (sockets_->AttachFilter(fd, &pf) != 0) { |
| PLOG(ERROR) << "Failed to attach filter"; |
| return false; |
| } |
| |
| if (sockets_->ReuseAddress(fd) == -1) { |
| PLOG(ERROR) << "Failed to reuse socket address"; |
| return false; |
| } |
| |
| if (sockets_->BindToDevice(fd, interface_name_) < 0) { |
| PLOG(ERROR) << "Failed to bind socket to device"; |
| return false; |
| } |
| |
| struct sockaddr_ll local; |
| memset(&local, 0, sizeof(local)); |
| local.sll_family = PF_PACKET; |
| local.sll_protocol = htons(ETHERTYPE_IP); |
| local.sll_ifindex = static_cast<int>(interface_index_); |
| |
| if (sockets_->Bind(fd, |
| reinterpret_cast<struct sockaddr*>(&local), |
| sizeof(local)) < 0) { |
| PLOG(ERROR) << "Failed to bind to address"; |
| return false; |
| } |
| |
| socket_ = socket_closer.Release(); |
| return true; |
| } |
| |
| 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; |
| } |
| |
| // TODO(nywang): Validate the ip address using ARP. |
| // 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(); |
| 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); |
| // TODO(nywang): Setup a udp socket for future unicast, so that kernel can |
| // fill the ethernet header with gateway mac address for us. |
| } |
| |
| 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. |
| ResetStateVariables(); |
| |
| 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 (!SendRawPacket(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; |
| } |
| |
| bool DHCPV4::SendRequest() { |
| LOG(INFO) << __func__; |
| DHCPMessage message; |
| DHCPMessage::InitRequest(&message); |
| message.SetMessageType(kDHCPMessageTypeRequest); |
| message.SetClientHardwareAddress(hardware_address_); |
| if (state_ == State::BOUND || |
| state_ == State::RENEW || |
| state_ == State::REBIND) { |
| 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; |
| if (!MakeRawPacket(message, &packet)) { |
| LOG(ERROR) << "Failed to serialize a DHCP request message"; |
| return false; |
| } |
| // TODO(nywang): Use unicast for renewal. |
| if (!SendRawPacket(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 (!MakeRawPacket(message, &packet)) { |
| LOG(ERROR) << "Failed to serialize a DHCP release message"; |
| return false; |
| } |
| if (!SendRawPacket(packet)) { |
| LOG(ERROR) << "Failed to send a DHCP release packet"; |
| return false; |
| } |
| // Set state variables upon success. |
| ResetStateVariables(); |
| 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::SendRawPacket(const ByteString& packet) { |
| LOG(INFO) << __func__; |
| struct sockaddr_ll remote; |
| memset(&remote, 0, sizeof(remote)); |
| remote.sll_family = AF_PACKET; |
| remote.sll_protocol = htons(ETHERTYPE_IP); |
| remote.sll_ifindex = interface_index_; |
| remote.sll_hatype = htons(ARPHRD_ETHER); |
| // Use broadcast hardware address. |
| remote.sll_halen = IFHWADDRLEN; |
| memset(remote.sll_addr, 0xff, IFHWADDRLEN); |
| |
| size_t result = sockets_->SendTo(socket_, |
| packet.GetConstData(), |
| packet.GetLength(), |
| 0, |
| reinterpret_cast<struct sockaddr *>(&remote), |
| sizeof(remote)); |
| |
| if (result != packet.GetLength()) { |
| PLOG(ERROR) << "Socket sento failed"; |
| return false; |
| } |
| 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. |
| } |
| LOG(INFO) << "Interface MTU: " << msg.interface_mtu(); |
| |
| std::vector<uint32_t> router = msg.router(); |
| if (router.size() == 0) { |
| LOG(ERROR) << "Failed to get default gateway address"; |
| return false; |
| } |
| LOG(INFO) << "Routers:"; |
| for (uint32_t ip : router) { |
| LOG(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 { |
| LOG(INFO) << "DNS Server:"; |
| for (uint32_t ip : dns_server) { |
| LOG(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"; |
| } |
| if (!SendRequest()) { |
| return; |
| } |
| state_ = State::RENEW; |
| } |
| |
| void DHCPV4::RebindTask() { |
| LOG(INFO) << __func__; |
| if (state_ != State::RENEW) { |
| LOG(ERROR) << "Ingore rebind task"; |
| } |
| if (!SendRequest()) { |
| return; |
| } |
| 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(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); |
| } |
| |
| 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 |
| |