diff options
Diffstat (limited to 'src/socket.c')
-rw-r--r-- | src/socket.c | 183 |
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; +} |