| // Copyright 2018 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/icmp6.h> |
| #include <netinet/ip_icmp.h> |
| |
| #include "shill/logging.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), |
| destination_(IPAddress::kFamilyUnknown), |
| interface_index_(-1) {} |
| |
| Icmp::~Icmp() {} |
| |
| bool Icmp::Start(const IPAddress& destination, int interface_index) { |
| if (!destination.IsValid()) { |
| LOG(ERROR) << "Destination address is not valid."; |
| return false; |
| } |
| |
| int socket = -1; |
| if (destination.family() == IPAddress::kFamilyIPv4) { |
| socket = sockets_->Socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); |
| } else if (destination.family() == IPAddress::kFamilyIPv6) { |
| socket = sockets_->Socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); |
| } else { |
| NOTREACHED(); |
| } |
| |
| 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; |
| } |
| |
| destination_ = destination; |
| interface_index_ = interface_index; |
| return true; |
| } |
| |
| void Icmp::Stop() { |
| socket_closer_.reset(); |
| socket_ = -1; |
| } |
| |
| bool Icmp::IsStarted() const { |
| return socket_closer_.get(); |
| } |
| |
| bool Icmp::TransmitV4EchoRequest(uint16_t id, uint16_t seq_num) { |
| 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; |
| } |
| |
| bool Icmp::TransmitV6EchoRequest(uint16_t id, uint16_t seq_num) { |
| struct icmp6_hdr icmp_header; |
| memset(&icmp_header, 0, sizeof(icmp_header)); |
| icmp_header.icmp6_type = ICMP6_ECHO_REQUEST; |
| icmp_header.icmp6_code = kIcmpEchoCode; |
| icmp_header.icmp6_id = id; |
| icmp_header.icmp6_seq = seq_num; |
| // icmp6_cksum is filled in by the kernel for IPPROTO_ICMPV6 sockets |
| // (RFC3542 section 3.1) |
| |
| struct sockaddr_in6 destination_address; |
| memset(&destination_address, 0, sizeof(destination_address)); |
| destination_address.sin6_family = AF_INET6; |
| destination_address.sin6_scope_id = interface_index_; |
| CHECK_EQ(sizeof(destination_address.sin6_addr.s6_addr), |
| destination_.GetLength()); |
| memcpy(&destination_address.sin6_addr.s6_addr, |
| destination_.address().GetConstData(), |
| sizeof(destination_address.sin6_addr.s6_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; |
| } |
| |
| bool Icmp::TransmitEchoRequest(uint16_t id, uint16_t seq_num) { |
| if (!IsStarted()) { |
| return false; |
| } |
| |
| if (destination_.family() == IPAddress::kFamilyIPv4) { |
| return TransmitV4EchoRequest(id, seq_num); |
| } else { |
| return TransmitV6EchoRequest(id, seq_num); |
| } |
| } |
| |
| // 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 |