/* This software was written by Dirk Engling It is considered beerware. Prost. Skol. Cheers or whatever. $id$ */ /* System */ #include #include #include #include #include #include /* Opentracker */ #include "ot_ip.h" #include "ot_socket.h" static void sockaddr_from_ip(struct sockaddr_in6 *sa, const ot_ip6 ip, uint16_t port) { memset(sa, 0, sizeof(*sa)); sa->sin6_family = AF_INET6; sa->sin6_port = htons(port); memcpy(&sa->sin6_addr, ip, sizeof(ot_ip6)); } /* Linux reports an IPv4 peer on a dual stack socket as a v4 mapped sockaddr_in6, the BSDs and macOS hand back a plain sockaddr_in instead, so both forms have to be read. */ static void ip_from_sockaddr(ot_ip6 ip, uint16_t *port, const struct sockaddr_storage *ss) { if (ss->ss_family == AF_INET) { const struct sockaddr_in *sa = (const struct sockaddr_in *)ss; memcpy(ip, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); memcpy(ip + sizeof(ip_v4mapped_prefix), &sa->sin_addr, 4); *port = ntohs(sa->sin_port); } else { const struct sockaddr_in6 *sa = (const struct sockaddr_in6 *)ss; memcpy(ip, &sa->sin6_addr, sizeof(ot_ip6)); *port = ntohs(sa->sin6_port); } } static int set_nonblocking(int s) { int flags = fcntl(s, F_GETFL, 0); if (flags == -1) return -1; return fcntl(s, F_SETFL, flags | O_NONBLOCK); } /* Close without letting the failure we are about to report be overwritten */ static int close_and_fail(int s) { int saved = errno; close(s); errno = saved; return -1; } static int socket_v6(int type) { int s = socket(PF_INET6, type, 0); int zero = 0; if (s == -1) return -1; /* A tracker that answered only one family would be half a tracker */ if (setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1) return close_and_fail(s); if (set_nonblocking(s) == -1) return close_and_fail(s); return s; } /* The only place in the tree that needs to know what a kernel calls this */ static void socket_set_sharing(int s, socket_sharing sharing) { int one = 1; setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); #ifdef SO_REUSEPORT if (sharing == SOCKET_SHARED) setsockopt(s, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)); #endif } static int socket_bind_v6(int s, const ot_ip6 ip, uint16_t port, socket_sharing sharing) { struct sockaddr_in6 sa; socket_set_sharing(s, sharing); sockaddr_from_ip(&sa, ip, port); return bind(s, (struct sockaddr *)&sa, sizeof(sa)); } int socket_tcp_listen(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { int s = socket_v6(SOCK_STREAM); if (s == -1) return -1; if (socket_bind_v6(s, ip, port, sharing) == -1 || listen(s, SOMAXCONN) == -1) return close_and_fail(s); return s; } int socket_udp_bind(const ot_ip6 ip, uint16_t port, socket_sharing sharing) { int s = socket_v6(SOCK_DGRAM); if (s == -1) return -1; if (socket_bind_v6(s, ip, port, sharing) == -1) return close_and_fail(s); return s; } int socket_tcp_connect(const ot_ip6 srcip, uint16_t srcport, const ot_ip6 dstip, uint16_t dstport) { struct sockaddr_in6 sa; int s = socket_v6(SOCK_STREAM); if (s == -1) return -1; if (socket_bind_v6(s, srcip, srcport, SOCKET_EXCLUSIVE) == -1) return close_and_fail(s); sockaddr_from_ip(&sa, dstip, dstport); if (connect(s, (struct sockaddr *)&sa, sizeof(sa)) == -1 && errno != EINPROGRESS && errno != EWOULDBLOCK) return close_and_fail(s); return s; } int socket_is_connected(int s) { int error = 0; socklen_t len = sizeof(error); if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &len) == -1) return 0; if (error) { errno = error; return 0; } return 1; } int socket_accept(int s, ot_ip6 ip, uint16_t *port) { struct sockaddr_storage ss; socklen_t len = sizeof(ss); int fd; memset(&ss, 0, sizeof(ss)); if ((fd = accept(s, (struct sockaddr *)&ss, &len)) == -1) return -1; /* Linux hands over a blocking socket whatever the listening socket was, the BSDs and macOS inherit the listener's flags, so say which one we want. */ if (set_nonblocking(fd) == -1) return close_and_fail(fd); ip_from_sockaddr(ip, port, &ss); return fd; } ssize_t socket_recv(int s, void *buf, size_t len, ot_ip6 ip, uint16_t *port) { struct sockaddr_storage ss; socklen_t sslen = sizeof(ss); ssize_t received; memset(&ss, 0, sizeof(ss)); received = recvfrom(s, buf, len, 0, (struct sockaddr *)&ss, &sslen); if (received == -1) return -1; ip_from_sockaddr(ip, port, &ss); return received; } ssize_t socket_send(int s, const void *buf, size_t len, const ot_ip6 ip, uint16_t port) { struct sockaddr_in6 sa; sockaddr_from_ip(&sa, ip, port); return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa)); } #ifdef WANT_SYNC_LIVE static void sockaddr4_from_ip(struct sockaddr_in *sa, const ot_ip6 ip, uint16_t port) { memset(sa, 0, sizeof(*sa)); sa->sin_family = AF_INET; sa->sin_port = htons(port); memcpy(&sa->sin_addr, ip + sizeof(ip_v4mapped_prefix), 4); } /* A multicast group is meant to have several listeners, so these are always shared */ static int socket_v4_bind(const ot_ip6 ip, uint16_t port) { struct sockaddr_in sa; int s; if ((s = socket(PF_INET, SOCK_DGRAM, 0)) == -1) return -1; socket_set_sharing(s, SOCKET_SHARED); sockaddr4_from_ip(&sa, ip, port); if (bind(s, (struct sockaddr *)&sa, sizeof(sa)) == -1) return close_and_fail(s); return s; } int socket_mcast_join(const ot_ip6 group, const ot_ip6 interface, uint16_t port) { struct ip_mreq mreq; ot_ip6 any; int s; /* The group is what is listened to, so the socket takes the wildcard and the join names the interface to receive it on */ memcpy(any, ip_v4mapped_prefix, sizeof(ip_v4mapped_prefix)); memset(any + sizeof(ip_v4mapped_prefix), 0, 4); if ((s = socket_v4_bind(any, port)) == -1) return -1; memset(&mreq, 0, sizeof(mreq)); memcpy(&mreq.imr_multiaddr, group + sizeof(ip_v4mapped_prefix), 4); memcpy(&mreq.imr_interface, interface + sizeof(ip_v4mapped_prefix), 4); if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) == -1) return close_and_fail(s); return s; } int socket_mcast_bind(const ot_ip6 interface, uint16_t port, int loopback) { /* The BSDs and macOS want a byte for the IPv4 multicast options, Linux accepts either that or an int. */ unsigned char ttl = 1, loop = (unsigned char)(loopback != 0); int s = socket_v4_bind(interface, port); if (s == -1) return -1; if (set_nonblocking(s) == -1) return close_and_fail(s); if (setsockopt(s, IPPROTO_IP, IP_MULTICAST_TTL, &ttl, sizeof(ttl)) == -1 || setsockopt(s, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) == -1) return close_and_fail(s); return s; } ssize_t socket_mcast_send(int s, const void *buf, size_t len, const ot_ip6 group, uint16_t port) { struct sockaddr_in sa; sockaddr4_from_ip(&sa, group, port); return sendto(s, buf, len, 0, (struct sockaddr *)&sa, sizeof(sa)); } #endif