├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── libproxyproto.c ├── libproxyproto_connect.c ├── strtonum.c └── strtonum.h /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Object files 5 | *.o 6 | *.ko 7 | *.obj 8 | *.elf 9 | 10 | # Linker output 11 | *.ilk 12 | *.map 13 | *.exp 14 | 15 | # Precompiled Headers 16 | *.gch 17 | *.pch 18 | 19 | # Libraries 20 | *.lib 21 | *.a 22 | *.la 23 | *.lo 24 | 25 | # Shared objects (inc. Windows DLLs) 26 | *.dll 27 | *.so 28 | *.so.* 29 | *.dylib 30 | 31 | # Executables 32 | *.exe 33 | *.out 34 | *.app 35 | *.i*86 36 | *.x86_64 37 | *.hex 38 | 39 | # Debug files 40 | *.dSYM/ 41 | *.su 42 | *.idb 43 | *.pdb 44 | 45 | # Kernel Module Compile Results 46 | *.mod* 47 | *.cmd 48 | .tmp_versions/ 49 | modules.order 50 | Module.symvers 51 | Mkfile.old 52 | dkms.conf 53 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019-2023, Michael Santos 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | all: libproxyproto libproxyproto_connect 4 | 5 | UNAME_SYS := $(shell uname -s) 6 | ifeq ($(UNAME_SYS), OpenBSD) 7 | LIBPROXYPROTO_LDFLAGS ?= -Wl,-z,relro,-z,now -Wl,-z,noexecstack 8 | else 9 | LIBPROXYPROTO_LDFLAGS ?= -ldl -Wl,-z,relro,-z,now -Wl,-z,noexecstack 10 | endif 11 | 12 | LIBPROXYPROTO_GETPEERNAME_CACHE ?= ENABLED 13 | 14 | libproxyproto: 15 | $(CC) -Wall -Wextra -pedantic -D_GNU_SOURCE -nostartfiles -shared -fpic -fPIC \ 16 | -DGETPEERNAME_CACHE_$(LIBPROXYPROTO_GETPEERNAME_CACHE) \ 17 | -fvisibility=hidden \ 18 | -Wconversion -Wshadow \ 19 | -Wpointer-arith -Wcast-qual \ 20 | -Wstrict-prototypes -Wmissing-prototypes \ 21 | -o $@.so $@.c strtonum.c \ 22 | $(LIBPROXYPROTO_LDFLAGS) 23 | 24 | libproxyproto_connect: 25 | $(CC) -Wall -Wextra -pedantic -D_GNU_SOURCE -nostartfiles -shared -fpic -fPIC \ 26 | -fvisibility=hidden \ 27 | -Wconversion -Wshadow \ 28 | -Wpointer-arith -Wcast-qual \ 29 | -Wstrict-prototypes -Wmissing-prototypes \ 30 | -o $@.so $@.c \ 31 | $(LIBPROXYPROTO_LDFLAGS) 32 | 33 | clean: 34 | -@rm libproxyproto.so libproxyproto_connect.so 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NAME 2 | 3 | libproxyproto - LD_PRELOAD library for adding support for proxy protocol v1 and v2 4 | 5 | # SYNOPSIS 6 | 7 | * server 8 | 9 | LD_PRELOAD=libproxyproto.so *COMMAND* *ARG* *...* 10 | 11 | * test client 12 | 13 | LD_PRELOAD=libproxyproto_connect.so *COMMAND* *ARG* *...* 14 | 15 | # DESCRIPTION 16 | 17 | libproxyproto provides a method for applications to discover the original 18 | client IP address and port of proxied connections. The application must 19 | be dynamically linked. 20 | 21 | Intermediary proxies insert the proxy protocol header before the application 22 | data: 23 | 24 | https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt 25 | 26 | Proxy protocol v1 and v2 are supported. 27 | 28 | When the connection is `accept(2)`'ed, libproxyproto: 29 | 30 | * intercepts the call to `accept(2)` 31 | * reads the proxy protocol header 32 | * sets the source IP address and port in the `struct sockaddr` argument of 33 | `accept(2)` 34 | * caches the IP address and intercepts calls to `getpeername(2)` 35 | 36 | libproxyproto_connect does the same thing for calls to `connect(2)` 37 | and can be used for testing. 38 | 39 | # ENVIRONMENT VARIABLES 40 | 41 | ## Compile 42 | 43 | `LIBPROXYPROTO_GETPEERNAME_CACHE` 44 | : enable or disable support for the `getpeername(2)` (default: ENABLED, 45 | set to any value to disable) 46 | 47 | ## Runtime 48 | 49 | ### common 50 | 51 | `LIBPROXYPROTO_DEBUG` 52 | : Write errors to stderr (default: disabled) 53 | 54 | ### libproxyproto 55 | 56 | `LIBPROXYPROTO_MUST_USE_PROTOCOL_HEADER` 57 | : By default, connections without the proxy protocol header are 58 | allowed. Enabling this option drops connections without a protocol header 59 | (default: disabled). 60 | 61 | `LIBPROXYPROTO_VERSION` 62 | : Supported proxy protocol version (default: 3): 63 | 64 | ``` 65 | 0: proxy protocol disabled 66 | 1: proxy protocol v1 only 67 | 2: proxy protocol v2 only 68 | 3: proxy protocol v1 and v2 69 | ``` 70 | 71 | ### libproxyproto_connect 72 | 73 | `LIBPROXYPROTO_ADDR` 74 | : Source IP address (default: 127.0.0.1) 75 | 76 | `LIBPROXYPROTO_PORT` 77 | : Source port (default: 8080) 78 | 79 | `LIBPROXYPROTO_VERSION` 80 | : Supported proxy protocol version (default: 2): 81 | 82 | ``` 83 | 0: proxy protocol disabled 84 | 1: proxy protocol v1 85 | 2: proxy protocol v2 86 | ``` 87 | 88 | # EXAMPLES 89 | 90 | ## netcat 91 | 92 | ``` 93 | # run in a shell 94 | LD_PRELOAD=libproxyproto.so nc -vvvv -k -l 9090 95 | 96 | # in another shell 97 | LD_PRELOAD=libproxyproto_connect.so \ 98 | LIBPROXYPROTO_ADDR="8.8.8.8" LIBPROXYPROTO_PORT="4321" \ 99 | nc 127.0.0.1 9090 100 | ``` 101 | 102 | ### IPv6 103 | 104 | ``` 105 | # run in a shell 106 | LD_PRELOAD=libproxyproto.so nc -vvvv -n -6 -k -l 9090 107 | 108 | # in another shell 109 | LD_PRELOAD=libproxyproto_connect.so \ 110 | LIBPROXYPROTO_ADDR="2001:4860:4860::8888" LIBPROXYPROTO_PORT="4321" \ 111 | nc -6 -v localhost 9090 112 | ``` 113 | 114 | ## haproxy.conf 115 | 116 | ``` 117 | # test haproxy 118 | # server: LD_PRELOAD=libproxyproto.so nc -vvvv -k -l 9090 119 | defaults 120 | mode tcp 121 | timeout connect 4s 122 | timeout client 30s 123 | timeout server 30s 124 | 125 | listen example 126 | bind :8080 127 | server test 127.0.0.1:9090 send-proxy-v2 128 | ``` 129 | 130 | # ALTERNATIVES 131 | 132 | * [proxyproto.nim](https://github.com/ba0f3/proxyproto.nim) 133 | 134 | # BENCHMARK 135 | 136 | To give a very rough idea of the overhead, a 137 | [benchmark](https://github.com/path-network/go-mmproxy/blob/master/README.md#benchmark) 138 | was run against an echo service. 139 | 140 | This benchmark is not meant to be conclusive: 141 | 142 | * run over the loopback 143 | * on an old system running many other services 144 | 145 | ## Benchmark Client 146 | 147 | [tcpkali](https://github.com/satori-com/tcpkali) 148 | 149 | ``` 150 | tcpkali -c 50 -T 10s -e1 'PROXY TCP4 127.0.0.1 127.0.0.1 \{connection.uid} 25578\r\n' -m 'PING\r\n' 127.0.0.1:1122 151 | ``` 152 | 153 | ## Echo Server 154 | 155 | To run: 156 | 157 | ``` 158 | erlc echo.erl 159 | 160 | # no proxy: run on port 1122 161 | erl -noshell -eval "echo:start()" 162 | 163 | # libproxyproto: run on port 1122 164 | LD_PRELOAD=libproxyproto.so erl -noshell -eval "echo:start()" 165 | 166 | # go-mmproxy: run on port 1123 167 | erl -noshell -eval "echo:start(1123)" 168 | sudo ./go-mmproxy -l 0.0.0.0:1122 -4 127.0.0.1:1123 169 | ``` 170 | 171 | ```erlang 172 | -module(echo). 173 | 174 | -export([start/0, start/1]). 175 | 176 | start() -> 177 | start(1122). 178 | 179 | start(Port) -> 180 | {ok, S} = gen_tcp:listen(Port, [ 181 | binary, 182 | {reuseaddr, true}, 183 | {backlog, 1024} 184 | ]), 185 | accept(S). 186 | 187 | accept(LS) -> 188 | {ok, S} = gen_tcp:accept(LS), 189 | Pid = spawn(fun() -> recv(S) end), 190 | _ = gen_tcp:controlling_process(S, Pid), 191 | accept(LS). 192 | 193 | recv(S) -> 194 | receive 195 | {tcp, S, Data} -> 196 | gen_tcp:send(S, Data), 197 | recv(S); 198 | {tcp_closed, S} -> 199 | ok; 200 | Error -> 201 | error_logger:error_report([{socket, S}, {error, Error}]) 202 | end. 203 | ``` 204 | 205 | ## Results 206 | 207 | | | ⇅ Mbps | ↓ Mbps | ↑ Mbps | ↓ pkt/s | ↑ pkt/s | 208 | |---------------|--------|----------|----------|----------|----------| 209 | | noproxy | 98.238 | 2455.263 | 2456.626 | 229245.6 | 210847.5 | 210 | | libproxyproto | 94.974 | 2373.474 | 2375.247 | 221341.9 | 203862.9 | 211 | | go-mmproxy | 76.567 | 1901.043 | 1927.293 | 163515.0 | 165415.9 | 212 | 213 | Bandwidth per channel: ⇅ Mbps 214 | Aggregate bandwidth: ↓, ↑ Mbps 215 | Packet rate estimate: ↓, ↑ 216 | 217 | # SEE ALSO 218 | 219 | *connect*(2), *accept*(2), *getpeername*(2) 220 | -------------------------------------------------------------------------------- /libproxyproto.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2023, Michael Santos 2 | * 3 | * Permission to use, copy, modify, and/or distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | #include "strtonum.h" 33 | 34 | enum { 35 | LIBPROXYPROTO_V1 = (1 << 0), 36 | LIBPROXYPROTO_V2 = (1 << 1), 37 | }; 38 | 39 | void _init(void); 40 | static int (*sys_accept)(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 41 | static int (*sys_accept4)(int sockfd, struct sockaddr *addr, socklen_t *addrlen, 42 | int flags); 43 | #ifdef GETPEERNAME_CACHE_ENABLED 44 | static int (*sys_close)(int fd); 45 | static int (*sys_getpeername)(int sockfd, struct sockaddr *addr, 46 | socklen_t *addrlen); 47 | #endif 48 | #pragma GCC diagnostic ignored "-Wpedantic" 49 | int __attribute__((visibility("default"))) 50 | accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 51 | int __attribute__((visibility("default"))) 52 | accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags); 53 | #ifdef GETPEERNAME_CACHE_ENABLED 54 | int __attribute__((visibility("default"))) close(int fd); 55 | int __attribute__((visibility("default"))) 56 | getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 57 | #endif 58 | #pragma GCC diagnostic warning "-Wpedantic" 59 | static int read_evt(int fd, struct sockaddr *from, socklen_t ofromlen, 60 | socklen_t fromlen); 61 | 62 | static const char v2sig[12] = 63 | "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; 64 | 65 | static char *debug; 66 | static char *must_use_protocol_header; 67 | static int version = LIBPROXYPROTO_V1 | LIBPROXYPROTO_V2; 68 | 69 | #ifdef GETPEERNAME_CACHE_ENABLED 70 | // cache of sock addresses 71 | #define CACHE_MAX 1024 72 | static struct sockaddr *addr_cache[CACHE_MAX + 1] = {0}; 73 | #endif 74 | 75 | void _init(void) { 76 | const char *err; 77 | char *env_version; 78 | 79 | debug = getenv("LIBPROXYPROTO_DEBUG"); 80 | must_use_protocol_header = getenv("LIBPROXYPROTO_MUST_USE_PROTOCOL_HEADER"); 81 | env_version = getenv("LIBPROXYPROTO_VERSION"); 82 | 83 | if (env_version != NULL) { 84 | version = atoi(env_version); 85 | if (version > 255) 86 | version = 255; 87 | else if (version < 0) 88 | version = 0; 89 | } 90 | 91 | #pragma GCC diagnostic ignored "-Wpedantic" 92 | sys_accept = dlsym(RTLD_NEXT, "accept"); 93 | sys_accept4 = dlsym(RTLD_NEXT, "accept4"); 94 | #ifdef GETPEERNAME_CACHE_ENABLED 95 | sys_close = dlsym(RTLD_NEXT, "close"); 96 | sys_getpeername = dlsym(RTLD_NEXT, "getpeername"); 97 | #endif 98 | #pragma GCC diagnostic warning "-Wpedantic" 99 | err = dlerror(); 100 | 101 | if (err != NULL) 102 | (void)fprintf(stderr, "libproxyproto:dlsym (accept):%s\n", err); 103 | } 104 | 105 | int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 106 | int fd; 107 | struct sockaddr *tmp_addr; 108 | socklen_t tmp_addrlen; 109 | 110 | tmp_addrlen = sizeof(struct sockaddr_storage); 111 | tmp_addr = calloc(1, tmp_addrlen); 112 | if (!tmp_addr) 113 | return -1; 114 | 115 | fd = sys_accept(sockfd, tmp_addr, &tmp_addrlen); 116 | if (fd < 0) 117 | return fd; 118 | 119 | if (debug) 120 | (void)fprintf(stderr, "accept: accepted connection\n"); 121 | 122 | if (read_evt(fd, tmp_addr, sizeof(struct sockaddr_storage), tmp_addrlen) <= 123 | 0) { 124 | if (debug) 125 | (void)fprintf(stderr, "error: not proxy protocol\n"); 126 | 127 | if (!must_use_protocol_header) 128 | goto LIBPROXYPROTO_DONE; 129 | 130 | if (debug) 131 | (void)fprintf(stderr, "dropping connection\n"); 132 | 133 | (void)close(fd); 134 | errno = ECONNABORTED; 135 | return -1; 136 | } 137 | 138 | LIBPROXYPROTO_DONE: 139 | /* copy the result to the caller */ 140 | if (addr && *addrlen) { 141 | memcpy(addr, tmp_addr, *addrlen > tmp_addrlen ? tmp_addrlen : *addrlen); 142 | *addrlen = tmp_addrlen; 143 | } 144 | 145 | #ifdef GETPEERNAME_CACHE_ENABLED 146 | /* store in the cache if possible */ 147 | if (fd < CACHE_MAX) { 148 | if (addr_cache[fd] != NULL) 149 | free(addr_cache[fd]); 150 | addr_cache[fd] = tmp_addr; 151 | } else { 152 | free(tmp_addr); 153 | } 154 | #endif 155 | 156 | return fd; 157 | } 158 | 159 | int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags) { 160 | int fd; 161 | int nonblock; 162 | struct sockaddr *tmp_addr; 163 | socklen_t tmp_addrlen; 164 | 165 | tmp_addrlen = sizeof(struct sockaddr_storage); 166 | tmp_addr = calloc(1, tmp_addrlen); 167 | if (!tmp_addr) 168 | return -1; 169 | 170 | nonblock = flags & SOCK_NONBLOCK; 171 | if (nonblock) 172 | flags &= ~SOCK_NONBLOCK; 173 | 174 | fd = sys_accept4(sockfd, tmp_addr, &tmp_addrlen, flags); 175 | if (fd < 0) 176 | return fd; 177 | 178 | if (debug) 179 | (void)fprintf(stderr, "accept4: accepted connection\n"); 180 | 181 | if (read_evt(fd, tmp_addr, sizeof(struct sockaddr_storage), tmp_addrlen) <= 182 | 0) { 183 | if (debug) 184 | (void)fprintf(stderr, "error: not proxy protocol\n"); 185 | 186 | if (!must_use_protocol_header) 187 | goto LIBPROXYPROTO_DONE; 188 | 189 | if (debug) 190 | (void)fprintf(stderr, "dropping connection\n"); 191 | 192 | (void)close(fd); 193 | errno = ECONNABORTED; 194 | return -1; 195 | } 196 | 197 | LIBPROXYPROTO_DONE: 198 | if (nonblock) { 199 | if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { 200 | (void)close(fd); 201 | return -1; 202 | } 203 | } 204 | 205 | /* copy the result to the caller */ 206 | if (addr && *addrlen) { 207 | memcpy(addr, tmp_addr, *addrlen > tmp_addrlen ? tmp_addrlen : *addrlen); 208 | *addrlen = tmp_addrlen; 209 | } 210 | 211 | #ifdef GETPEERNAME_CACHE_ENABLED 212 | /* store in the cache if possible */ 213 | if (fd < CACHE_MAX) { 214 | if (addr_cache[fd] != NULL) 215 | free(addr_cache[fd]); 216 | addr_cache[fd] = tmp_addr; 217 | } else { 218 | free(tmp_addr); 219 | } 220 | #endif 221 | 222 | return fd; 223 | } 224 | 225 | #ifdef GETPEERNAME_CACHE_ENABLED 226 | int close(int fd) { 227 | int ret = sys_close(fd); 228 | 229 | if (ret == 0 && addr_cache[fd] != NULL) { 230 | if (debug) 231 | (void)fprintf(stderr, "close(): freeing cache\n"); 232 | free(addr_cache[fd]); 233 | addr_cache[fd] = NULL; 234 | } 235 | 236 | return ret; 237 | } 238 | 239 | int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { 240 | 241 | if (addr_cache[sockfd] == NULL) 242 | return sys_getpeername(sockfd, addr, addrlen); 243 | 244 | memcpy(addr, addr_cache[sockfd], *addrlen); 245 | 246 | switch (addr_cache[sockfd]->sa_family) { 247 | case AF_INET: 248 | *addrlen = sizeof(struct sockaddr_in); 249 | break; 250 | case AF_INET6: 251 | *addrlen = sizeof(struct sockaddr_in6); 252 | default: 253 | break; 254 | } 255 | 256 | if (debug) 257 | (void)fprintf(stderr, "getpeername() replacing addr\n"); 258 | 259 | return 0; 260 | } 261 | #endif 262 | 263 | /* returns 0 if needs to poll, <0 upon error or >0 if it did the job */ 264 | static int read_evt(int fd, struct sockaddr *from, socklen_t ofromlen, 265 | socklen_t fromlen) { 266 | union { 267 | struct { 268 | char line[108]; 269 | } v1; 270 | struct { 271 | uint8_t sig[12]; 272 | uint8_t ver_cmd; 273 | uint8_t fam; 274 | uint16_t len; 275 | union { 276 | struct { /* for TCP/UDP over IPv4, len = 12 */ 277 | uint32_t src_addr; 278 | uint32_t dst_addr; 279 | uint16_t src_port; 280 | uint16_t dst_port; 281 | } ip4; 282 | struct { /* for TCP/UDP over IPv6, len = 36 */ 283 | uint8_t src_addr[16]; 284 | uint8_t dst_addr[16]; 285 | uint16_t src_port; 286 | uint16_t dst_port; 287 | } ip6; 288 | struct { /* for AF_UNIX sockets, len = 216 */ 289 | uint8_t src_addr[108]; 290 | uint8_t dst_addr[108]; 291 | } unx; 292 | } addr; 293 | } v2; 294 | } hdr; 295 | 296 | ssize_t size, ret; 297 | 298 | do { 299 | ret = recv(fd, &hdr, sizeof(hdr), MSG_PEEK); 300 | } while (ret == -1 && errno == EINTR); 301 | 302 | if (ret == -1) 303 | return (errno == EAGAIN) ? 0 : -1; 304 | 305 | if (ret >= 16 && memcmp(&hdr.v2, v2sig, 12) == 0 && 306 | (hdr.v2.ver_cmd & 0xF0) == 0x20) { 307 | size = 16 + ntohs(hdr.v2.len); 308 | if (ret < size) 309 | return -1; /* truncated or too large header */ 310 | 311 | if (from == NULL || !(version & LIBPROXYPROTO_V2)) 312 | goto done; 313 | 314 | switch (hdr.v2.ver_cmd & 0xF) { 315 | case 0x01: /* PROXY command */ 316 | switch (hdr.v2.fam) { 317 | case 0x11: /* TCPv4 */ 318 | if (ofromlen < fromlen) 319 | goto done; 320 | if (debug) 321 | (void)fprintf(stderr, "*** orig addr=%s:%u\n", 322 | inet_ntoa(((struct sockaddr_in *)from)->sin_addr), 323 | ntohs(((struct sockaddr_in *)from)->sin_port)); 324 | ((struct sockaddr_in *)from)->sin_family = AF_INET; 325 | ((struct sockaddr_in *)from)->sin_addr.s_addr = 326 | hdr.v2.addr.ip4.src_addr; 327 | ((struct sockaddr_in *)from)->sin_port = hdr.v2.addr.ip4.src_port; 328 | if (debug) 329 | (void)fprintf(stderr, "*** proxied addr=%s:%u\n", 330 | inet_ntoa(((struct sockaddr_in *)from)->sin_addr), 331 | ntohs(((struct sockaddr_in *)from)->sin_port)); 332 | goto done; 333 | case 0x21: /* TCPv6 */ 334 | if (ofromlen < fromlen) 335 | goto done; 336 | ((struct sockaddr_in6 *)from)->sin6_family = AF_INET6; 337 | memcpy(&((struct sockaddr_in6 *)from)->sin6_addr, 338 | hdr.v2.addr.ip6.src_addr, 16); 339 | ((struct sockaddr_in6 *)from)->sin6_port = hdr.v2.addr.ip6.src_port; 340 | goto done; 341 | } 342 | /* unsupported protocol, keep local connection address */ 343 | break; 344 | case 0x00: /* LOCAL command */ 345 | /* keep local connection address for LOCAL */ 346 | break; 347 | default: 348 | return -1; /* not a supported command */ 349 | } 350 | } else if (ret >= 8 && memcmp(hdr.v1.line, "PROXY", 5) == 0) { 351 | char *end; 352 | 353 | char *str, *token; 354 | char *saveptr = NULL; 355 | const char *errstr = NULL; 356 | int j; 357 | unsigned char buf[sizeof(struct in6_addr)] = {0}; 358 | uint16_t port; 359 | 360 | end = memchr(hdr.v1.line, '\r', (size_t)ret - 1); 361 | 362 | if (!end || end[1] != '\n') 363 | return -1; /* partial or invalid header */ 364 | 365 | *end = '\0'; /* terminate the string to ease parsing */ 366 | size = end + 2 - hdr.v1.line; /* skip header + CRLF */ 367 | 368 | if (from == NULL || !(version & LIBPROXYPROTO_V1)) 369 | goto done; 370 | 371 | /* PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535 372 | * PROXY TCP6 ffff:f...f:ffff ffff:f...f:ffff 65535 65535 373 | * PROXY UNKNOWN 374 | * PROXY UNKNOWN ffff:f...f:ffff ffff:f...f:ffff 65535 65535 375 | */ 376 | for (j = 1, str = hdr.v1.line;; j++, str = NULL) { 377 | token = strtok_r(str, " ", &saveptr); 378 | if (token == NULL) 379 | return -1; 380 | 381 | if (debug) 382 | (void)fprintf(stderr, "v1:%d:%s\n", j, token); 383 | 384 | switch (j) { 385 | case 1: 386 | /* PROXY */ 387 | continue; 388 | case 2: 389 | /* TCP4, TCP6, UNKNOWN */ 390 | if (strcmp(token, "UNKNOWN") == 0) { 391 | goto done; 392 | } else if (strcmp(token, "TCP4") == 0) { 393 | if (ofromlen < fromlen) 394 | goto done; 395 | ((struct sockaddr_in *)from)->sin_family = AF_INET; 396 | } else if (strcmp(token, "TCP6") == 0) { 397 | if (ofromlen < fromlen) 398 | goto done; 399 | ((struct sockaddr_in6 *)from)->sin6_family = AF_INET6; 400 | } else { 401 | return -1; 402 | } 403 | break; 404 | case 3: 405 | /* source address */ 406 | if (inet_pton(((struct sockaddr *)from)->sa_family, token, buf) != 1) { 407 | return -1; 408 | } 409 | if (((struct sockaddr *)from)->sa_family == AF_INET) { 410 | ((struct sockaddr_in *)from)->sin_addr.s_addr = 411 | ((struct in_addr *)buf)->s_addr; 412 | } else if (((struct sockaddr *)from)->sa_family == AF_INET6) { 413 | (void)memcpy(hdr.v2.addr.ip6.src_addr, buf, 16); 414 | } 415 | break; 416 | case 4: 417 | /* destination address */ 418 | if (inet_pton(((struct sockaddr *)from)->sa_family, token, buf) != 1) { 419 | return -1; 420 | } 421 | continue; 422 | case 5: 423 | /* source port */ 424 | if (token[0] < '1' || token[0] > '9') 425 | return -1; 426 | port = (uint16_t)strtonum(token, 0, UINT16_MAX, &errstr); 427 | if (errstr != NULL) 428 | return -1; 429 | 430 | if (((struct sockaddr *)from)->sa_family == AF_INET) { 431 | ((struct sockaddr_in *)from)->sin_port = htons(port); 432 | } else if (((struct sockaddr *)from)->sa_family == AF_INET6) { 433 | ((struct sockaddr_in6 *)from)->sin6_port = htons(port); 434 | } 435 | break; 436 | case 6: 437 | /* destination port */ 438 | if (token[0] < '1' || token[0] > '9') 439 | return -1; 440 | (void)strtonum(token, 0, UINT16_MAX, &errstr); 441 | if (errstr != NULL) 442 | return -1; 443 | goto done; 444 | default: 445 | return -1; 446 | } 447 | } 448 | } else { 449 | /* Wrong protocol */ 450 | return -1; 451 | } 452 | 453 | done: 454 | /* we need to consume the appropriate amount of data from the socket */ 455 | do { 456 | ret = recv(fd, &hdr, (size_t)size, 0); 457 | } while (ret == -1 && errno == EINTR); 458 | return (ret >= 0) ? 1 : -1; 459 | } 460 | -------------------------------------------------------------------------------- /libproxyproto_connect.c: -------------------------------------------------------------------------------- 1 | /* Copyright (c) 2019-2023, Michael Santos 2 | * 3 | * Permission to use, copy, modify, and/or distribute this software for any 4 | * purpose with or without fee is hereby granted, provided that the above 5 | * copyright notice and this permission notice appear in all copies. 6 | * 7 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | */ 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | 32 | void _init(void); 33 | static int (*sys_connect)(int sockfd, const struct sockaddr *addr, 34 | socklen_t addrlen); 35 | #pragma GCC diagnostic ignored "-Wpedantic" 36 | int __attribute__((visibility("default"))) 37 | connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 38 | #pragma GCC diagnostic warning "-Wpedantic" 39 | static int write_evt(int fd, void *from, uint16_t port, 40 | const struct sockaddr *to, socklen_t tolen); 41 | static int write_v1(int fd, void *from, uint16_t port, 42 | const struct sockaddr *to, socklen_t tolen); 43 | static int write_v2(int fd, void *from, uint16_t port, 44 | const struct sockaddr *to, socklen_t tolen); 45 | 46 | static const char v2sig[12] = 47 | "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A"; 48 | 49 | static char *debug; 50 | static char *paddr; 51 | static uint16_t pport; 52 | static int version = 2; 53 | 54 | void _init(void) { 55 | const char *err; 56 | char *env_port; 57 | char *env_version; 58 | 59 | debug = getenv("LIBPROXYPROTO_DEBUG"); 60 | 61 | paddr = getenv("LIBPROXYPROTO_ADDR"); 62 | if (paddr == NULL) 63 | paddr = "127.0.0.1"; 64 | 65 | env_version = getenv("LIBPROXYPROTO_VERSION"); 66 | if (env_version != NULL) { 67 | version = atoi(env_version); 68 | if (version < 0 || version > 2) 69 | _exit(111); 70 | } 71 | 72 | env_port = getenv("LIBPROXYPROTO_PORT"); 73 | pport = htons((uint16_t)atoi(env_port ? env_port : "8080")); 74 | 75 | #pragma GCC diagnostic ignored "-Wpedantic" 76 | sys_connect = dlsym(RTLD_NEXT, "connect"); 77 | #pragma GCC diagnostic warning "-Wpedantic" 78 | err = dlerror(); 79 | 80 | if (err != NULL) 81 | (void)fprintf(stderr, "libproxyproto:dlsym (connect):%s\n", err); 82 | } 83 | 84 | int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { 85 | int fd; 86 | int oflags; 87 | int nflags = 0; 88 | unsigned char buf[sizeof(struct in6_addr)]; 89 | 90 | oflags = fcntl(sockfd, F_GETFL); 91 | if (oflags < 0) 92 | return -1; 93 | 94 | nflags = oflags & ~O_NONBLOCK; 95 | 96 | if (oflags != nflags) { 97 | if (fcntl(sockfd, F_SETFL, nflags) < 0) 98 | return -1; 99 | } 100 | 101 | fd = sys_connect(sockfd, addr, addrlen); 102 | if (fd < 0) 103 | return fd; 104 | 105 | if (debug) 106 | (void)fprintf(stderr, "connected\n"); 107 | 108 | switch (((const struct sockaddr *)addr)->sa_family) { 109 | case AF_INET: 110 | if (inet_pton(AF_INET, paddr, buf) != 1) { 111 | if (debug) 112 | (void)fprintf(stderr, "error: invalid address\n"); 113 | goto LIBPROXYPROTO_DONE; 114 | } 115 | break; 116 | case AF_INET6: 117 | if (inet_pton(AF_INET6, paddr, buf) != 1) { 118 | if (debug) 119 | (void)fprintf(stderr, "error: invalid address\n"); 120 | goto LIBPROXYPROTO_DONE; 121 | } 122 | break; 123 | default: 124 | goto LIBPROXYPROTO_DONE; 125 | } 126 | 127 | if (write_evt(sockfd, buf, pport, (const struct sockaddr *)addr, addrlen) < 128 | 0) { 129 | if (debug) 130 | (void)fprintf(stderr, 131 | "error: proxy protocol not supported for socket type\n"); 132 | } 133 | 134 | LIBPROXYPROTO_DONE: 135 | if (oflags != nflags) { 136 | if (fcntl(sockfd, F_SETFL, oflags) < 0) 137 | _exit(111); 138 | } 139 | 140 | return fd; 141 | } 142 | 143 | static int write_evt(int fd, void *from, uint16_t port, 144 | const struct sockaddr *to, socklen_t tolen) { 145 | switch (version) { 146 | case 0: 147 | return 1; 148 | case 1: 149 | return write_v1(fd, from, port, to, tolen); 150 | case 2: 151 | return write_v2(fd, from, port, to, tolen); 152 | default: 153 | return -1; 154 | } 155 | } 156 | 157 | static int write_v1(int fd, void *from, uint16_t port, 158 | const struct sockaddr *to, socklen_t tolen) { 159 | char buf[108] = {0}; 160 | char saddr[INET6_ADDRSTRLEN] = {0}; 161 | char daddr[INET6_ADDRSTRLEN] = {0}; 162 | uint16_t size = 0; 163 | ssize_t ret; 164 | int rv; 165 | 166 | switch (((const struct sockaddr *)to)->sa_family) { 167 | case AF_INET: 168 | if (tolen < sizeof(struct sockaddr_in)) 169 | return -1; 170 | 171 | if (inet_ntop(AF_INET, &(((struct in_addr *)from)->s_addr), saddr, 172 | INET_ADDRSTRLEN) == NULL) 173 | return -1; 174 | 175 | if (inet_ntop(AF_INET, &(((const struct sockaddr_in *)to)->sin_addr.s_addr), 176 | daddr, INET_ADDRSTRLEN) == NULL) 177 | return -1; 178 | 179 | rv = snprintf(buf, sizeof(buf), "PROXY TCP4 %s %s %u %u\r\n", saddr, daddr, 180 | ntohs(port), 181 | ntohs(((const struct sockaddr_in *)to)->sin_port)); 182 | 183 | break; 184 | case AF_INET6: 185 | if (tolen < sizeof(struct sockaddr_in6)) 186 | return -1; 187 | 188 | if (inet_ntop(AF_INET6, from, saddr, INET6_ADDRSTRLEN) == NULL) 189 | return -1; 190 | 191 | if (inet_ntop(AF_INET6, &((const struct sockaddr_in6 *)to)->sin6_addr, 192 | daddr, INET6_ADDRSTRLEN) == NULL) 193 | return -1; 194 | 195 | rv = snprintf(buf, sizeof(buf), "PROXY TCP6 %s %s %u %u\r\n", saddr, daddr, 196 | ntohs(port), 197 | ntohs(((const struct sockaddr_in6 *)to)->sin6_port)); 198 | 199 | break; 200 | default: 201 | return -1; 202 | } 203 | 204 | if (rv <= 0 || (unsigned)rv > sizeof(buf)) 205 | return -1; 206 | 207 | size = (uint16_t)rv; 208 | 209 | while ((ret = write(fd, buf, size)) == -1 && errno == EINTR) 210 | ; 211 | 212 | return ret == size ? 1 : -1; 213 | } 214 | 215 | static int write_v2(int fd, void *from, uint16_t port, 216 | const struct sockaddr *to, socklen_t tolen) { 217 | union { 218 | struct { 219 | char line[108]; 220 | } v1; 221 | struct { 222 | uint8_t sig[12]; 223 | uint8_t ver_cmd; 224 | uint8_t fam; 225 | uint16_t len; 226 | union { 227 | struct { /* for TCP/UDP over IPv4, len = 12 */ 228 | uint32_t src_addr; 229 | uint32_t dst_addr; 230 | uint16_t src_port; 231 | uint16_t dst_port; 232 | } ip4; 233 | struct { /* for TCP/UDP over IPv6, len = 36 */ 234 | uint8_t src_addr[16]; 235 | uint8_t dst_addr[16]; 236 | uint16_t src_port; 237 | uint16_t dst_port; 238 | } ip6; 239 | struct { /* for AF_UNIX sockets, len = 216 */ 240 | uint8_t src_addr[108]; 241 | uint8_t dst_addr[108]; 242 | } unx; 243 | } addr; 244 | } v2; 245 | } hdr; 246 | 247 | uint16_t size; 248 | ssize_t ret; 249 | 250 | (void)memcpy(hdr.v2.sig, v2sig, sizeof(hdr.v2.sig)); 251 | hdr.v2.ver_cmd = 0x21; 252 | 253 | switch (((const struct sockaddr *)to)->sa_family) { 254 | case AF_INET: 255 | if (tolen < sizeof(struct sockaddr_in)) 256 | return -1; 257 | hdr.v2.fam = 0x11; 258 | size = 16 + 12; 259 | hdr.v2.len = htons(12); 260 | hdr.v2.addr.ip4.src_addr = ((struct in_addr *)from)->s_addr; 261 | hdr.v2.addr.ip4.src_port = port; 262 | hdr.v2.addr.ip4.dst_addr = 263 | ((const struct sockaddr_in *)to)->sin_addr.s_addr; 264 | hdr.v2.addr.ip4.dst_port = ((const struct sockaddr_in *)to)->sin_port; 265 | break; 266 | case AF_INET6: 267 | if (tolen < sizeof(struct sockaddr_in6)) 268 | return -1; 269 | hdr.v2.fam = 0x21; 270 | size = 16 + 36; 271 | hdr.v2.len = htons(36); 272 | memcpy(hdr.v2.addr.ip6.src_addr, from, 16); 273 | hdr.v2.addr.ip6.src_port = port; 274 | memcpy(hdr.v2.addr.ip6.dst_addr, 275 | &((const struct sockaddr_in6 *)to)->sin6_addr, 16); 276 | hdr.v2.addr.ip6.dst_port = ((const struct sockaddr_in6 *)to)->sin6_port; 277 | break; 278 | default: 279 | return -1; 280 | } 281 | 282 | while ((ret = write(fd, &hdr.v2, size)) == -1 && errno == EINTR) 283 | ; 284 | 285 | return ret == size ? 1 : -1; 286 | } 287 | -------------------------------------------------------------------------------- /strtonum.c: -------------------------------------------------------------------------------- 1 | /* $OpenBSD: strtonum.c,v 1.6 2004/08/03 19:38:01 millert Exp $ */ 2 | 3 | /* 4 | * Copyright (c) 2004 Ted Unangst and Todd Miller 5 | * All rights reserved. 6 | * 7 | * Permission to use, copy, modify, and distribute this software for any 8 | * purpose with or without fee is hereby granted, provided that the above 9 | * copyright notice and this permission notice appear in all copies. 10 | * 11 | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12 | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13 | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14 | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15 | * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16 | * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17 | * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18 | */ 19 | 20 | /* OPENBSD ORIGINAL: lib/libc/stdlib/strtonum.c */ 21 | 22 | #include 23 | #ifndef HAVE_STRTONUM 24 | #include 25 | #include 26 | #include 27 | 28 | #include "strtonum.h" 29 | 30 | #define INVALID 1 31 | #define TOOSMALL 2 32 | #define TOOLARGE 3 33 | 34 | long long strtonum(const char *numstr, long long minval, long long maxval, 35 | const char **errstrp) { 36 | long long ll = 0; 37 | char *ep; 38 | int error = 0; 39 | struct errval { 40 | const char *errstr; 41 | int err; 42 | } ev[4] = { 43 | {NULL, 0}, 44 | {"invalid", EINVAL}, 45 | {"too small", ERANGE}, 46 | {"too large", ERANGE}, 47 | }; 48 | 49 | ev[0].err = errno; 50 | errno = 0; 51 | if (minval > maxval) 52 | error = INVALID; 53 | else { 54 | ll = strtoll(numstr, &ep, 10); 55 | if (numstr == ep || *ep != '\0') 56 | error = INVALID; 57 | else if ((ll == LLONG_MIN && errno == ERANGE) || ll < minval) 58 | error = TOOSMALL; 59 | else if ((ll == LLONG_MAX && errno == ERANGE) || ll > maxval) 60 | error = TOOLARGE; 61 | } 62 | if (errstrp != NULL) 63 | *errstrp = ev[error].errstr; 64 | errno = ev[error].err; 65 | if (error) 66 | ll = 0; 67 | 68 | return (ll); 69 | } 70 | 71 | #endif /* HAVE_STRTONUM */ 72 | -------------------------------------------------------------------------------- /strtonum.h: -------------------------------------------------------------------------------- 1 | #ifndef HAVE_STRTONUM 2 | long long strtonum(const char *numstr, long long minval, long long maxval, 3 | const char **errstrp); 4 | #endif 5 | --------------------------------------------------------------------------------