|
| 1 | +// v6to4 is an LD_PRELOAD library that lets a program which insists on IPv6 |
| 2 | +// sockets run on a kernel built without IPv6, where socket(AF_INET6, ...) |
| 3 | +// fails with EAFNOSUPPORT. sqlc-test-setup preloads it into Spanner Omni on |
| 4 | +// such kernels only, since Omni's services open IPv6 sockets and abort when |
| 5 | +// they cannot; see omni_ipv6.go. |
| 6 | +// |
| 7 | +// An AF_INET6 socket is created as an AF_INET one and remembered. Addresses |
| 8 | +// passed in are rewritten from sockaddr_in6 to sockaddr_in (:: to 0.0.0.0, |
| 9 | +// ::1 to 127.0.0.1, ::ffff:a.b.c.d to a.b.c.d), and addresses handed back |
| 10 | +// are rewritten the other way, so the program goes on seeing IPv6. An |
| 11 | +// address with no IPv4 equivalent fails with EADDRNOTAVAIL. |
| 12 | +// |
| 13 | +// Omni runs on its own glibc 2.27, so the library calls the kernel through |
| 14 | +// syscall() rather than looking up the real functions, and must not need a |
| 15 | +// glibc symbol newer than that: build it without _FORTIFY_SOURCE or the |
| 16 | +// stack protector. |
| 17 | +#define _GNU_SOURCE |
| 18 | +#include <errno.h> |
| 19 | +#include <netinet/in.h> |
| 20 | +#include <string.h> |
| 21 | +#include <sys/socket.h> |
| 22 | +#include <sys/syscall.h> |
| 23 | +#include <unistd.h> |
| 24 | + |
| 25 | +#define MAXFD 65536 |
| 26 | +static unsigned char fake[MAXFD]; |
| 27 | + |
| 28 | +static int isfake(int fd) { return fd >= 0 && fd < MAXFD && fake[fd]; } |
| 29 | +static void mark(int fd, int v) { if (fd >= 0 && fd < MAXFD) fake[fd] = v; } |
| 30 | + |
| 31 | +// to4 rewrites a sockaddr_in6 as a sockaddr_in in out; it returns the length to |
| 32 | +// pass on, or -1 when the address has no IPv4 equivalent. |
| 33 | +static socklen_t to4(const struct sockaddr *a, socklen_t len, struct sockaddr_in *out) { |
| 34 | + const struct sockaddr_in6 *s6 = (const struct sockaddr_in6 *)a; |
| 35 | + static const unsigned char any[16] = {0}; |
| 36 | + static const unsigned char lo[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1}; |
| 37 | + memset(out, 0, sizeof *out); |
| 38 | + out->sin_family = AF_INET; |
| 39 | + out->sin_port = s6->sin6_port; |
| 40 | + const unsigned char *b = s6->sin6_addr.s6_addr; |
| 41 | + if (memcmp(b, any, 16) == 0) out->sin_addr.s_addr = htonl(INADDR_ANY); |
| 42 | + else if (memcmp(b, lo, 16) == 0) out->sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
| 43 | + else if (memcmp(b, any, 10) == 0 && b[10] == 0xff && b[11] == 0xff) memcpy(&out->sin_addr, b + 12, 4); |
| 44 | + else return (socklen_t)-1; |
| 45 | + (void)len; |
| 46 | + return sizeof *out; |
| 47 | +} |
| 48 | + |
| 49 | +// to6 rewrites the sockaddr_in the kernel wrote into a v4-mapped sockaddr_in6. |
| 50 | +static void to6(struct sockaddr *a, socklen_t *len, socklen_t cap) { |
| 51 | + if (!a || !len || *len < sizeof(struct sockaddr_in) || a->sa_family != AF_INET) return; |
| 52 | + struct sockaddr_in s4; |
| 53 | + memcpy(&s4, a, sizeof s4); |
| 54 | + struct sockaddr_in6 s6; |
| 55 | + memset(&s6, 0, sizeof s6); |
| 56 | + s6.sin6_family = AF_INET6; |
| 57 | + s6.sin6_port = s4.sin_port; |
| 58 | + if (s4.sin_addr.s_addr == htonl(INADDR_LOOPBACK)) s6.sin6_addr.s6_addr[15] = 1; |
| 59 | + else if (s4.sin_addr.s_addr != htonl(INADDR_ANY)) { |
| 60 | + s6.sin6_addr.s6_addr[10] = 0xff; |
| 61 | + s6.sin6_addr.s6_addr[11] = 0xff; |
| 62 | + memcpy(s6.sin6_addr.s6_addr + 12, &s4.sin_addr, 4); |
| 63 | + } |
| 64 | + memcpy(a, &s6, cap < sizeof s6 ? cap : sizeof s6); |
| 65 | + *len = sizeof s6; |
| 66 | +} |
| 67 | + |
| 68 | +int socket(int domain, int type, int protocol) { |
| 69 | + int fake6 = domain == AF_INET6; |
| 70 | + int fd = syscall(SYS_socket, fake6 ? AF_INET : domain, type, protocol); |
| 71 | + if (fd >= 0) mark(fd, fake6); |
| 72 | + return fd; |
| 73 | +} |
| 74 | + |
| 75 | +int close(int fd) { |
| 76 | + mark(fd, 0); |
| 77 | + return syscall(SYS_close, fd); |
| 78 | +} |
| 79 | + |
| 80 | +#define WITH4(fd, addr, len, call4, call) \ |
| 81 | + if ((addr) && (addr)->sa_family == AF_INET6 && isfake(fd)) { \ |
| 82 | + struct sockaddr_in s4; \ |
| 83 | + socklen_t l4 = to4((addr), (len), &s4); \ |
| 84 | + if (l4 == (socklen_t)-1) { errno = EADDRNOTAVAIL; return -1; } \ |
| 85 | + return call4; \ |
| 86 | + } \ |
| 87 | + return call; |
| 88 | + |
| 89 | +int bind(int fd, const struct sockaddr *addr, socklen_t len) { |
| 90 | + WITH4(fd, addr, len, syscall(SYS_bind, fd, &s4, l4), syscall(SYS_bind, fd, addr, len)) |
| 91 | +} |
| 92 | + |
| 93 | +int connect(int fd, const struct sockaddr *addr, socklen_t len) { |
| 94 | + WITH4(fd, addr, len, syscall(SYS_connect, fd, &s4, l4), syscall(SYS_connect, fd, addr, len)) |
| 95 | +} |
| 96 | + |
| 97 | +ssize_t sendto(int fd, const void *buf, size_t n, int flags, const struct sockaddr *addr, socklen_t len) { |
| 98 | + WITH4(fd, addr, len, syscall(SYS_sendto, fd, buf, n, flags, &s4, l4), |
| 99 | + syscall(SYS_sendto, fd, buf, n, flags, addr, len)) |
| 100 | +} |
| 101 | + |
| 102 | +ssize_t sendmsg(int fd, const struct msghdr *msg, int flags) { |
| 103 | + if (msg && msg->msg_name && isfake(fd) && ((struct sockaddr *)msg->msg_name)->sa_family == AF_INET6) { |
| 104 | + struct sockaddr_in s4; |
| 105 | + socklen_t l4 = to4(msg->msg_name, msg->msg_namelen, &s4); |
| 106 | + if (l4 == (socklen_t)-1) { errno = EADDRNOTAVAIL; return -1; } |
| 107 | + struct msghdr m = *msg; |
| 108 | + m.msg_name = &s4; |
| 109 | + m.msg_namelen = l4; |
| 110 | + return syscall(SYS_sendmsg, fd, &m, flags); |
| 111 | + } |
| 112 | + return syscall(SYS_sendmsg, fd, msg, flags); |
| 113 | +} |
| 114 | + |
| 115 | +#define BACK6(fd, ret, addr, lenp) \ |
| 116 | + do { \ |
| 117 | + if ((ret) >= 0 && isfake(fd) && (addr) && (lenp)) to6((addr), (lenp), cap); \ |
| 118 | + } while (0) |
| 119 | + |
| 120 | +int getsockname(int fd, struct sockaddr *addr, socklen_t *len) { |
| 121 | + socklen_t cap = len ? *len : 0; |
| 122 | + struct sockaddr_in6 tmp; socklen_t tl = sizeof tmp; |
| 123 | + if (!isfake(fd)) return syscall(SYS_getsockname, fd, addr, len); |
| 124 | + int r = syscall(SYS_getsockname, fd, &tmp, &tl); |
| 125 | + if (r < 0) return r; |
| 126 | + to6((struct sockaddr *)&tmp, &tl, sizeof tmp); |
| 127 | + memcpy(addr, &tmp, cap < tl ? cap : tl); |
| 128 | + *len = tl; |
| 129 | + return r; |
| 130 | +} |
| 131 | + |
| 132 | +int getpeername(int fd, struct sockaddr *addr, socklen_t *len) { |
| 133 | + socklen_t cap = len ? *len : 0; |
| 134 | + struct sockaddr_in6 tmp; socklen_t tl = sizeof tmp; |
| 135 | + if (!isfake(fd)) return syscall(SYS_getpeername, fd, addr, len); |
| 136 | + int r = syscall(SYS_getpeername, fd, &tmp, &tl); |
| 137 | + if (r < 0) return r; |
| 138 | + to6((struct sockaddr *)&tmp, &tl, sizeof tmp); |
| 139 | + memcpy(addr, &tmp, cap < tl ? cap : tl); |
| 140 | + *len = tl; |
| 141 | + return r; |
| 142 | +} |
| 143 | + |
| 144 | +static int acc(int fd, struct sockaddr *addr, socklen_t *len, int flags) { |
| 145 | + if (!isfake(fd)) return syscall(SYS_accept4, fd, addr, len, flags); |
| 146 | + struct sockaddr_in6 tmp; socklen_t tl = sizeof tmp; |
| 147 | + int r = syscall(SYS_accept4, fd, &tmp, &tl, flags); |
| 148 | + if (r < 0) return r; |
| 149 | + mark(r, 1); |
| 150 | + if (addr && len) { |
| 151 | + socklen_t cap = *len; |
| 152 | + to6((struct sockaddr *)&tmp, &tl, sizeof tmp); |
| 153 | + memcpy(addr, &tmp, cap < tl ? cap : tl); |
| 154 | + *len = tl; |
| 155 | + } |
| 156 | + return r; |
| 157 | +} |
| 158 | +int accept(int fd, struct sockaddr *addr, socklen_t *len) { return acc(fd, addr, len, 0); } |
| 159 | +int accept4(int fd, struct sockaddr *addr, socklen_t *len, int flags) { return acc(fd, addr, len, flags); } |
| 160 | + |
| 161 | +ssize_t recvfrom(int fd, void *buf, size_t n, int flags, struct sockaddr *addr, socklen_t *len) { |
| 162 | + socklen_t cap = len ? *len : 0; |
| 163 | + ssize_t r = syscall(SYS_recvfrom, fd, buf, n, flags, addr, len); |
| 164 | + BACK6(fd, r, addr, len); |
| 165 | + return r; |
| 166 | +} |
| 167 | + |
| 168 | +ssize_t recvmsg(int fd, struct msghdr *msg, int flags) { |
| 169 | + socklen_t cap = msg ? msg->msg_namelen : 0; |
| 170 | + ssize_t r = syscall(SYS_recvmsg, fd, msg, flags); |
| 171 | + if (r >= 0 && msg && msg->msg_name) BACK6(fd, r, (struct sockaddr *)msg->msg_name, &msg->msg_namelen); |
| 172 | + return r; |
| 173 | +} |
| 174 | + |
| 175 | +int setsockopt(int fd, int level, int name, const void *val, socklen_t len) { |
| 176 | + if (isfake(fd) && level == IPPROTO_IPV6) { |
| 177 | + if (name == IPV6_TCLASS) return syscall(SYS_setsockopt, fd, IPPROTO_IP, IP_TOS, val, len); |
| 178 | + return 0; // IPV6_V6ONLY and the rest have no IPv4 meaning |
| 179 | + } |
| 180 | + return syscall(SYS_setsockopt, fd, level, name, val, len); |
| 181 | +} |
| 182 | + |
| 183 | +int getsockopt(int fd, int level, int name, void *val, socklen_t *len) { |
| 184 | + if (isfake(fd) && level == SOL_SOCKET && name == SO_DOMAIN && val && len && *len >= sizeof(int)) { |
| 185 | + *(int *)val = AF_INET6; |
| 186 | + *len = sizeof(int); |
| 187 | + return 0; |
| 188 | + } |
| 189 | + if (isfake(fd) && level == IPPROTO_IPV6) { |
| 190 | + if (val && len && *len >= sizeof(int)) { *(int *)val = 0; *len = sizeof(int); } |
| 191 | + return 0; |
| 192 | + } |
| 193 | + return syscall(SYS_getsockopt, fd, level, name, val, len); |
| 194 | +} |
0 commit comments