Skip to content

Commit

Permalink
Merge pull request #1155 from jetstreamsoft/AF_UNIX
Browse files Browse the repository at this point in the history
Unix domain socket support
  • Loading branch information
Tachi107 authored Aug 9, 2023
2 parents 3ec5a68 + def2367 commit fe5639a
Show file tree
Hide file tree
Showing 9 changed files with 296 additions and 75 deletions.
2 changes: 2 additions & 0 deletions include/pistache/listener.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,8 @@ namespace Pistache::Tcp

TransportFactory defaultTransportFactory() const;

bool bindListener(const struct addrinfo* addr);

void handleNewConnection();
int acceptConnection(struct sockaddr_storage& peer_addr) const;
void dispatchPeer(const std::shared_ptr<Peer>& peer);
Expand Down
62 changes: 55 additions & 7 deletions include/pistache/net.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/un.h>

#ifndef _KERNEL_FASTOPEN
#define _KERNEL_FASTOPEN
Expand All @@ -38,7 +39,7 @@ namespace Pistache
{
public:
// Disable copy and assign.
AddrInfo(const AddrInfo&) = delete;
AddrInfo(const AddrInfo&) = delete;
AddrInfo& operator=(const AddrInfo&) = delete;

// Default construction: do nothing.
Expand Down Expand Up @@ -119,6 +120,12 @@ namespace Pistache
void toNetwork(struct in6_addr*) const;
// Returns 'true' if the system has IPV6 support, false if not.
static bool supported();
// Exposes the underlying socket address as a constant struct sockaddr
// reference.
const struct sockaddr& getSockAddr() const
{
return reinterpret_cast<const struct sockaddr&>(addr_);
}
};
using Ipv4 = IP;
using Ipv6 = IP;
Expand Down Expand Up @@ -146,28 +153,66 @@ namespace Pistache
public:
Address();
Address(std::string host, Port port);

/*
* Constructors for creating addresses from strings. They're
* typically used to create IP-based addresses, but can also be used
* to create unix domain socket addresses. By default the created
* address will be IP-based. However, if the addr argument meets one
* of the criteria below, a unix domain address will result. Note
* that matching such a criterion implies that addr would be invalid
* as an IP-based address.
*
* The criteria are:
* - addr is empty
* - addr[0] == '\0'
* - addr contains a '/' character
*/
explicit Address(std::string addr);
explicit Address(const char* addr);

Address(IP ip, Port port);

Address(const Address& other) = default;
Address(Address&& other) = default;

Address& operator=(const Address& other) = default;
Address& operator=(Address&& other) = default;
Address& operator=(Address&& other) = default;

/*
* Supports the AF_INET, AF_INET6, and AF_UNIX address families.
*/
static Address fromUnix(struct sockaddr* addr);

std::string host() const;
Port port() const;
int family() const;

/*
* Returns the address length to be used in calls to bind(2).
*/
socklen_t addrLen() const
{
return addrLen_;
}

/*
* Exposes the underlying socket address as a constant struct sockaddr
* reference.
*/
const struct sockaddr& getSockAddr() const
{
return ip_.getSockAddr();
}

friend std::ostream& operator<<(std::ostream& os, const Address& address);

private:
void init(const std::string& addr);
static bool isUnixDomain(const std::string& addr);
IP ip_;
Port port_;
socklen_t addrLen_;
};

std::ostream& operator<<(std::ostream& os, const Address& address);
Expand Down Expand Up @@ -223,11 +268,14 @@ namespace Pistache
}
};

#define DEFINE_INTEGRAL_SIZE(Int) \
template <> \
struct Size<Int> \
{ \
size_t operator()(Int val) const { return digitsCount(val); } \
#define DEFINE_INTEGRAL_SIZE(Int) \
template <> \
struct Size<Int> \
{ \
size_t operator()(Int val) const \
{ \
return digitsCount(val); \
} \
}

DEFINE_INTEGRAL_SIZE(uint8_t);
Expand Down
104 changes: 95 additions & 9 deletions src/common/net.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,14 @@ namespace Pistache
ss_in_addr->sin6_flowinfo = in_addr->sin6_flowinfo; /* Should be 0 per RFC 3493 */
memcpy(ss_in_addr->sin6_addr.s6_addr, in_addr->sin6_addr.s6_addr, sizeof(ss_in_addr->sin6_addr.s6_addr));
}
else if (addr->sa_family == AF_UNIX)
{
const struct sockaddr_un* un_addr = reinterpret_cast<const struct sockaddr_un*>(addr);
struct sockaddr_un* ss_un_addr = reinterpret_cast<struct sockaddr_un*>(&addr_);

ss_un_addr->sun_family = un_addr->sun_family;
memcpy(ss_un_addr->sun_path, un_addr->sun_path, sizeof(ss_un_addr->sun_path));
}
else
{
throw std::invalid_argument("Invalid socket family");
Expand Down Expand Up @@ -163,6 +171,12 @@ namespace Pistache
{
return ntohs(reinterpret_cast<const struct sockaddr_in6*>(&addr_)->sin6_port);
}
else if (addr_.ss_family == AF_UNIX)
{
// Ports are a meaningless concept for unix domain sockets. Return
// an arbitrary value.
return 0;
}
else
{
unreachable();
Expand All @@ -171,9 +185,28 @@ namespace Pistache

std::string IP::toString() const
{
if (addr_.ss_family == AF_UNIX)
{
auto& unAddr = reinterpret_cast<const struct sockaddr_un&>(addr_);
if (unAddr.sun_path[0] == '\0')
{
// The socket is abstract (not present in the file system name
// space). Its name starts with the byte following the initial
// NUL. As the name may contain embedded NUL bytes and its
// length is not available here, simply note that it's an
// abstract address.
return std::string("[Abstract]");
}
else
{
return std::string(unAddr.sun_path);
}
}

char buff[INET6_ADDRSTRLEN];
const auto* addr_sa = reinterpret_cast<const struct sockaddr*>(&addr_);
int err = getnameinfo(addr_sa, sizeof(addr_), buff, sizeof(buff), NULL, 0, NI_NUMERICHOST);
int err = getnameinfo(
addr_sa, sizeof(addr_), buff, sizeof(buff), NULL, 0, NI_NUMERICHOST);
if (err) /* [[unlikely]] */
{
throw std::runtime_error(gai_strerror(err));
Expand All @@ -185,7 +218,7 @@ namespace Pistache
{
if (addr_.ss_family != AF_INET)
{
throw std::invalid_argument("Invalid address family");
throw std::invalid_argument("Inapplicable or invalid address family");
}
*out = reinterpret_cast<const struct sockaddr_in*>(&addr_)->sin_addr.s_addr;
}
Expand All @@ -194,7 +227,7 @@ namespace Pistache
{
if (addr_.ss_family != AF_INET)
{
throw std::invalid_argument("Invalid address family");
throw std::invalid_argument("Inapplicable or invalid address family");
}
*out = reinterpret_cast<const struct sockaddr_in6*>(&addr_)->sin6_addr;
}
Expand Down Expand Up @@ -239,7 +272,7 @@ namespace Pistache
{
char normalized_addr[INET6_ADDRSTRLEN];
inet_ntop(AF_INET6, &tmp, normalized_addr, sizeof(normalized_addr));
host_ = normalized_addr;
host_ = normalized_addr;
family_ = AF_INET6;
return;
}
Expand Down Expand Up @@ -298,6 +331,7 @@ namespace Pistache
Address::Address()
: ip_ {}
, port_ { 0 }
, addrLen_(sizeof(struct sockaddr_in6))
{ }

Address::Address(std::string host, Port port)
Expand All @@ -315,18 +349,20 @@ namespace Pistache
Address::Address(IP ip, Port port)
: ip_(ip)
, port_(port)
{ }
{
addrLen_ = ip.getFamily() == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);
}

Address Address::fromUnix(struct sockaddr* addr)
{
if ((addr->sa_family == AF_INET) or (addr->sa_family == AF_INET6))
const auto family = addr->sa_family;
if (family == AF_INET || family == AF_INET6 || family == AF_UNIX)
{
IP ip = IP(addr);
Port port = Port(static_cast<uint16_t>(ip.getPort()));
assert(addr);
Port port = Port(ip.getPort());
return Address(ip, port);
}
throw Error("Not an IP socket");
throw Error("Not an IP or unix domain socket");
}

std::string Address::host() const { return ip_.toString(); }
Expand All @@ -337,6 +373,43 @@ namespace Pistache

void Address::init(const std::string& addr)
{
// Handle unix domain addresses separately.
if (isUnixDomain(addr))
{
struct sockaddr_un unAddr = {};
unAddr.sun_family = AF_UNIX;

// See unix(7) manual page; distinguish among unnamed, abstract,
// and pathname socket addresses.
const auto size = std::min(addr.size(), sizeof unAddr.sun_path);
if (size == 0)
{
addrLen_ = sizeof unAddr.sun_family;
}
else if (addr[0] == '\0')
{
addrLen_ = static_cast<socklen_t>(
sizeof unAddr.sun_family + size);
std::memcpy(unAddr.sun_path, addr.data(), size);
}
else
{
addrLen_ = static_cast<socklen_t>(
offsetof(struct sockaddr_un, sun_path) + size);
std::strncpy(unAddr.sun_path, addr.c_str(), size);
if (size == sizeof unAddr.sun_path)
{
unAddr.sun_path[size - 1] = '\0';
}
}

ip_ = IP(reinterpret_cast<struct sockaddr*>(&unAddr));
port_ = Port(ip_.getPort());
return;
}

addrLen_ = family() == AF_INET ? sizeof(struct sockaddr_in) : sizeof(struct sockaddr_in6);

const AddressParser parser(addr);
const std::string& host = parser.rawHost();
const std::string& port = parser.rawPort();
Expand Down Expand Up @@ -379,6 +452,19 @@ namespace Pistache
}
}

// Applies heuristics to deterimine whether or not addr names a unix
// domain address. If it is zero-length, begins with a NUL byte, or
// contains a '/' character (none of which are possible for legitimate
// IP-based addresses), it's deemed to be a unix domain address.
//
// This heuristic rejects pathname unix domain addresses that contain no
// '/' characters; such addresses tend not to occur in practice. See the
// unix(7) manual page for more infomation.
bool Address::isUnixDomain(const std::string& addr)
{
return addr.size() == 0 || addr[0] == '\0' || addr.find('/') != std::string::npos;
}

std::ostream& operator<<(std::ostream& os, const Address& address)
{
/* As recommended by section 6 of RFC 5952,
Expand Down
34 changes: 23 additions & 11 deletions src/common/peer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,22 +68,34 @@ namespace Pistache::Tcp
{
if (hostname_.empty())
{
char host[NI_MAXHOST];
struct sockaddr_in sa;
sa.sin_family = AF_INET;
if (inet_pton(AF_INET, addr.host().c_str(), &sa.sin_addr) == 0)
if (addr.family() == AF_UNIX)
{
hostname_ = addr.host();
//
// Communication through unix domain sockets is constrained to
// the local host.
//
hostname_.assign("localhost");
}
else
{
if (!getnameinfo(reinterpret_cast<struct sockaddr*>(&sa), sizeof(sa), host, sizeof(host),
nullptr, 0 // Service info
,
NI_NAMEREQD // Raise an error if name resolution failed
))
char host[NI_MAXHOST];
struct sockaddr_in sa;
sa.sin_family = AF_INET;
if (inet_pton(AF_INET, addr.host().c_str(), &sa.sin_addr) == 0)
{
hostname_.assign(static_cast<char*>(host));
hostname_ = addr.host();
}
else
{
if (!getnameinfo(
reinterpret_cast<struct sockaddr*>(&sa),
sizeof(sa), host, sizeof(host),
nullptr, 0, // Service info
NI_NAMEREQD // Raise an error if name resolution failed
))
{
hostname_.assign(static_cast<char*>(host));
}
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/server/endpoint.cc
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ namespace Pistache::Http
if (entry.getTag() == Polling::Tag(timerFd))
{
uint64_t wakeups;
::read(timerFd, &wakeups, sizeof wakeups);
[[maybe_unused]] auto rv = ::read(timerFd, &wakeups, sizeof wakeups);
checkIdlePeers();
break;
}
Expand Down Expand Up @@ -160,7 +160,7 @@ namespace Pistache::Http
void TransportImpl::closePeer(std::shared_ptr<Tcp::Peer>& peer)
{
// true: there is no http request on the keepalive peer -> only call removePeer
// false: there is at least one http request on the peer(keepalive or not) -> send 408 message firsst, then call removePeer
// false: there is at least one http request on the peer(keepalive or not) -> send 408 message first, then call removePeer
if (peer->isIdle())
{
removePeer(peer);
Expand Down
Loading

0 comments on commit fe5639a

Please sign in to comment.