blob: b69fe9cf229d87fcded61167f48ae54bbb241875 [file] [log] [blame]
//
// Copyright (C) 2013 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 "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