From e0fa525f01d8e7c1408a97bc70c11dcd24909a29 Mon Sep 17 00:00:00 2001 From: Felix Kaechele Date: Thu, 17 Oct 2019 00:52:57 -0400 Subject: [PATCH] client: Implement IPv6 This adds IPv6 capabilities to the client. A fallback to IPv4 has been implemented for DNS names that return both an A and AAAA record but where the broker only replies to IPv4 requests. This removes binding to local IPs or interfaces as we connect() the socket which makes the Kernel choose the local endpoint automatically based on the interface that was used for the outgoing request. IPv6 Addresses can be passed to the client using the -b parameter like this: tunneldigger [opts] -b [2001:db8::1]:8942 you can also specify an the interface scope for IPv6 like this: tunneldigger [opts] -b [fe80::1%ens3]:8942 Signed-off-by: Felix Kaechele --- client/l2tp_client.c | 169 ++++++++++++++++++++++++------------------- 1 file changed, 96 insertions(+), 73 deletions(-) diff --git a/client/l2tp_client.c b/client/l2tp_client.c index 69ce54c2..43ae7501 100644 --- a/client/l2tp_client.c +++ b/client/l2tp_client.c @@ -17,7 +17,6 @@ * along with this program. If not, see . */ -#define _GNU_SOURCE #include #include @@ -41,7 +40,6 @@ #include #include #include -#include #include #include @@ -65,6 +63,7 @@ // Overhead of IP and UDP headers for measuring PMTU. #define IPV4_HDR_OVERHEAD 28 +#define IPV6_HDR_OVERHEAD 48 // L2TP data header overhead for calculating tunnel MTU; takes // the following headers into account: @@ -143,6 +142,7 @@ enum l2tp_limit_type { enum l2tp_ctrl_state { STATE_REINIT, STATE_RESOLVING, + STATE_CONNECTING, STATE_GET_USAGE, STATE_STANBDY, STATE_GET_COOKIE, @@ -175,14 +175,13 @@ typedef struct { unsigned int tunnel_id; // External hook script. char *hook; - // Local IP endpoint. - struct sockaddr_in local_endpoint; // Broker hostname. char *broker_hostname; // Broker port (or service name). char *broker_port; // Broker hostname resolution. asyncns_query_t *broker_resq; + struct addrinfo *broker_res; struct addrinfo broker_reshints; // Tunnel's UDP socket file descriptor. int fd; @@ -190,6 +189,8 @@ typedef struct { int state; // Broker usage. uint16_t usage; + // Reconnection tries + int tries; // Cookie. char cookie[8]; // Netlink socket. @@ -201,8 +202,6 @@ typedef struct { uint32_t keepalive_seqno; // List of unacked reliable messages. reliable_message *reliable_unacked; - // Force the tunnel to go over a certain interface. - char *bind_iface; // Limits. uint32_t limit_bandwidth_down; // Tunnel uptime. @@ -256,9 +255,8 @@ static asyncns_t *asyncns_context = NULL; int broker_selector_usage(broker_cfg *brokers, int broker_cnt, int ready_cnt) { // Select the available broker with the least usage and use it to establish a tunnel. - int i = -1; int best = -1; - for (i = 0; i < broker_cnt; i++) { + for (int i = 0; i < broker_cnt; i++) { if (brokers[i].ctx->state == STATE_STANBDY && (best < 0 || brokers[i].ctx->usage < brokers[best].ctx->usage)) { best = i; @@ -360,8 +358,8 @@ void put_u32(char **buffer, uint32_t value) (*buffer) += sizeof(value); } -l2tp_context *context_new(char *uuid, const char *local_ip, const char *broker_hostname, - char *broker_port, char *tunnel_iface, char *bind_iface, char *hook, int tunnel_id, int limit_bandwidth_down) +l2tp_context *context_new(char *uuid, const char *broker_hostname, + char *broker_port, char *tunnel_iface, char *hook, unsigned int tunnel_id, int limit_bandwidth_down) { l2tp_context *ctx = (l2tp_context*) calloc(1, sizeof(l2tp_context)); if (!ctx) { @@ -370,13 +368,7 @@ l2tp_context *context_new(char *uuid, const char *local_ip, const char *broker_h } ctx->state = STATE_REINIT; - - ctx->local_endpoint.sin_family = AF_INET; - ctx->local_endpoint.sin_port = 0; - if (inet_aton(local_ip, &ctx->local_endpoint.sin_addr) < 0) { - syslog(LOG_ERR, "Failed to parse local endpoint!"); - goto free_and_return; - } + ctx->tries = 0; ctx->broker_hostname = strdup(broker_hostname); ctx->broker_port = strdup(broker_port); @@ -386,8 +378,6 @@ l2tp_context *context_new(char *uuid, const char *local_ip, const char *broker_h ctx->tunnel_id = tunnel_id; ctx->hook = hook ? strdup(hook) : NULL; - ctx->bind_iface = bind_iface ? strdup(bind_iface) : NULL; - // Reset limits. ctx->limit_bandwidth_down = (uint32_t) limit_bandwidth_down; @@ -421,32 +411,6 @@ int context_reinitialize(l2tp_context *ctx) // because other functions than the state machine call this function. ctx->state = STATE_REINIT; - if (ctx->fd > 0) - close(ctx->fd); - ctx->fd = socket(AF_INET, SOCK_DGRAM, 0); - if (ctx->fd < 0) - return -1; - - // Bind the socket to an interface if given. - if (ctx->bind_iface) { - int rc; - - rc = setsockopt(ctx->fd, SOL_SOCKET, SO_BINDTODEVICE, ctx->bind_iface, strlen(ctx->bind_iface) + 1); - if (rc != 0) { - syslog(LOG_ERR, "Failed to bind to device!"); - return -1; - } - } - - if (bind(ctx->fd, (struct sockaddr*) &ctx->local_endpoint, sizeof(ctx->local_endpoint)) < 0) { - syslog(LOG_ERR, "Failed to bind to local endpoint - check WAN connectivity!"); - return -1; - } - - int val = IP_PMTUDISC_PROBE; - if (setsockopt(ctx->fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)) < 0) - return -1; - ctx->keepalive_seqno = 0; ctx->reliable_seqno = 0; while (ctx->reliable_unacked != NULL) { @@ -459,6 +423,7 @@ int context_reinitialize(l2tp_context *ctx) asyncns_cancel(asyncns_context, ctx->broker_resq); ctx->broker_resq = NULL; ctx->usage = -1; + ctx->tries = 0; // Reset relevant timers. time_t now = timer_now(); @@ -491,10 +456,18 @@ void context_start_connect(l2tp_context *ctx) if (ctx->state != STATE_RESOLVING) return; + char *broker_host = malloc(strlen(ctx->broker_hostname) + 1); + strcpy(broker_host, ctx->broker_hostname); + // If this is an IPv6 address we need to cut off the brackets + if(broker_host[0] == '[') { + broker_host++; + broker_host[strlen(broker_host)-1] = 0; + } + memset(&ctx->broker_reshints, 0, sizeof(struct addrinfo)); - ctx->broker_reshints.ai_family = AF_INET; + ctx->broker_reshints.ai_family = AF_UNSPEC; ctx->broker_reshints.ai_socktype = SOCK_DGRAM; - ctx->broker_resq = asyncns_getaddrinfo(asyncns_context, ctx->broker_hostname, ctx->broker_port, + ctx->broker_resq = asyncns_getaddrinfo(asyncns_context, broker_host, ctx->broker_port, &ctx->broker_reshints); ctx->timer_resolving = timer_now(); @@ -504,6 +477,27 @@ void context_start_connect(l2tp_context *ctx) } } +int context_connect(l2tp_context *ctx) { + if (ctx->fd > 0) + close(ctx->fd); + + ctx->fd = socket(ctx->broker_res->ai_family, ctx->broker_res->ai_socktype, ctx->broker_res->ai_protocol); + + if (ctx->fd < 0) + return -1; + + int val = IP_PMTUDISC_PROBE; + if (setsockopt(ctx->fd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)) < 0) + return -1; + + if (connect(ctx->fd, ctx->broker_res->ai_addr, ctx->broker_res->ai_addrlen) < 0) { + syslog(LOG_ERR, "Failed to connect to remote endpoint - check WAN connectivity!"); + return -1; + } + + return 0; +} + void context_call_hook(l2tp_context *ctx, const char *hook) { if (ctx->hook == NULL) @@ -543,7 +537,7 @@ void context_setup_limits(l2tp_context *ctx) void context_process_control_packet(l2tp_context *ctx) { char buffer[2048]; - struct sockaddr_in endpoint; + struct sockaddr_storage endpoint; socklen_t endpoint_len = sizeof(endpoint); ssize_t bytes = recvfrom(ctx->fd, &buffer, sizeof(buffer), 0, (struct sockaddr*) &endpoint, &endpoint_len); @@ -661,7 +655,8 @@ void context_process_control_packet(l2tp_context *ctx) if (payload_length != 2) break; // Process a PMTU probe. - uint16_t psize = parse_u16(&buf) + IPV4_HDR_OVERHEAD; + int overhead = ctx->broker_res->ai_family == AF_INET6 ? IPV4_HDR_OVERHEAD : IPV6_HDR_OVERHEAD; + uint16_t psize = parse_u16(&buf) + overhead; if (psize > ctx->probed_pmtu) ctx->probed_pmtu = psize; } @@ -815,7 +810,8 @@ void context_send_pmtu_probe(l2tp_context *ctx, size_t size) put_u8(&buf, 0); // Send the packet. - if (send(ctx->fd, &buffer, size - IPV4_HDR_OVERHEAD, 0) < 0) { + int overhead = ctx->broker_res->ai_family == AF_INET6 ? IPV4_HDR_OVERHEAD : IPV6_HDR_OVERHEAD; + if (send(ctx->fd, &buffer, size - overhead, 0) < 0) { switch (errno) { // Sometimes EAFNOSUPPORT is emitted for messages larger than the local MTU in case of PPPoE. case EAFNOSUPPORT: @@ -1097,25 +1093,19 @@ void context_process(l2tp_context *ctx) // Check if address has already been resolved and change state. if (ctx->broker_resq && asyncns_isdone(asyncns_context, ctx->broker_resq)) { - struct addrinfo *result; - int status = asyncns_getaddrinfo_done(asyncns_context, ctx->broker_resq, &result); + int status = asyncns_getaddrinfo_done(asyncns_context, ctx->broker_resq, &ctx->broker_res); if (status != 0) { - syslog(LOG_ERR, "Failed to resolve hostname '%s'.", ctx->broker_hostname); + syslog(LOG_ERR, "Failed to resolve hostname '%s'. (Error %i: %s)", ctx->broker_hostname, status, gai_strerror(status)); /* TODO: memory leak - asyncns_getaddrinfo_done() does not free in all error cases ctx->broker_resp. * Fix asyncns - remove free() from asyncns_getaddrinfo_done() */ ctx->broker_resq = NULL; - ctx->state = STATE_REINIT; + ctx->broker_res = NULL; + ctx->state = STATE_RESOLVING; return; } else { - if (connect(ctx->fd, result->ai_addr, result->ai_addrlen) < 0) { - syslog(LOG_ERR, "Failed to connect to remote endpoint - check WAN connectivity!"); - ctx->state = STATE_REINIT; - } else { - ctx->state = STATE_GET_USAGE; - } - asyncns_freeaddrinfo(result); + ctx->state = STATE_CONNECTING; ctx->broker_resq = NULL; } } else if (is_timeout(&ctx->timer_resolving, 2)) { @@ -1127,6 +1117,19 @@ void context_process(l2tp_context *ctx) ctx->state = STATE_REINIT; return; } + if (ctx->state == STATE_CONNECTING) { + // Deliberate fall-through, let's get the usage ASAP. + } else { + break; + } + } + case STATE_CONNECTING: { + ctx->tries = 0; + if (context_connect(ctx) != 0) { + syslog(LOG_ERR, "Failed to connect to remote endpoint - check WAN connectivity!"); + ctx->state = STATE_REINIT; + } + ctx->state = STATE_GET_USAGE; if (ctx->state == STATE_GET_USAGE) { // Deliberate fall-through, let's get the usage ASAP. } else { @@ -1136,6 +1139,14 @@ void context_process(l2tp_context *ctx) case STATE_GET_USAGE: { if (ctx->timer_usage == 0) { // The initial request. We only ask for usage. + char str[ctx->broker_res->ai_addrlen]; + if(ctx->broker_res->ai_family == AF_INET6) { + inet_ntop(ctx->broker_res->ai_family, &((struct sockaddr_in6 *)ctx->broker_res->ai_addr)->sin6_addr , str, ctx->broker_res->ai_addrlen); + } else { + inet_ntop(ctx->broker_res->ai_family, &((struct sockaddr_in *)ctx->broker_res->ai_addr)->sin_addr , str, ctx->broker_res->ai_addrlen); + } + syslog(LOG_INFO, "Trying broker %s on IP %s", ctx->broker_hostname, str); + context_send_usage_request(ctx); ctx->timer_usage = timer_now(); } else if (is_timeout(&ctx->timer_usage, 2)) { @@ -1263,7 +1274,7 @@ void context_free(l2tp_context *ctx) free(ctx->hook); free(ctx->broker_hostname); free(ctx->broker_port); - free(ctx->bind_iface); + asyncns_freeaddrinfo(ctx->broker_res); free(ctx); } @@ -1294,10 +1305,8 @@ void show_help(const char *app) " -h this text\n" " -f don't daemonize into background\n" " -u uuid set UUID string\n" - " -l ip local IP address to bind to (default 0.0.0.0)\n" " -b host:port broker hostname and port (can be specified multiple times)\n" " -i iface tunnel interface name\n" - " -I iface force client to bind tunnel socket to a specific interface\n" " -s hook hook script\n" " -t id local tunnel id (default 1)\n" " -L limit request broker to set downstream bandwidth limit (in kbps)\n" @@ -1317,7 +1326,7 @@ int main(int argc, char **argv) // Parse program options. int log_option = 0; - char *uuid = NULL, *local_ip = "0.0.0.0", *tunnel_iface = NULL, *bind_iface_opt = NULL; + char *uuid = NULL, *tunnel_iface = NULL; char *hook = NULL; unsigned int tunnel_id = 1; int limit_bandwidth_down = 0; @@ -1327,7 +1336,7 @@ int main(int argc, char **argv) int broker_cnt = 0; int c; - while ((c = getopt(argc, argv, "hfu:l:b:p:i:s:t:L:I:agr")) != -1) { + while ((c = getopt(argc, argv, "hfu:b:p:i:s:t:L:agr")) != -1) { switch (c) { case 'h': { show_help(argv[0]); @@ -1339,14 +1348,13 @@ int main(int argc, char **argv) case 'f': log_option |= LOG_PERROR; break; case 'u': uuid = strdup(optarg); break; - case 'l': local_ip = strdup(optarg); break; case 'b': { if (broker_cnt >= MAX_BROKERS) { fprintf(stderr, "ERROR: You cannot specify more than %d brokers!\n", MAX_BROKERS); return 1; } - char *pos = strchr(optarg, ':'); + char *pos = strrchr(optarg, ':'); if (!pos) { fprintf(stderr, "ERROR: Each broker must be passed in the format 'host:port'!\n"); return 1; @@ -1363,7 +1371,6 @@ int main(int argc, char **argv) case 's': hook = strdup(optarg); break; case 't': tunnel_id = strtoul(optarg, NULL, 0); break; case 'L': limit_bandwidth_down = atoi(optarg); break; - case 'I': bind_iface_opt = strdup(optarg); break; default: { fprintf(stderr, "ERROR: Invalid option %c!\n", c); show_help(argv[0]); @@ -1400,8 +1407,8 @@ int main(int argc, char **argv) // unreachable or if the L2TP kernel modules are not loaded. We will retry for 5 minutes // and then abort. for (;;) { - brokers[i].ctx = context_new(uuid, local_ip, brokers[i].address, brokers[i].port, - tunnel_iface, bind_iface_opt, hook, tunnel_id, limit_bandwidth_down); + brokers[i].ctx = context_new(uuid, brokers[i].address, brokers[i].port, + tunnel_iface, hook, tunnel_id, limit_bandwidth_down); if (!brokers[i].ctx) { syslog(LOG_ERR, "Unable to initialize tunneldigger context! Retrying in 5 seconds..."); @@ -1449,6 +1456,16 @@ int main(int argc, char **argv) broker_select(brokers, broker_cnt); for (i = 0; i < broker_cnt; i++) { context_process(brokers[i].ctx); + // If we are stuck in in STATE_GET_USAGE and have more resolved addresses at our disposal try them up to three times. + if(brokers[i].ctx->broker_res != NULL) { + if(brokers[i].ctx->state == STATE_GET_USAGE && + brokers[i].ctx->tries > 2 && + brokers[i].ctx->broker_res->ai_next != NULL) { + brokers[i].ctx->broker_res = brokers[i].ctx->broker_res->ai_next; + brokers[i].ctx->state = STATE_CONNECTING; + } + } + brokers[i].ctx->tries++; } for (i = 0; i < broker_cnt; i++) { @@ -1476,8 +1493,14 @@ int main(int argc, char **argv) } main_context = brokers[i].ctx; - syslog(LOG_INFO, "Selected %s:%s as the best broker.", brokers[i].address, - brokers[i].port); + char *ipver; + if (brokers[i].ctx->broker_res->ai_family == AF_INET) { + ipver = "IPv4"; + } else { + ipver = "IPv6"; + } + syslog(LOG_INFO, "Selected %s:%s (%s) as the best broker.", brokers[i].address, + brokers[i].port, ipver); // Activate the broker. main_context->state = STATE_GET_COOKIE;