From badf3dd69e966fbe882ea161b1129225e72791a6 Mon Sep 17 00:00:00 2001 From: Anton Rieger Date: Wed, 3 Apr 2019 17:11:04 +0200 Subject: [PATCH] Added -4 -6 Switches and default tcp46 As *BSD defaults to use IPv6 only sockets and Linux to IPv4 mapped IPv6 sockets, switches to support explicit binding address families are required. Now set explicitly if you want IPv6 only, IPv4 only or mapped IPv4. Caveat: OpenBSD explicitly states to not support IPv4 mapped IPv6 via setsock-API --- README.md | 8 ++++ endlessh.c | 94 ++++++++++++++++++++++++++++++++++++------ util/smf/endlessh.conf | 6 +++ 3 files changed, 95 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 811bc7a..e1279d1 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Usage information is printed with `-h`. ``` Usage: endlessh [-vh] [-d MS] [-f CONFIG] [-l LEN] [-m LIMIT] [-p PORT] + -4 Bind to IPv4 only + -6 Bind to IPv6 only -d INT Message millisecond delay [10000] -f Set and load config file [/etc/endlessh/config] -h Print this help message and exit @@ -69,6 +71,12 @@ MaxClients 4096 # 1 = Standard, useful log messages # 2 = Very noisy debugging information LogLevel 0 + +# Set the family of the listening socket +# 0 = Use IPv4 Mapped IPv6 (Both v4 and v6, default) +# 4 = Use IPv4 only +# 6 = Use IPv6 only +BindFamily 0 ``` ## Build issues diff --git a/endlessh.c b/endlessh.c index 90543aa..6371fd7 100644 --- a/endlessh.c +++ b/endlessh.c @@ -23,6 +23,7 @@ #define DEFAULT_MAX_LINE_LENGTH 32 #define DEFAULT_MAX_CLIENTS 4096 #define DEFAULT_CONFIG_FILE "/etc/endlessh/config" +#define DEFAULT_BIND_FAMILY AF_UNSPEC #define XSTR(s) STR(s) #define STR(s) #s @@ -87,6 +88,7 @@ client_new(int fd, long long send_next) c->bytes_sent = 0; c->next = 0; c->fd = fd; + c->port = 0; /* Set the smallest possible recieve buffer. This reduces local * resource usage and slows down the remote end. @@ -231,6 +233,7 @@ struct config { int delay; int max_line_length; int max_clients; + int bind_family; }; #define CONFIG_DEFAULT { \ @@ -238,6 +241,7 @@ struct config { .delay = DEFAULT_DELAY, \ .max_line_length = DEFAULT_MAX_LINE_LENGTH, \ .max_clients = DEFAULT_MAX_CLIENTS, \ + .bind_family = DEFAULT_BIND_FAMILY, \ } static void @@ -300,6 +304,27 @@ config_set_max_line_length(struct config *c, const char *s, int hardfail) } } +static void +config_set_bind_family(struct config *c, const char *s, int hardfail) +{ + switch (*s) { + case '4': + c->bind_family = AF_INET; + break; + case '6': + c->bind_family = AF_INET6; + break; + case '0': + c->bind_family = AF_UNSPEC; + break; + default: + fprintf(stderr, "endlessh: Invalid address family: %s\n", s); + if (hardfail) + exit(EXIT_FAILURE); + break; + } +} + enum config_key { KEY_INVALID, KEY_PORT, @@ -307,6 +332,7 @@ enum config_key { KEY_MAX_LINE_LENGTH, KEY_MAX_CLIENTS, KEY_LOG_LEVEL, + KEY_BIND_FAMILY, }; static enum config_key @@ -318,6 +344,7 @@ config_key_parse(const char *tok) [KEY_MAX_LINE_LENGTH] = "MaxLineLength", [KEY_MAX_CLIENTS] = "MaxClients", [KEY_LOG_LEVEL] = "LogLevel", + [KEY_BIND_FAMILY] = "BindFamily" }; for (size_t i = 1; i < sizeof(table) / sizeof(*table); i++) if (!strcmp(tok, table[i])) @@ -384,6 +411,9 @@ config_load(struct config *c, const char *file, int hardfail) case KEY_MAX_CLIENTS: config_set_max_clients(c, tokens[1], hardfail); break; + case KEY_BIND_FAMILY: + config_set_bind_family(c, tokens[1], hardfail); + break; case KEY_LOG_LEVEL: { errno = 0; char *end; @@ -410,13 +440,19 @@ config_log(const struct config *c) logmsg(LOG_INFO, "Delay %ld", c->delay); logmsg(LOG_INFO, "MaxLineLength %d", c->max_line_length); logmsg(LOG_INFO, "MaxClients %d", c->max_clients); + logmsg(LOG_INFO, "BindFamily %s", + c->bind_family == AF_INET6 ? "IPv6 Only" : + c->bind_family == AF_INET ? "IPv4 Only" : + "IPv4 Mapped IPv6"); } static void usage(FILE *f) { - fprintf(f, "Usage: endlessh [-vh] [-d MS] [-f CONFIG] [-l LEN] " + fprintf(f, "Usage: endlessh [-vh] [-46] [-d MS] [-f CONFIG] [-l LEN] " "[-m LIMIT] [-p PORT]\n"); + fprintf(f, " -4 Bind to IPv4 only"); + fprintf(f, " -6 Bind to IPv6 only"); fprintf(f, " -d INT Message millisecond delay [" XSTR(DEFAULT_DELAY) "]\n"); fprintf(f, " -f Set and load config file [" @@ -439,11 +475,11 @@ print_version(void) } static int -server_create(int port) +server_create(int port, int family) { int r, s, value; - s = socket(AF_INET6, SOCK_STREAM, 0); + s = socket(family == AF_UNSPEC ? AF_INET6 : family, SOCK_STREAM, 0); logmsg(LOG_DEBUG, "socket() = %d", s); if (s == -1) die(); @@ -454,12 +490,37 @@ server_create(int port) if (r == -1) logmsg(LOG_DEBUG, "errno = %d, %s", errno, strerror(errno)); - struct sockaddr_in6 addr = { - .sin6_family = AF_INET6, - .sin6_port = htons(port), - .sin6_addr = in6addr_any - }; - r = bind(s, (void *)&addr, sizeof(addr)); + /* + * With OpenBSD IPv6 sockets are always IPv6-only, so the socket option + * is read-only (not modifiable). + * http://man.openbsd.org/ip6#IPV6_V6ONLY + */ +#ifndef __OpenBSD__ + if (family == AF_INET6 || family == AF_UNSPEC) { + errno = 0; + value = (family == AF_INET6); + r = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value)); + logmsg(LOG_DEBUG, "setsockopt(%d, IPV6_V6ONLY, true) = %d", s, r); + if (r == -1) + logmsg(LOG_DEBUG, "errno = %d, %s", errno, strerror(errno)); + } +#endif + + if (family == AF_INET) { + struct sockaddr_in addr4 = { + .sin_family = AF_INET, + .sin_port = htons(port), + .sin_addr = {INADDR_ANY} + }; + r = bind(s, (void *)&addr4, sizeof(addr4)); + } else { + struct sockaddr_in6 addr6 = { + .sin6_family = AF_INET6, + .sin6_port = htons(port), + .sin6_addr = in6addr_any + }; + r = bind(s, (void *)&addr6, sizeof(addr6)); + } logmsg(LOG_DEBUG, "bind(%d, port=%d) = %d", s, port, r); if (r == -1) die(); @@ -503,8 +564,14 @@ main(int argc, char **argv) config_load(&config, config_file, 1); int option; - while ((option = getopt(argc, argv, "d:f:hl:m:p:vV")) != -1) { + while ((option = getopt(argc, argv, "46d:f:hl:m:p:vV")) != -1) { switch (option) { + case '4': + config_set_bind_family(&config, "4", 1); + break; + case '6': + config_set_bind_family(&config, "6", 1); + break; case 'd': config_set_delay(&config, optarg, 1); break; @@ -567,17 +634,18 @@ main(int argc, char **argv) unsigned long rng = uepoch(); - int server = server_create(config.port); + int server = server_create(config.port, config.bind_family); while (running) { if (reload) { /* Configuration reload requested (SIGHUP) */ int oldport = config.port; + int oldfamily = config.bind_family; config_load(&config, config_file, 0); config_log(&config); - if (oldport != config.port) { + if (oldport != config.port || oldfamily != config.bind_family) { close(server); - server = server_create(config.port); + server = server_create(config.port, config.bind_family); } reload = 0; } diff --git a/util/smf/endlessh.conf b/util/smf/endlessh.conf index a33e192..5137178 100644 --- a/util/smf/endlessh.conf +++ b/util/smf/endlessh.conf @@ -19,3 +19,9 @@ MaxClients 4096 # 1 = Standard, useful log messages # 2 = Very noisy debugging information LogLevel 1 + +# Set the family of the listening socket +# 0 = Use IPv4 Mapped IPv6 (Both v4 and v6, default) +# 4 = Use IPv4 only +# 6 = Use IPv6 only +BindFamily 0