| // Copyright (c) 2013 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "shill/icmp.h" |
| |
| #include <netinet/ip_icmp.h> |
| |
| #include "shill/logging.h" |
| #include "shill/net/ip_address.h" |
| #include "shill/net/sockets.h" |
| |
| namespace shill { |
| |
| const int Icmp::kIcmpEchoCode = 0; // value specified in RFC 792. |
| |
| Icmp::Icmp() |
| : sockets_(new Sockets()), |
| socket_(-1) {} |
| |
| Icmp::~Icmp() {} |
| |
| bool Icmp::Start() { |
| int socket = sockets_->Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); |
| if (socket == -1) { |
| PLOG(ERROR) << "Could not create ICMP socket"; |
| Stop(); |
| return false; |
| } |
| socket_ = socket; |
| socket_closer_.reset(new ScopedSocketCloser(sockets_.get(), socket_)); |
| |
| if (sockets_->SetNonBlocking(socket_) != 0) { |
| PLOG(ERROR) << "Could not set socket to be non-blocking"; |
| Stop(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| void Icmp::Stop() { |
| socket_closer_.reset(); |
| socket_ = -1; |
| } |
| |
| bool Icmp::IsStarted() const { |
| return socket_closer_.get(); |
| } |
| |
| bool Icmp::TransmitEchoRequest(const IPAddress& destination, uint16_t id, |
| uint16_t seq_num) { |
| if (!IsStarted() && !Start()) { |
| return false; |
| } |
| |
| if (!destination.IsValid()) { |
| LOG(ERROR) << "Destination address is not valid."; |
| return false; |
| } |
| |
| if (destination.family() != IPAddress::kFamilyIPv4) { |
| NOTIMPLEMENTED() << "Only IPv4 destination addresses are implemented."; |
| return false; |
| } |
| |
| struct icmphdr icmp_header; |
| memset(&icmp_header, 0, sizeof(icmp_header)); |
| icmp_header.type = ICMP_ECHO; |
| icmp_header.code = kIcmpEchoCode; |
| icmp_header.un.echo.id = id; |
| icmp_header.un.echo.sequence = seq_num; |
| icmp_header.checksum = ComputeIcmpChecksum(icmp_header, sizeof(icmp_header)); |
| |
| struct sockaddr_in destination_address; |
| destination_address.sin_family = AF_INET; |
| CHECK_EQ(sizeof(destination_address.sin_addr.s_addr), |
| destination.GetLength()); |
| memcpy(&destination_address.sin_addr.s_addr, |
| destination.address().GetConstData(), |
| sizeof(destination_address.sin_addr.s_addr)); |
| |
| int result = sockets_->SendTo( |
| socket_, |
| &icmp_header, |
| sizeof(icmp_header), |
| 0, |
| reinterpret_cast<struct sockaddr*>(&destination_address), |
| sizeof(destination_address)); |
| int expected_result = sizeof(icmp_header); |
| if (result != expected_result) { |
| if (result < 0) { |
| PLOG(ERROR) << "Socket sendto failed"; |
| } else if (result < expected_result) { |
| LOG(ERROR) << "Socket sendto returned " |
| << result |
| << " which is less than the expected result " |
| << expected_result; |
| } |
| return false; |
| } |
| |
| return true; |
| } |
| |
| // static |
| uint16_t Icmp::ComputeIcmpChecksum(const struct icmphdr& hdr, size_t len) { |
| // Compute Internet Checksum for "len" bytes beginning at location "hdr". |
| // Adapted directly from the canonical implementation in RFC 1071 Section 4.1. |
| uint32_t sum = 0; |
| const uint16_t* addr = reinterpret_cast<const uint16_t*>(&hdr); |
| |
| while (len > 1) { |
| sum += *addr; |
| ++addr; |
| len -= sizeof(*addr); |
| } |
| |
| // Add left-over byte, if any. |
| if (len > 0) { |
| sum += *reinterpret_cast<const uint8_t*>(addr); |
| } |
| |
| // Fold 32-bit sum to 16 bits. |
| while (sum >> 16) { |
| sum = (sum & 0xffff) + (sum >> 16); |
| } |
| |
| return static_cast<uint16_t>(~sum); |
| } |
| |
| } // namespace shill |