Skip to content

Commit

Permalink
client: Implement IPv6
Browse files Browse the repository at this point in the history
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 <felix@kaechele.ca>
  • Loading branch information
kaechele committed Oct 21, 2019
1 parent 63182ff commit 276772a
Showing 1 changed file with 96 additions and 72 deletions.
168 changes: 96 additions & 72 deletions client/l2tp_client.c
Expand Up @@ -41,7 +41,6 @@
#include <netlink/genl/genl.h>
#include <netlink/genl/mngt.h>
#include <netlink/genl/ctrl.h>
#include <netlink/utils.h>

#include <netinet/in.h>
#include <arpa/inet.h>
Expand All @@ -65,6 +64,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:
Expand Down Expand Up @@ -143,6 +143,7 @@ enum l2tp_limit_type {
enum l2tp_ctrl_state {
STATE_REINIT,
STATE_RESOLVING,
STATE_CONNECTING,
STATE_GET_USAGE,
STATE_STANBDY,
STATE_GET_COOKIE,
Expand Down Expand Up @@ -175,21 +176,22 @@ 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;
// Tunnel state.
int state;
// Broker usage.
uint16_t usage;
// Reconnection tries
int tries;
// Cookie.
char cookie[8];
// Netlink socket.
Expand All @@ -201,8 +203,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.
Expand Down Expand Up @@ -256,9 +256,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;
Expand Down Expand Up @@ -360,8 +359,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) {
Expand All @@ -370,13 +369,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);
Expand All @@ -386,8 +379,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;

Expand Down Expand Up @@ -421,32 +412,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) {
Expand All @@ -459,6 +424,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();
Expand Down Expand Up @@ -491,10 +457,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();

Expand All @@ -504,6 +478,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)
Expand Down Expand Up @@ -543,7 +538,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);
Expand Down Expand Up @@ -661,7 +656,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;
}
Expand Down Expand Up @@ -815,7 +811,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:
Expand Down Expand Up @@ -1097,25 +1094,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)) {
Expand All @@ -1127,6 +1118,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 {
Expand All @@ -1136,6 +1140,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)) {
Expand Down Expand Up @@ -1263,7 +1275,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);
}

Expand Down Expand Up @@ -1294,10 +1306,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"
Expand All @@ -1317,7 +1327,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;
Expand All @@ -1327,7 +1337,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]);
Expand All @@ -1339,14 +1349,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;
Expand All @@ -1363,7 +1372,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]);
Expand Down Expand Up @@ -1400,8 +1408,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...");
Expand Down Expand Up @@ -1449,6 +1457,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++) {
Expand Down Expand Up @@ -1476,8 +1494,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;
Expand Down

0 comments on commit 276772a

Please sign in to comment.