From c63ea9124b30873f796c3d7b13ab702e67223654 Mon Sep 17 00:00:00 2001 From: Brian Candler Date: Fri, 5 Mar 2021 16:52:22 +0000 Subject: [PATCH] Proof-of-concept for #371 Usage: listen 0.0.0.0 443 { proto tls embed fd46:1:: } If the inbound connection is IPv4, and the outbound connection is IPv6, then the IPv4 address is embedded in the low 32 bits of the source. --- src/config.c | 4 ++++ src/connection.c | 29 +++++++++++++++++++++++++++++ src/listener.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/listener.h | 3 ++- 4 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/config.c b/src/config.c index 4ff23127..a45a5467 100644 --- a/src/config.c +++ b/src/config.c @@ -125,6 +125,10 @@ static const struct Keyword listener_stanza_grammar[] = { .keyword="source", .parse_arg=(int(*)(void *, const char *))accept_listener_source_address, }, + { + .keyword="embed", + .parse_arg=(int(*)(void *, const char *))accept_listener_embed_address, + }, { .keyword="access_log", .create=(void *(*)())new_logger_builder, diff --git a/src/connection.c b/src/connection.c index c10a1090..7ccc78e3 100644 --- a/src/connection.c +++ b/src/connection.c @@ -653,6 +653,35 @@ initiate_server_connect(struct Connection *con, struct ev_loop *loop) { abort_connection(con); return; } + } else if (con->listener->embed_address + && con->client.addr.ss_family == AF_INET + && con->server.addr.ss_family == AF_INET6) { + struct sockaddr_in6 embedded_source; +#ifdef IP_FREEBIND + int on = 1; + int result = setsockopt(sockfd, SOL_IP, IP_FREEBIND, &on, sizeof(on)); + if (result < 0) { + warn("setsockopt IP_FREEBIND failed: %s", strerror(errno)); + // May not be necessary if user has set sysctl net.ipv6.ip_nonlocal_bind + } +#else + int result = -EPERM; +#endif + + memcpy(&embedded_source, + address_sa(con->listener->embed_address), + sizeof(embedded_source)); + memcpy(&(embedded_source.sin6_addr.s6_addr[12]), + &((struct sockaddr_in*)(&con->client.addr))->sin_addr, 4); + // XXX: maybe optionally the source port in bits 80-95 + + result = bind(sockfd, &embedded_source, sizeof(embedded_source)); + if (result < 0) { + err("bind failed: %s", strerror(errno)); + close(sockfd); + abort_connection(con); + return; + } } else if (con->listener->source_address) { int on = 1; int result = setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); diff --git a/src/listener.c b/src/listener.c index ce5956da..e693314a 100644 --- a/src/listener.c +++ b/src/listener.c @@ -176,6 +176,10 @@ listener_update(struct Listener *existing_listener, struct Listener *new_listene existing_listener->source_address = new_listener->source_address; new_listener->source_address = NULL; + free(existing_listener->embed_address); + existing_listener->embed_address = new_listener->embed_address; + new_listener->embed_address = NULL; + existing_listener->protocol = new_listener->protocol; free(existing_listener->table_name); @@ -212,6 +216,7 @@ new_listener() { listener->address = NULL; listener->fallback_address = NULL; listener->source_address = NULL; + listener->embed_address = NULL; listener->protocol = tls_protocol; listener->table_name = NULL; listener->access_log = NULL; @@ -406,6 +411,40 @@ accept_listener_source_address(struct Listener *listener, const char *source) { return 1; } +int +accept_listener_embed_address(struct Listener *listener, const char *embed) { + if (listener->embed_address != NULL) { + err("Duplicate embed address: %s", embed); + return 0; + } + + listener->embed_address = new_address(embed); + if (listener->embed_address == NULL) { + err("Unable to parse embed address: %s", embed); + return 0; + } + if (!address_is_sockaddr(listener->embed_address)) { + err("Only embed socket addresses permitted"); + free(listener->embed_address); + listener->embed_address = NULL; + return 0; + } + if (address_sa(listener->embed_address)->sa_family != AF_INET6) { + err("Embed address must be IPv6 prefix"); + free(listener->embed_address); + listener->embed_address = NULL; + return 0; + } + if (address_port(listener->embed_address) != 0) { + err("Port on embed address not permitted"); + free(listener->embed_address); + listener->embed_address = NULL; + return 0; + } + + return 1; +} + int accept_listener_bad_request_action(struct Listener *listener, const char *action) { if (strncmp("log", action, strlen(action)) == 0) { @@ -707,6 +746,11 @@ print_listener_config(FILE *file, const struct Listener *listener) { display_address(listener->source_address, address, sizeof(address))); + if (listener->embed_address) + fprintf(file, "\tembed %s\n", + display_address(listener->embed_address, + address, sizeof(address))); + if (listener->reuseport) fprintf(file, "\treuseport on\n"); @@ -732,6 +776,7 @@ free_listener(struct Listener *listener) { free(listener->address); free(listener->fallback_address); free(listener->source_address); + free(listener->embed_address); free(listener->table_name); table_ref_put(listener->table); diff --git a/src/listener.h b/src/listener.h index c6fb5514..ad3adf7a 100644 --- a/src/listener.h +++ b/src/listener.h @@ -35,7 +35,7 @@ SLIST_HEAD(Listener_head, Listener); struct Listener { /* Configuration fields */ - struct Address *address, *fallback_address, *source_address; + struct Address *address, *fallback_address, *source_address, *embed_address; const struct Protocol *protocol; char *table_name; struct Logger *access_log; @@ -57,6 +57,7 @@ int accept_listener_arg(struct Listener *, const char *); int accept_listener_table_name(struct Listener *, const char *); int accept_listener_fallback_address(struct Listener *, const char *); int accept_listener_source_address(struct Listener *, const char *); +int accept_listener_embed_address(struct Listener *, const char *); int accept_listener_protocol(struct Listener *, const char *); int accept_listener_reuseport(struct Listener *, const char *); int accept_listener_ipv6_v6only(struct Listener *, const char *);