blob: 7be9679579a34530aa7e5a33ecd3114df08a796a [file] [log] [blame]
// Copyright 2018 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 "platform/api/socket.h"
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <cstring>
#include <memory>
#include "platform/api/logging.h"
#include "platform/posix/socket.h"
namespace openscreen {
namespace platform {
namespace {
constexpr bool IsPowerOf2(uint32_t x) {
return (x > 0) && ((x & (x - 1)) == 0);
}
static_assert(IsPowerOf2(alignof(struct cmsghdr)),
"std::align requires power-of-2 alignment");
using IPv4InterfaceIndex = decltype(ip_mreqn().imr_ifindex);
using IPv6InterfaceIndex = decltype(ipv6_mreq().ipv6mr_interface);
int CreateNonBlockingUdpSocket(int domain) {
const int fd = socket(domain, SOCK_DGRAM, 0);
if (fd == -1) {
return -1;
}
// On non-Linux, the SOCK_NONBLOCK option is not available, so use the
// more-portable method of calling fcntl() to set this behavior.
if (fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) == -1) {
close(fd);
return -1;
}
return fd;
}
} // namespace
UdpSocketPtr CreateUdpSocketIPv4() {
const int fd = CreateNonBlockingUdpSocket(AF_INET);
return fd == -1 ? nullptr
: new UdpSocketPrivate{fd, UdpSocketPrivate::Version::kV4};
}
UdpSocketPtr CreateUdpSocketIPv6() {
const int fd = CreateNonBlockingUdpSocket(AF_INET6);
return fd == -1 ? nullptr
: new UdpSocketPrivate{fd, UdpSocketPrivate::Version::kV6};
}
bool IsIPv4Socket(UdpSocketPtr socket) {
return socket && socket->version == UdpSocketPrivate::Version::kV4;
}
bool IsIPv6Socket(UdpSocketPtr socket) {
return socket && socket->version == UdpSocketPrivate::Version::kV6;
}
void DestroyUdpSocket(UdpSocketPtr socket) {
close(socket->fd);
delete socket;
}
bool BindUdpSocket(UdpSocketPtr socket,
const IPEndpoint& endpoint,
InterfaceIndex ifindex) {
OSP_DCHECK_GE(socket->fd, 0);
if (socket->version == UdpSocketPrivate::Version::kV4) {
if (ifindex > 0) {
struct ip_mreqn multicast_properties;
// Appropriate address is set based on |imr_ifindex| when set.
multicast_properties.imr_address.s_addr = INADDR_ANY;
multicast_properties.imr_multiaddr.s_addr = INADDR_ANY;
multicast_properties.imr_ifindex =
static_cast<IPv4InterfaceIndex>(ifindex);
if (setsockopt(socket->fd, IPPROTO_IP, IP_MULTICAST_IF,
&multicast_properties,
sizeof(multicast_properties)) == -1) {
return false;
}
// This is effectively a boolean passed to setsockopt() to allow a future
// bind() on |socket| to succeed, even if the address is already in use.
const int reuse_addr = 1;
if (setsockopt(socket->fd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,
sizeof(reuse_addr)) == -1) {
return false;
}
}
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_port = htons(endpoint.port);
endpoint.address.CopyToV4(
reinterpret_cast<uint8_t*>(&address.sin_addr.s_addr));
return bind(socket->fd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address)) != -1;
} else {
if (ifindex > 0) {
const auto index = static_cast<IPv6InterfaceIndex>(ifindex);
if (setsockopt(socket->fd, IPPROTO_IPV6, IPV6_MULTICAST_IF, &index,
sizeof(index)) == -1) {
return false;
}
// This is effectively a boolean passed to setsockopt() to allow a future
// bind() on |socket| to succeed, even if the address is already in use.
const int reuse_addr = 1;
if (setsockopt(socket->fd, SOL_SOCKET, SO_REUSEADDR, &reuse_addr,
sizeof(reuse_addr)) == -1) {
return false;
}
}
struct sockaddr_in6 address;
address.sin6_family = AF_INET6;
address.sin6_flowinfo = 0;
address.sin6_port = htons(endpoint.port);
endpoint.address.CopyToV6(reinterpret_cast<uint8_t*>(&address.sin6_addr));
address.sin6_scope_id = 0;
return bind(socket->fd, reinterpret_cast<struct sockaddr*>(&address),
sizeof(address)) != -1;
}
}
bool JoinUdpMulticastGroup(UdpSocketPtr socket,
const IPAddress& address,
InterfaceIndex ifindex) {
OSP_DCHECK_GE(socket->fd, 0);
if (socket->version == UdpSocketPrivate::Version::kV4) {
// Passed as data to setsockopt(). 1 means return IP_PKTINFO control data
// in recvmsg() calls.
const int enable_pktinfo = 1;
if (setsockopt(socket->fd, IPPROTO_IP, IP_PKTINFO, &enable_pktinfo,
sizeof(enable_pktinfo)) == -1) {
return false;
}
struct ip_mreqn multicast_properties;
// Appropriate address is set based on |imr_ifindex| when set.
multicast_properties.imr_address.s_addr = INADDR_ANY;
multicast_properties.imr_ifindex = static_cast<IPv4InterfaceIndex>(ifindex);
static_assert(sizeof(multicast_properties.imr_multiaddr) == 4u,
"IPv4 address requires exactly 4 bytes");
address.CopyToV4(
reinterpret_cast<uint8_t*>(&multicast_properties.imr_multiaddr));
if (setsockopt(socket->fd, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&multicast_properties, sizeof(multicast_properties)) == -1) {
return false;
}
return true;
} else {
// Passed as data to setsockopt(). 1 means return IPV6_PKTINFO control data
// in recvmsg() calls.
const int enable_pktinfo = 1;
if (setsockopt(socket->fd, IPPROTO_IPV6, IPV6_RECVPKTINFO, &enable_pktinfo,
sizeof(enable_pktinfo)) == -1) {
return false;
}
struct ipv6_mreq multicast_properties = {
{/* filled-in below */},
static_cast<IPv6InterfaceIndex>(ifindex),
};
static_assert(sizeof(multicast_properties.ipv6mr_multiaddr) == 16u,
"IPv6 address requires exactly 16 bytes");
address.CopyToV6(
reinterpret_cast<uint8_t*>(&multicast_properties.ipv6mr_multiaddr));
// Portability note: All platforms support IPV6_JOIN_GROUP, which is
// synonymous with IPV6_ADD_MEMBERSHIP.
if (setsockopt(socket->fd, IPPROTO_IPV6, IPV6_JOIN_GROUP,
&multicast_properties, sizeof(multicast_properties)) == -1) {
return false;
}
return true;
}
}
int64_t ReceiveUdp(UdpSocketPtr socket,
void* data,
int64_t length,
IPEndpoint* src,
IPEndpoint* original_destination) {
OSP_DCHECK_GE(socket->fd, 0);
struct iovec iov = {data, static_cast<size_t>(length)};
char control_buf[1024];
size_t cmsg_size = sizeof(control_buf) - sizeof(struct cmsghdr) + 1;
void* cmsg_buf = control_buf;
std::align(alignof(struct cmsghdr), sizeof(cmsg_buf), cmsg_buf, cmsg_size);
if (socket->version == UdpSocketPrivate::Version::kV4) {
struct sockaddr_in sa;
struct msghdr msg;
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = cmsg_size;
msg.msg_flags = 0;
int64_t len = recvmsg(socket->fd, &msg, 0);
if (len < 0)
return len;
src->address =
IPAddress(IPAddress::Version::kV4,
reinterpret_cast<const uint8_t*>(&sa.sin_addr.s_addr));
src->port = ntohs(sa.sin_port);
// For multicast sockets, the packet's original destination address may be
// the host address (since we called bind()) but it may also be a multicast
// address. This may be relevant for handling multicast data; specifically,
// mDNSResponder requires this information to work properly.
if (original_destination) {
*original_destination = IPEndpoint{{}, 0};
if ((msg.msg_flags & MSG_CTRUNC) == 0) {
for (struct cmsghdr* cmh = CMSG_FIRSTHDR(&msg); cmh;
cmh = CMSG_NXTHDR(&msg, cmh)) {
if (cmh->cmsg_level != IPPROTO_IP || cmh->cmsg_type != IP_PKTINFO)
continue;
struct sockaddr_in addr;
socklen_t len = sizeof(addr);
if (getsockname(socket->fd, reinterpret_cast<struct sockaddr*>(&addr),
&len) == -1) {
break;
}
// |original_destination->port| will be 0 if this line isn't reached.
original_destination->port = ntohs(addr.sin_port);
struct in_pktinfo* pktinfo =
reinterpret_cast<struct in_pktinfo*>(CMSG_DATA(cmh));
original_destination->address =
IPAddress(IPAddress::Version::kV4,
reinterpret_cast<const uint8_t*>(&pktinfo->ipi_addr));
break;
}
}
}
return len;
} else {
struct sockaddr_in6 sa;
struct msghdr msg;
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsg_buf;
msg.msg_controllen = cmsg_size;
msg.msg_flags = 0;
int64_t len = recvmsg(socket->fd, &msg, 0);
if (len < 0)
return len;
src->address =
IPAddress(IPAddress::Version::kV6,
reinterpret_cast<const uint8_t*>(&sa.sin6_addr.s6_addr));
src->port = ntohs(sa.sin6_port);
// For multicast sockets, the packet's original destination address may be
// the host address (since we called bind()) but it may also be a multicast
// address. This may be relevant for handling multicast data; specifically,
// mDNSResponder requires this information to work properly.
if (original_destination) {
*original_destination = IPEndpoint{{}, 0};
if ((msg.msg_flags & MSG_CTRUNC) == 0) {
for (struct cmsghdr* cmh = CMSG_FIRSTHDR(&msg); cmh;
cmh = CMSG_NXTHDR(&msg, cmh)) {
if (cmh->cmsg_level != IPPROTO_IPV6 ||
cmh->cmsg_type != IPV6_PKTINFO) {
continue;
}
struct sockaddr_in6 addr;
socklen_t len = sizeof(addr);
if (getsockname(socket->fd, reinterpret_cast<struct sockaddr*>(&addr),
&len) == -1) {
break;
}
// |original_destination->port| will be 0 if this line isn't reached.
original_destination->port = ntohs(addr.sin6_port);
struct in6_pktinfo* pktinfo =
reinterpret_cast<struct in6_pktinfo*>(CMSG_DATA(cmh));
original_destination->address =
IPAddress(IPAddress::Version::kV6,
reinterpret_cast<const uint8_t*>(&pktinfo->ipi6_addr));
break;
}
}
}
return len;
}
}
int64_t SendUdp(UdpSocketPtr socket,
const void* data,
int64_t length,
const IPEndpoint& dest) {
OSP_DCHECK_GE(socket->fd, 0);
if (socket->version == UdpSocketPrivate::Version::kV4) {
struct sockaddr_in sa = {
.sin_family = AF_INET,
.sin_port = htons(dest.port),
};
dest.address.CopyToV4(reinterpret_cast<uint8_t*>(&sa.sin_addr.s_addr));
struct iovec iov = {const_cast<void*>(data), static_cast<size_t>(length)};
struct msghdr msg;
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
return sendmsg(socket->fd, &msg, 0);
} else {
struct sockaddr_in6 sa = {
.sin6_family = AF_INET6,
.sin6_port = htons(dest.port),
};
dest.address.CopyToV6(reinterpret_cast<uint8_t*>(&sa.sin6_addr.s6_addr));
struct iovec iov = {const_cast<void*>(data), static_cast<size_t>(length)};
struct msghdr msg;
msg.msg_name = &sa;
msg.msg_namelen = sizeof(sa);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = nullptr;
msg.msg_controllen = 0;
msg.msg_flags = 0;
return sendmsg(socket->fd, &msg, 0);
}
}
} // namespace platform
} // namespace openscreen