From 4629f6d8367a9f6ad8ca9f7d70746be26867d894 Mon Sep 17 00:00:00 2001 From: dmiller Date: Thu, 16 Mar 2017 21:54:26 +0000 Subject: [PATCH] Ncat supports DNS failover, including fallback to IPv4 from IPv6 --- CHANGELOG | 5 ++ ncat/docs/ncatguide.xml | 17 ++++-- ncat/ncat_connect.c | 127 ++++++++++++++++++++++++---------------- ncat/ncat_core.c | 62 +++++++++++++++++--- ncat/ncat_core.h | 18 +++++- ncat/ncat_main.c | 64 +++++++++++--------- ncat/test/ncat-test.pl | 23 ++++++++ ncat/util.c | 4 +- 8 files changed, 223 insertions(+), 97 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 30b13632c..12432dcb3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,10 @@ # Nmap Changelog ($Id$); -*-text-*- +o [Ncat][GH#157] Ncat will now continue trying to connect to each resolved + address for a hostname before declaring the connection refused, allowing it + to fallback from IPv6 to IPv4 or to connect to names that use DNS failover. + [Jaromir Koncicky, Michal Hlavinka] + o [NSE][GH#743] New script broadcast-ospf2-discover discovers OSPF 2 routers and neighbors. OSPFv2 authentication is supported. [Emiliano Ticci] diff --git a/ncat/docs/ncatguide.xml b/ncat/docs/ncatguide.xml index 402e45c08..991e41b6b 100644 --- a/ncat/docs/ncatguide.xml +++ b/ncat/docs/ncatguide.xml @@ -137,16 +137,19 @@ the tool? Many of these examples suppose a Unix environment. --> - By default, Ncat uses TCP and IPv4. The option + By default, Ncat uses TCP. The option (Ncat option) or (Ncat option) enables UDP instead, - (Ncat option) - enables SCTP,SCTPin Ncat and + (Ncat option) + enables SCTP.SCTPin Ncat + Ncat listens on both IPv4 and IPv6, and connects to either address family as well. The (Ncat option) - enables IPv6. See for more details. + option forces IPv6-only, and + (Ncat option) + forces IPv4-only. See for more details. The rest of this guide documents all the Ncat options through descriptions and examples. For a quick summary of options at any time, run @@ -331,8 +334,8 @@ Content-Type: text/html; charset=UTF-8 IPv4,IPv4in Ncat - the Internet Protocol version 4, is the dominant version of the - Internet Protocol in use. Ncat uses it by default. Using the + the Internet Protocol version 4, is the most popular version of the + Internet Protocol in use. Using the puts Ncat into IPv4-only mode; only IPv4 addresses will be used even if, for example, as hostname resolves to IPv6 addresses as well. @@ -343,6 +346,8 @@ Content-Type: text/html; charset=UTF-8 is the lesser-used successor to IPv4. Use (Ncat option) to put Ncat into IPv6-only mode. + By default, Ncat will listen on both IPv4 and IPv6, and will connect to + resolved addresses in the order they are returned by the operating system. diff --git a/ncat/ncat_connect.c b/ncat/ncat_connect.c index 6997f1aae..b7773bd57 100644 --- a/ncat/ncat_connect.c +++ b/ncat/ncat_connect.c @@ -171,6 +171,7 @@ static struct conn_state cs = { 0 }; +static void try_nsock_connect(nsock_pool nsp, struct sockaddr_list *conn_addr); static void connect_handler(nsock_pool nsp, nsock_event evt, void *data); static void post_connect(nsock_pool nsp, nsock_iod iod); static void read_stdin_handler(nsock_pool nsp, nsock_event evt, void *data); @@ -551,8 +552,8 @@ static int do_proxy_socks4(void) socket_buffer_init(&stateful_buf, sd); if (o.verbose) { - loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetss), - inet_port(&targetss)); + loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetaddrs->addr), + inet_port(&targetaddrs->addr)); } /* Fill the socks4_data struct */ @@ -652,8 +653,8 @@ static int do_proxy_socks5(void) socket_buffer_init(&stateful_buf, sd); if (o.verbose) { - loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetss), - inet_port(&targetss)); + loguser("Connected to proxy %s:%hu\n", inet_socktop(&targetaddrs->addr), + inet_port(&targetaddrs->addr)); } zmem(&socks5msg,sizeof(socks5msg)); @@ -983,7 +984,7 @@ int ncat_connect(void) if (o.af != AF_INET) bye("Sorry, -g can only currently be used with IPv4."); - ipopts = buildsrcrte(targetss.in.sin_addr, o.srcrtes, o.numsrcrtes, o.srcrteptr, &ipoptslen); + ipopts = buildsrcrte(targetaddrs->addr.in.sin_addr, o.srcrtes, o.numsrcrtes, o.srcrteptr, &ipoptslen); nsock_iod_set_ipoptions(cs.sock_nsi, ipopts, ipoptslen); free(ipopts); /* Nsock has its own copy */ @@ -993,49 +994,18 @@ int ncat_connect(void) if (o.af == AF_UNIX) { if (o.proto == IPPROTO_UDP) { nsock_connect_unixsock_datagram(mypool, cs.sock_nsi, connect_handler, NULL, - &targetss.sockaddr, - SUN_LEN((struct sockaddr_un *)&targetss.sockaddr)); + &targetaddrs->addr.sockaddr, + SUN_LEN((struct sockaddr_un *)&targetaddrs->addr.sockaddr)); } else { nsock_connect_unixsock_stream(mypool, cs.sock_nsi, connect_handler, o.conntimeout, - NULL, &targetss.sockaddr, - SUN_LEN((struct sockaddr_un *)&targetss.sockaddr)); + NULL, &targetaddrs->addr.sockaddr, + SUN_LEN((struct sockaddr_un *)&targetaddrs->addr.sockaddr)); } } else #endif - if (o.proto == IPPROTO_UDP) { - nsock_connect_udp(mypool, cs.sock_nsi, connect_handler, - NULL, &targetss.sockaddr, targetsslen, - inet_port(&targetss)); - } -#ifdef HAVE_OPENSSL - else if (o.proto == IPPROTO_SCTP && o.ssl) { - nsock_connect_ssl(mypool, cs.sock_nsi, connect_handler, - o.conntimeout, NULL, - &targetss.sockaddr, targetsslen, - IPPROTO_SCTP, inet_port(&targetss), - NULL); - } -#endif - else if (o.proto == IPPROTO_SCTP) { - nsock_connect_sctp(mypool, cs.sock_nsi, connect_handler, - o.conntimeout, NULL, - &targetss.sockaddr, targetsslen, - inet_port(&targetss)); - } -#ifdef HAVE_OPENSSL - else if (o.ssl) { - nsock_connect_ssl(mypool, cs.sock_nsi, connect_handler, - o.conntimeout, NULL, - &targetss.sockaddr, targetsslen, - IPPROTO_TCP, inet_port(&targetss), - NULL); - } -#endif - else { - nsock_connect_tcp(mypool, cs.sock_nsi, connect_handler, - o.conntimeout, NULL, - &targetss.sockaddr, targetsslen, - inet_port(&targetss)); + { + /* Add connection to first resolved address. */ + try_nsock_connect(mypool, targetaddrs); } } else { /* A proxy connection. */ @@ -1071,6 +1041,8 @@ int ncat_connect(void) /* connect */ rc = nsock_loop(mypool, -1); + free_sockaddr_list(targetaddrs); + if (o.verbose) { struct timeval end_time; double time; @@ -1094,6 +1066,45 @@ int ncat_connect(void) return rc == NSOCK_LOOP_ERROR ? 1 : 0; } +static void try_nsock_connect(nsock_pool nsp, struct sockaddr_list *conn_addr) +{ + if (o.proto == IPPROTO_UDP) { + nsock_connect_udp(nsp, cs.sock_nsi, connect_handler, (void *)conn_addr->next, + &conn_addr->addr.sockaddr, conn_addr->addrlen, + inet_port(&conn_addr->addr)); + } +#ifdef HAVE_OPENSSL + else if (o.proto == IPPROTO_SCTP && o.ssl) { + nsock_connect_ssl(nsp, cs.sock_nsi, connect_handler, + o.conntimeout, (void *)conn_addr->next, + &conn_addr->addr.sockaddr, conn_addr->addrlen, + IPPROTO_SCTP, inet_port(&conn_addr->addr), + NULL); + } +#endif + else if (o.proto == IPPROTO_SCTP) { + nsock_connect_sctp(nsp, cs.sock_nsi, connect_handler, + o.conntimeout, (void *)conn_addr->next, + &conn_addr->addr.sockaddr, conn_addr->addrlen, + inet_port(&conn_addr->addr)); + } +#ifdef HAVE_OPENSSL + else if (o.ssl) { + nsock_connect_ssl(nsp, cs.sock_nsi, connect_handler, + o.conntimeout, (void *)conn_addr->next, + &conn_addr->addr.sockaddr, conn_addr->addrlen, + IPPROTO_TCP, inet_port(&conn_addr->addr), + NULL); + } +#endif + else { + nsock_connect_tcp(nsp, cs.sock_nsi, connect_handler, + o.conntimeout, (void *)conn_addr->next, + &conn_addr->addr.sockaddr, conn_addr->addrlen, + inet_port(&conn_addr->addr)); + } +} + static void send_udp_null(nsock_pool nsp) { char *NULL_PROBE = "\0"; @@ -1105,17 +1116,31 @@ static void connect_handler(nsock_pool nsp, nsock_event evt, void *data) { enum nse_status status = nse_status(evt); enum nse_type type = nse_type(evt); + struct sockaddr_list *next_addr = (struct sockaddr_list *)data; ncat_assert(type == NSE_TYPE_CONNECT || type == NSE_TYPE_CONNECT_SSL); - if (status == NSE_STATUS_ERROR) { - if (!o.zerobyte||o.verbose) - loguser("%s.\n", socket_strerror(nse_errorcode(evt))); - exit(1); - } else if (status == NSE_STATUS_TIMEOUT) { - if (!o.zerobyte||o.verbose) - loguser("%s.\n", socket_strerror(ETIMEDOUT)); - exit(1); + if (status == NSE_STATUS_ERROR || status == NSE_STATUS_TIMEOUT) { + int errcode = (status == NSE_STATUS_TIMEOUT)?ETIMEDOUT:nse_errorcode(evt); + /* If there are more resolved addresses, try connecting to next one */ + if (next_addr != NULL) { + if (o.verbose) { + union sockaddr_u peer; + zmem(&peer, sizeof(peer.storage)); + nsock_iod_get_communication_info(cs.sock_nsi, NULL, NULL, NULL, + &peer.sockaddr, sizeof(peer.storage)); + loguser("Connection to %s failed: %s.\n", inet_socktop(&peer), socket_strerror(errcode)); + loguser("Trying next address...\n"); + } + try_nsock_connect(nsp, next_addr); + return; + } + else { + free_sockaddr_list(targetaddrs); + if (!o.zerobyte||o.verbose) + loguser("%s.\n", socket_strerror(errcode)); + exit(1); + } } else { ncat_assert(status == NSE_STATUS_SUCCESS); } diff --git a/ncat/ncat_core.c b/ncat/ncat_core.c index 53d5821be..15d21bb9d 100644 --- a/ncat/ncat_core.c +++ b/ncat/ncat_core.c @@ -153,8 +153,7 @@ int num_listenaddrs = 0; union sockaddr_u srcaddr; size_t srcaddrlen; -union sockaddr_u targetss; -size_t targetsslen; +struct sockaddr_list *targetaddrs; /* Global options structure. */ struct options o; @@ -220,18 +219,22 @@ void options_init(void) } /* Internal helper for resolve and resolve_numeric. addl_flags is ored into - hints.ai_flags, so you can add AI_NUMERICHOST. */ + hints.ai_flags, so you can add AI_NUMERICHOST. + sl is a pointer to first element of sockaddr linked list, which is always + statically allocated. Next list elements are dynamically allocated. + If multiple_addrs is false then only first address is returned. */ static int resolve_internal(const char *hostname, unsigned short port, - struct sockaddr_storage *ss, size_t *sslen, int af, int addl_flags) + struct sockaddr_list *sl, int af, int addl_flags, int multiple_addrs) { struct addrinfo hints; struct addrinfo *result; + struct addrinfo *next; + struct sockaddr_list **item_ptr = &sl; + struct sockaddr_list *new_item; char portbuf[16]; int rc; ncat_assert(hostname != NULL); - ncat_assert(ss != NULL); - ncat_assert(sslen != NULL); memset(&hints, 0, sizeof(hints)); hints.ai_family = af; @@ -248,8 +251,19 @@ static int resolve_internal(const char *hostname, unsigned short port, if (result == NULL) return EAI_NONAME; ncat_assert(result->ai_addrlen > 0 && result->ai_addrlen <= (int) sizeof(struct sockaddr_storage)); - *sslen = result->ai_addrlen; - memcpy(ss, result->ai_addr, *sslen); + for (next = result; next != NULL; next = next->ai_next) { + if (*item_ptr == NULL) + { + *item_ptr = (struct sockaddr_list *)safe_malloc(sizeof(struct sockaddr_list)); + (**item_ptr).next = NULL; + } + new_item = *item_ptr; + new_item->addrlen = next->ai_addrlen; + memcpy(&new_item->addr.storage, next->ai_addr, next->ai_addrlen); + if (!multiple_addrs) + break; + item_ptr = &new_item->next; + } freeaddrinfo(result); return 0; @@ -268,12 +282,42 @@ int resolve(const char *hostname, unsigned short port, struct sockaddr_storage *ss, size_t *sslen, int af) { int flags; + struct sockaddr_list sl; + int result; flags = 0; if (o.nodns) flags |= AI_NUMERICHOST; - return resolve_internal(hostname, port, ss, sslen, af, flags); + result = resolve_internal(hostname, port, &sl, af, flags, 0); + *ss = sl.addr.storage; + *sslen = sl.addrlen; + return result; +} + +/* Resolves the given hostname or IP address with getaddrinfo, and stores + all results into a linked list. + The rest of the behavior is same as resolve(). */ +int resolve_multi(const char *hostname, unsigned short port, + struct sockaddr_list *sl, int af) +{ + int flags; + + flags = 0; + if (o.nodns) + flags |= AI_NUMERICHOST; + + return resolve_internal(hostname, port, sl, af, flags, 1); +} + +void free_sockaddr_list(struct sockaddr_list *sl) +{ + struct sockaddr_list *current, *next = sl; + while (next != NULL) { + current = next; + next = current->next; + free(current); + } } int fdinfo_close(struct fdinfo *fdn) diff --git a/ncat/ncat_core.h b/ncat/ncat_core.h index 343192825..98a6d860a 100644 --- a/ncat/ncat_core.h +++ b/ncat/ncat_core.h @@ -139,14 +139,20 @@ a IPV4 INADDR_ANY and a IPV6 in6addr_any at most or a user defined address */ #define NUM_LISTEN_ADDRS 2 +/* Structure to store a linked list of resolved addresses. */ +struct sockaddr_list { + union sockaddr_u addr; + size_t addrlen; + struct sockaddr_list* next; +}; + extern union sockaddr_u listenaddrs[NUM_LISTEN_ADDRS]; extern int num_listenaddrs; extern union sockaddr_u srcaddr; extern size_t srcaddrlen; -extern union sockaddr_u targetss; -extern size_t targetsslen; +extern struct sockaddr_list *targetaddrs; enum exec_mode { EXEC_PLAIN, @@ -235,6 +241,14 @@ void options_init(void); int resolve(const char *hostname, unsigned short port, struct sockaddr_storage *ss, size_t *sslen, int af); +/* Resolves the given hostname or IP address with getaddrinfo, and stores + all results into a linked list. + The rest of behavior is same as resolve(). */ +int resolve_multi(const char *hostname, unsigned short port, + struct sockaddr_list *sl, int af); + +void free_sockaddr_list(struct sockaddr_list *sl); + int fdinfo_close(struct fdinfo *fdn); int fdinfo_recv(struct fdinfo *fdn, char *buf, size_t size); int fdinfo_send(struct fdinfo *fdn, const char *buf, size_t size); diff --git a/ncat/ncat_main.c b/ncat/ncat_main.c index 79074bed8..fe7b5275c 100644 --- a/ncat/ncat_main.c +++ b/ncat/ncat_main.c @@ -688,15 +688,18 @@ int main(int argc, char *argv[]) } #endif /* HAVE_SYS_UN_H */ + /* Create a static target address, because at least one target address must be always allocated */ + targetaddrs = (struct sockaddr_list *)safe_zalloc(sizeof(struct sockaddr_list)); + /* Will be AF_INET or AF_INET6 or AF_UNIX when valid */ - memset(&targetss.storage, 0, sizeof(targetss.storage)); - targetss.storage.ss_family = AF_UNSPEC; - srcaddr.storage = targetss.storage; + memset(&srcaddr.storage, 0, sizeof(srcaddr.storage)); + srcaddr.storage.ss_family = AF_UNSPEC; + targetaddrs->addr.storage = srcaddr.storage; /* Clear the listenaddrs array */ int i; for (i = 0; i < NUM_LISTEN_ADDRS; i++) { - listenaddrs[i].storage = targetss.storage; + listenaddrs[i].storage = srcaddr.storage; } if (o.proxyaddr) { @@ -712,12 +715,12 @@ int main(int argc, char *argv[]) * (due to the colons in the IPv6 address and host:port separator). */ - targetsslen = parseproxy(o.proxyaddr, - &targetss.storage, &targetsslen, &proxyport); + targetaddrs->addrlen = parseproxy(o.proxyaddr, + &targetaddrs->addr.storage, &targetaddrs->addrlen, &proxyport); if (o.af == AF_INET) { - targetss.in.sin_port = htons(proxyport); + targetaddrs->addr.in.sin_port = htons(proxyport); } else { // might modify to else if and test AF_{INET6|UNIX|UNSPEC} - targetss.in6.sin6_port = htons(proxyport); + targetaddrs->addr.in6.sin6_port = htons(proxyport); } } else { bye("Invalid proxy type \"%s\".", o.proxytype); @@ -796,10 +799,10 @@ int main(int argc, char *argv[]) } else { #if HAVE_SYS_UN_H if (o.af == AF_UNIX) { - memset(&targetss.storage, 0, sizeof(struct sockaddr_un)); - targetss.un.sun_family = AF_UNIX; - strncpy(targetss.un.sun_path, argv[optind], sizeof(targetss.un.sun_path)); - targetsslen = SUN_LEN(&targetss.un); + memset(&targetaddrs->addr.storage, 0, sizeof(struct sockaddr_un)); + targetaddrs->addr.un.sun_family = AF_UNIX; + strncpy(targetaddrs->addr.un.sun_path, argv[optind], sizeof(targetaddrs->addr.un.sun_path)); + targetaddrs->addrlen = SUN_LEN(&targetaddrs->addr.un); o.target = argv[optind]; optind++; } else @@ -813,7 +816,7 @@ int main(int argc, char *argv[]) * targetss contains data already and you don't want remove them */ if( !o.proxytype - && (rc = resolve(o.target, 0, &targetss.storage, &targetsslen, o.af)) != 0) + && (rc = resolve_multi(o.target, 0, targetaddrs, o.af)) != 0) bye("Could not resolve hostname \"%s\": %s.", o.target, gai_strerror(rc)); optind++; @@ -851,21 +854,28 @@ int main(int argc, char *argv[]) if (o.proxytype && !o.listen) ; /* Do nothing - port is already set to proxyport */ - else if (targetss.storage.ss_family == AF_INET) - targetss.in.sin_port = htons(o.portno); + else { + struct sockaddr_list *targetaddrs_item = targetaddrs; + while (targetaddrs_item != NULL) + { + if (targetaddrs_item->addr.storage.ss_family == AF_INET) + targetaddrs_item->addr.in.sin_port = htons(o.portno); #ifdef HAVE_IPV6 - else if (targetss.storage.ss_family == AF_INET6) - targetss.in6.sin6_port = htons(o.portno); + else if (targetaddrs_item->addr.storage.ss_family == AF_INET6) + targetaddrs_item->addr.in6.sin6_port = htons(o.portno); #endif #if HAVE_SYS_UN_H - /* If we use Unix domain sockets, we have to count with them. */ - else if (targetss.storage.ss_family == AF_UNIX) - ; /* Do nothing. */ + /* If we use Unix domain sockets, we have to count with them. */ + else if (targetaddrs_item->addr.storage.ss_family == AF_UNIX) + ; /* Do nothing. */ #endif - else if (targetss.storage.ss_family == AF_UNSPEC) - ; /* Leave unspecified. */ - else - bye("Unknown address family %d.", targetss.storage.ss_family); + else if (targetaddrs_item->addr.storage.ss_family == AF_UNSPEC) + ; /* Leave unspecified. */ + else + bye("Unknown address family %d.", targetaddrs_item->addr.storage.ss_family); + targetaddrs_item = targetaddrs_item->next; + } + } if (srcport != -1) { if (o.listen) { @@ -877,7 +887,7 @@ int main(int argc, char *argv[]) /* We have a source port but not an explicit source address; fill in an unspecified address of the same family as the target. */ - srcaddr.storage.ss_family = targetss.storage.ss_family; + srcaddr.storage.ss_family = targetaddrs->addr.storage.ss_family; if (srcaddr.storage.ss_family == AF_INET) srcaddr.in.sin_addr.s_addr = INADDR_ANY; else if (srcaddr.storage.ss_family == AF_INET6) @@ -969,8 +979,8 @@ static int ncat_listen_mode(void) bye("/bin/sh is not executable, so `-c' won't work."); #endif - if (targetss.storage.ss_family != AF_UNSPEC) { - listenaddrs[num_listenaddrs++] = targetss; + if (targetaddrs->addr.storage.ss_family != AF_UNSPEC) { + listenaddrs[num_listenaddrs++] = targetaddrs->addr; } else { size_t ss_len; int rc; diff --git a/ncat/test/ncat-test.pl b/ncat/test/ncat-test.pl index 496fd2557..e53d00051 100755 --- a/ncat/test/ncat-test.pl +++ b/ncat/test/ncat-test.pl @@ -600,6 +600,29 @@ sub { }; kill_children; +($s_pid, $s_out, $s_in) = ncat("-4", "-lk"); +test "Connect fallback with IPv4 server", +sub { + my $resp; + + my ($c_pid, $c_out, $c_in) = ncat("localhost"); + syswrite($c_in, "abc\n"); + $resp = timeout_read($s_out); + $resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\""; +}; + +($s_pid, $s_out, $s_in) = ncat("-6", "-lk"); +test "Connect fallback with IPv6 server", +sub { + my $resp; + + my ($c_pid, $c_out, $c_in) = ncat("localhost"); + syswrite($c_in, "abc\n"); + $resp = timeout_read($s_out); + $resp eq "abc\n" or die "Server got \"$resp\", not \"abc\\n\""; +}; + +kill_children; # Test UNIX domain sockets listening { local $xfail = 1 if !$HAVE_UNIXSOCK; diff --git a/ncat/util.c b/ncat/util.c index b823049da..791f55b2a 100644 --- a/ncat/util.c +++ b/ncat/util.c @@ -510,7 +510,7 @@ int do_connect(int type) /* We need a socket that can be inherited by child processes in ncat_exec_win.c, for --exec and --sh-exec. inheritable_socket is from nbase. */ - sock = inheritable_socket(targetss.storage.ss_family, type, 0); + sock = inheritable_socket(targetaddrs->addr.storage.ss_family, type, 0); if (srcaddr.storage.ss_family != AF_UNSPEC) { size_t sa_len; @@ -527,7 +527,7 @@ int do_connect(int type) } if (sock != -1) { - if (connect(sock, &targetss.sockaddr, (int) targetsslen) != -1) + if (connect(sock, &targetaddrs->addr.sockaddr, (int) targetaddrs->addrlen) != -1) return sock; else if (socket_errno() == EINPROGRESS || socket_errno() == EAGAIN) return sock;