summaryrefslogtreecommitdiff
path: root/src/socket.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/socket.c')
-rw-r--r--src/socket.c183
1 files changed, 182 insertions, 1 deletions
diff --git a/src/socket.c b/src/socket.c
index 45f4053..ced884a 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -17,8 +17,19 @@
#ifdef _WIN32
# include <ws2tcpip.h>
#else
-# include <sys/socket.h>
# include <netdb.h>
+# include <unistd.h>
+# include <netinet/in.h>
+# include <sys/socket.h>
+#endif
+#ifdef HAVE_SYS_UN_H
+# include <sys/un.h>
+#endif
+#ifdef __linux__
+# include <linux/rtnetlink.h>
+#endif
+#ifdef __OpenBSD__
+# include <net/route.h>
#endif
#include <libHX/proc.h>
#include <libHX/socket.h>
@@ -31,6 +42,9 @@
#if defined(__sunos__) && !defined(SO_PROTOCOL)
# define SO_PROTOCOL SO_PROTOTYPE
#endif
+#ifndef AI_V4MAPPED
+# define AI_V4MAPPED 0
+#endif
static int try_sk_from_env(int fd, const struct addrinfo *ai, const char *intf)
{
@@ -122,3 +136,170 @@ EXPORT_SYMBOL int HX_socket_from_env(const struct addrinfo *ai, const char *intf
errno = ENOENT;
return -1;
}
+
+#ifdef __linux__
+static int linux_sockaddr_local3(int sk, const void *buf, size_t bufsize)
+{
+ if (send(sk, buf, bufsize, 0) < 0)
+ return -errno;
+ char rsp[4096];
+ ssize_t ret = recv(sk, rsp, sizeof(rsp), 0);
+ if (ret < 0)
+ return -errno;
+ else if (static_cast(size_t, ret) < sizeof(struct nlmsghdr))
+ return -EIO;
+ struct nlmsghdr nlh;
+ memcpy(&nlh, rsp, sizeof(nlh));
+ if (!NLMSG_OK(&nlh, ret))
+ return -EIO;
+ const struct rtmsg *rtm = static_cast(void *, rsp + NLMSG_HDRLEN);
+ return rtm->rtm_type == RTN_LOCAL;
+}
+
+static int linux_sockaddr_local2(const struct sockaddr *sa, socklen_t sl)
+{
+ int sk = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+ if (sk < 0)
+ return -errno;
+ struct {
+ struct nlmsghdr nh;
+ struct rtmsg rth;
+ char attrbuf[4096];
+ } req;
+ memset(&req, 0, sizeof(req));
+ req.nh.nlmsg_len = NLMSG_LENGTH(sizeof(req.rth));
+ req.nh.nlmsg_flags = NLM_F_REQUEST;
+ req.nh.nlmsg_type = RTM_GETROUTE;
+ req.rth.rtm_family = sa->sa_family;
+ req.rth.rtm_protocol = RTPROT_UNSPEC;
+ req.rth.rtm_type = RTN_UNSPEC;
+ req.rth.rtm_scope = RT_SCOPE_UNIVERSE;
+ req.rth.rtm_table = RT_TABLE_UNSPEC;
+ struct rtattr *rta = reinterpret_cast(struct rtattr *,
+ reinterpret_cast(char *, &req) + NLMSG_ALIGN(req.nh.nlmsg_len));
+ rta->rta_type = RTA_DST;
+
+ int ret = -ENODATA;
+ if (sa->sa_family == AF_INET6) {
+ const struct in6_addr *ad = &reinterpret_cast(const struct sockaddr_in6 *, sa)->sin6_addr;
+ req.rth.rtm_dst_len = 16;
+ rta->rta_len = RTA_LENGTH(16);
+ req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + rta->rta_len;
+ memcpy(RTA_DATA(rta), ad, 16);
+ } else if (sa->sa_family == AF_INET) {
+ struct in_addr ad = reinterpret_cast(const struct sockaddr_in *, sa)->sin_addr;
+ req.rth.rtm_dst_len = 4;
+ rta->rta_len = RTA_LENGTH(4);
+ req.nh.nlmsg_len = NLMSG_ALIGN(req.nh.nlmsg_len) + rta->rta_len;
+ memcpy(RTA_DATA(rta), &ad, 4);
+ }
+ ret = linux_sockaddr_local3(sk, &req, req.nh.nlmsg_len);
+ close(sk);
+ return ret;
+}
+#endif
+
+#ifdef __OpenBSD__
+static int openbsd_sockaddr_local3(int rsk, const void *buf, size_t bufsize)
+{
+ ssize_t ret = send(rsk, buf, bufsize, 0);
+ if (ret < 0)
+ return -errno;
+ else if (ret != bufsize)
+ return -EIO;
+ struct rt_msghdr rsp;
+ do {
+ ret = recv(rsk, &rsp, sizeof(rsp), 0);
+ } while (ret > 0 && (rsp.rtm_version != RTM_VERSION ||
+ rsp.rtm_seq != 1 || rsp.rtm_pid != getpid()));
+ return rsp.rtm_flags & RTF_LOCAL;
+}
+
+static int openbsd_sockaddr_local2(const struct sockaddr *sa, socklen_t sl)
+{
+ int sk = socket(AF_ROUTE, SOCK_RAW, AF_UNSPEC);
+ if (sk < 0)
+ return -errno;
+ struct {
+ struct rt_msghdr rtm;
+ char ab[512];
+ } req;
+ memset(&req, 0, sizeof(req));
+ req.rtm.rtm_type = RTM_GET;
+ req.rtm.rtm_version = RTM_VERSION;
+ req.rtm.rtm_flags = RTF_STATIC | RTF_UP | RTF_HOST | RTF_GATEWAY;
+ req.rtm.rtm_seq = 1;
+ req.rtm.rtm_addrs = /*RTA_IFP |*/ RTA_DST;
+ req.rtm.rtm_tableid = getrtable();
+ req.rtm.rtm_hdrlen = sizeof(req.rtm);
+ memcpy(req.ab, sa, sl);
+ req.rtm.rtm_msglen = sizeof(req.rtm) + sl;
+ int ret = openbsd_sockaddr_local3(sk, &req, req.rtm.rtm_msglen);
+ close(sk);
+ return ret;
+}
+#endif
+
+EXPORT_SYMBOL int HX_sockaddr_is_local(const struct sockaddr *sa, socklen_t sl,
+ unsigned int flags)
+{
+ struct sockaddr_in xl = {};
+
+ if (sa->sa_family == AF_INET6) {
+ if (sl < sizeof(struct sockaddr_in6))
+ return -EINVAL;
+ } else if (sa->sa_family == AF_INET) {
+ if (sl < sizeof(struct sockaddr_in))
+ return -EINVAL;
+ }
+#ifdef HAVE_SYS_UN_H
+ else if (sa->sa_family == AF_UNIX) {
+ if (sl < sizeof(struct sockaddr_un))
+ return 1;
+ }
+#endif
+ else {
+ return -EPROTONOSUPPORT;
+ }
+ if (flags & AI_V4MAPPED && sa->sa_family == AF_INET6) {
+ /*
+ * Preprocess mapped addresses, becuase kernel interfaces do
+ * not support them.
+ */
+ const struct in6_addr *ad = &reinterpret_cast(const struct sockaddr_in6 *, sa)->sin6_addr;
+ static const uint8_t mappedv4[] = {0,0,0,0, 0,0,0,0, 0,0,0xff,0xff};
+ if (memcmp(ad, mappedv4, 12) == 0) {
+ xl.sin_family = AF_INET;
+ memcpy(&xl.sin_addr, &ad->s6_addr[12], 4);
+ sa = reinterpret_cast(struct sockaddr *, &xl);
+ sl = sizeof(xl);
+ }
+ }
+#if defined(__linux__)
+ return linux_sockaddr_local2(sa, sl);
+#elif defined(__OpenBSD__)
+ return openbsd_sockaddr_local2(sa, sl);
+#else
+ if (sa->sa_family == AF_INET) {
+ struct in_addr a = reinterpret_cast(const struct sockaddr_in *, sa)->sin_addr;
+ return ntohl(a.s_addr) >> 24 == 127;
+ } else if (sa->sa_family == AF_INET6) {
+ return IN6_IS_ADDR_LOOPBACK(&reinterpret_cast(const struct sockaddr_in6 *, sa)->sin6_addr);
+ }
+#endif
+ return -EPROTONOSUPPORT;
+}
+
+EXPORT_SYMBOL int HX_ipaddr_is_local(const char *addr, unsigned int flags)
+{
+ struct addrinfo hints = {.ai_flags = flags & AI_V4MAPPED};
+ struct addrinfo *r = nullptr;
+ int err = getaddrinfo(addr, nullptr, &hints, &r);
+ if (err != 0) {
+ freeaddrinfo(r);
+ return 0;
+ }
+ int lcl = HX_sockaddr_is_local(r->ai_addr, r->ai_addrlen, hints.ai_flags);
+ freeaddrinfo(r);
+ return lcl;
+}