blob: 87071a681ae8c0f1cb6c535f2b95eae29901dae8 [file] [log] [blame]
// Copyright (c) 2012 The Chromium 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 "net/tools/quic/quic_socket_utils.h"
#include <errno.h>
#include <netinet/in.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/uio.h>
#include <string>
#include "base/logging.h"
#include "net/quic/quic_protocol.h"
#ifndef SO_RXQ_OVFL
#define SO_RXQ_OVFL 40
#endif
namespace net {
namespace tools {
// static
IPAddressNumber QuicSocketUtils::GetAddressFromMsghdr(struct msghdr *hdr) {
if (hdr->msg_controllen > 0) {
for (cmsghdr* cmsg = CMSG_FIRSTHDR(hdr);
cmsg != NULL;
cmsg = CMSG_NXTHDR(hdr, cmsg)) {
const uint8* addr_data = NULL;
int len = 0;
if (cmsg->cmsg_type == IPV6_PKTINFO) {
in6_pktinfo* info = reinterpret_cast<in6_pktinfo*>CMSG_DATA(cmsg);
in6_addr addr = info->ipi6_addr;
addr_data = reinterpret_cast<const uint8*>(&addr);
len = sizeof(addr);
} else if (cmsg->cmsg_type == IP_PKTINFO) {
in_pktinfo* info = reinterpret_cast<in_pktinfo*>CMSG_DATA(cmsg);
in_addr addr = info->ipi_addr;
addr_data = reinterpret_cast<const uint8*>(&addr);
len = sizeof(addr);
} else {
continue;
}
return IPAddressNumber(addr_data, addr_data + len);
}
}
DCHECK(false) << "Unable to get address from msghdr";
return IPAddressNumber();
}
// static
bool QuicSocketUtils::GetOverflowFromMsghdr(struct msghdr *hdr,
int *dropped_packets) {
if (hdr->msg_controllen > 0) {
struct cmsghdr *cmsg;
for (cmsg = CMSG_FIRSTHDR(hdr);
cmsg != NULL;
cmsg = CMSG_NXTHDR(hdr, cmsg)) {
if (cmsg->cmsg_type == SO_RXQ_OVFL) {
*dropped_packets = *(reinterpret_cast<int*>CMSG_DATA(cmsg));
return true;
}
}
}
return false;
}
// static
int QuicSocketUtils::SetGetAddressInfo(int fd, int address_family) {
int get_local_ip = 1;
if (address_family == AF_INET) {
return setsockopt(fd, IPPROTO_IP, IP_PKTINFO,
&get_local_ip, sizeof(get_local_ip));
} else {
return setsockopt(fd, IPPROTO_IPV6, IPV6_RECVPKTINFO,
&get_local_ip, sizeof(get_local_ip));
}
}
// static
int QuicSocketUtils::ReadPacket(int fd, char* buffer, size_t buf_len,
int* dropped_packets,
IPAddressNumber* self_address,
IPEndPoint* peer_address) {
CHECK(peer_address != NULL);
const int kSpaceForOverflowAndIp =
CMSG_SPACE(sizeof(int)) + CMSG_SPACE(sizeof(in6_pktinfo));
char cbuf[kSpaceForOverflowAndIp];
memset(cbuf, 0, arraysize(cbuf));
iovec iov = {buffer, buf_len};
struct sockaddr_storage raw_address;
msghdr hdr;
hdr.msg_name = &raw_address;
hdr.msg_namelen = sizeof(sockaddr_storage);
hdr.msg_iov = &iov;
hdr.msg_iovlen = 1;
hdr.msg_flags = 0;
struct cmsghdr *cmsg = (struct cmsghdr *) cbuf;
cmsg->cmsg_len = arraysize(cbuf);
hdr.msg_control = cmsg;
hdr.msg_controllen = arraysize(cbuf);
int bytes_read = recvmsg(fd, &hdr, 0);
// Return before setting dropped packets: if we get EAGAIN, it will
// be 0.
if (bytes_read < 0 && errno != 0) {
if (errno != EAGAIN) {
LOG(ERROR) << "Error reading " << strerror(errno);
}
return -1;
}
if (dropped_packets != NULL) {
GetOverflowFromMsghdr(&hdr, dropped_packets);
}
if (self_address != NULL) {
*self_address = QuicSocketUtils::GetAddressFromMsghdr(&hdr);
}
if (raw_address.ss_family == AF_INET) {
CHECK(peer_address->FromSockAddr(
reinterpret_cast<const sockaddr*>(&raw_address),
sizeof(struct sockaddr_in)));
} else if (raw_address.ss_family == AF_INET6) {
CHECK(peer_address->FromSockAddr(
reinterpret_cast<const sockaddr*>(&raw_address),
sizeof(struct sockaddr_in6)));
}
return bytes_read;
}
// static
WriteResult QuicSocketUtils::WritePacket(int fd,
const char* buffer,
size_t buf_len,
const IPAddressNumber& self_address,
const IPEndPoint& peer_address) {
sockaddr_storage raw_address;
socklen_t address_len = sizeof(raw_address);
CHECK(peer_address.ToSockAddr(
reinterpret_cast<struct sockaddr*>(&raw_address),
&address_len));
iovec iov = {const_cast<char*>(buffer), buf_len};
msghdr hdr;
hdr.msg_name = &raw_address;
hdr.msg_namelen = address_len;
hdr.msg_iov = &iov;
hdr.msg_iovlen = 1;
hdr.msg_flags = 0;
const int kSpaceForIpv4 = CMSG_SPACE(sizeof(in_pktinfo));
const int kSpaceForIpv6 = CMSG_SPACE(sizeof(in6_pktinfo));
// kSpaceForIp should be big enough to hold both IPv4 and IPv6 packet info.
const int kSpaceForIp =
(kSpaceForIpv4 < kSpaceForIpv6) ? kSpaceForIpv6 : kSpaceForIpv4;
char cbuf[kSpaceForIp];
if (self_address.empty()) {
hdr.msg_control = 0;
hdr.msg_controllen = 0;
} else if (GetAddressFamily(self_address) == ADDRESS_FAMILY_IPV4) {
hdr.msg_control = cbuf;
hdr.msg_controllen = kSpaceForIp;
cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(in_pktinfo));
cmsg->cmsg_level = IPPROTO_IP;
cmsg->cmsg_type = IP_PKTINFO;
in_pktinfo* pktinfo = reinterpret_cast<in_pktinfo*>(CMSG_DATA(cmsg));
memset(pktinfo, 0, sizeof(in_pktinfo));
pktinfo->ipi_ifindex = 0;
memcpy(&pktinfo->ipi_spec_dst, &self_address[0], self_address.size());
hdr.msg_controllen = cmsg->cmsg_len;
} else {
hdr.msg_control = cbuf;
hdr.msg_controllen = kSpaceForIp;
cmsghdr* cmsg = CMSG_FIRSTHDR(&hdr);
cmsg->cmsg_len = CMSG_LEN(sizeof(in6_pktinfo));
cmsg->cmsg_level = IPPROTO_IPV6;
cmsg->cmsg_type = IPV6_PKTINFO;
in6_pktinfo* pktinfo = reinterpret_cast<in6_pktinfo*>(CMSG_DATA(cmsg));
memset(pktinfo, 0, sizeof(in6_pktinfo));
memcpy(&pktinfo->ipi6_addr, &self_address[0], self_address.size());
hdr.msg_controllen = cmsg->cmsg_len;
}
int rc = sendmsg(fd, &hdr, 0);
if (rc >= 0) {
return WriteResult(WRITE_STATUS_OK, rc);
}
return WriteResult((errno == EAGAIN || errno == EWOULDBLOCK) ?
WRITE_STATUS_BLOCKED : WRITE_STATUS_ERROR, errno);
}
} // namespace tools
} // namespace net