diff --git a/.gitignore b/.gitignore index 507459c..735ec3a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,11 @@ configure.ac config.* Makefile autom4te.cache -benchmark edge example_edge_embed supernode +tools/n2n-benchmark +tools/n2n-decode build packages/debian/debian/changelog packages/debian/debian/control diff --git a/CMakeLists.txt b/CMakeLists.txt index 1fd0a03..68d0097 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,6 @@ project(n2n) cmake_minimum_required(VERSION 2.6) +include(CheckFunctionExists) # N2n information set(N2N_VERSION 2.5.1) @@ -16,6 +17,8 @@ if(NOT DEFINED N2N_OPTION_AES) set(N2N_OPTION_AES ON) endif(NOT DEFINED N2N_OPTION_AES) +add_definitions(-DCMAKE_BUILD) +add_definitions(-DGIT_RELEASE="" -DPACKAGE_VERSION="${N2N_VERSION}" -DPACKAGE_OSNAME="${CMAKE_SYSTEM}") add_definitions(-DN2N_VERSION="${N2N_VERSION}" -DN2N_OSNAME="${N2N_OSNAME}") if(N2N_OPTION_AES) @@ -79,15 +82,33 @@ target_link_libraries(edge n2n) add_executable(supernode sn.c) target_link_libraries(supernode n2n) -add_executable(benchmark benchmark.c) -target_link_libraries(benchmark n2n) - install(TARGETS edge supernode RUNTIME DESTINATION sbin LIBRARY DESTINATION lib ARCHIVE DESTINATION lib ) +# Tools +include_directories(.) + +add_executable(n2n-benchmark tools/benchmark.c) +target_link_libraries(n2n-benchmark n2n) + +find_library(PCAP_LIB pcap) +if(PCAP_LIB) + add_executable(n2n-decode tools/n2n_decode.c) + target_link_libraries(n2n-decode n2n pcap) + install(TARGETS n2n-decode RUNTIME DESTINATION bin) + + set(CMAKE_REQUIRED_LIBRARIES ${PCAP_LIB}) + check_function_exists(pcap_set_immediate_mode HAVE_PCAP_IMMEDIATE_MODE) + IF(HAVE_PCAP_IMMEDIATE_MODE) + ADD_DEFINITIONS("-DHAVE_PCAP_IMMEDIATE_MODE") + ENDIF() +endif() + +install(TARGETS n2n-benchmark RUNTIME DESTINATION bin) + # Documentation if(DEFINED UNIX) add_dependencies(n2n doc) diff --git a/Makefile.in b/Makefile.in index 030cc33..ef523f3 100644 --- a/Makefile.in +++ b/Makefile.in @@ -8,7 +8,7 @@ GIT_COMMITS=@GIT_COMMITS@ CC?=gcc DEBUG?=-g3 #OPTIMIZATION?=-O2 -WARN?=-Wall -Wshadow -Wpointer-arith -Wmissing-declarations -Wnested-externs +WARN?=-Wall #Ultrasparc64 users experiencing SIGBUS should try the following gcc options #(thanks to Robert Gibbon) @@ -66,22 +66,23 @@ APPS+=example_edge_embed DOCS=edge.8.gz supernode.1.gz n2n.7.gz -all: $(APPS) $(DOCS) benchmark +.PHONY: steps build push all clean install tools +all: $(APPS) $(DOCS) tools + +tools: $(N2N_LIB) + $(MAKE) -C $@ edge: edge.c $(N2N_LIB) n2n_wire.h n2n.h Makefile - $(CC) $(CFLAGS) edge.c $(N2N_LIB) $(LIBS_EDGE) -o edge + $(CC) $(CFLAGS) $< $(N2N_LIB) $(LIBS_EDGE) -o $@ supernode: sn.c $(N2N_LIB) n2n.h Makefile - $(CC) $(CFLAGS) sn.c $(N2N_LIB) $(LIBS_SN) -o supernode - -benchmark: benchmark.c $(N2N_LIB) n2n_wire.h n2n.h Makefile - $(CC) $(CFLAGS) benchmark.c $(N2N_LIB) $(LIBS_EDGE) -o benchmark + $(CC) $(CFLAGS) $< $(N2N_LIB) $(LIBS_SN) -o $@ example_edge_embed: example_edge_embed.c $(N2N_LIB) n2n.h - $(CC) $(CFLAGS) example_edge_embed.c $(N2N_LIB) $(LIBS_EDGE) -o example_edge_embed + $(CC) $(CFLAGS) $< $(N2N_LIB) $(LIBS_EDGE) -o $@ .c.o: n2n.h n2n_transforms.h n2n_wire.h twofish.h Makefile - $(CC) $(CFLAGS) -c $< + $(CC) $(CFLAGS) -c $< -o $@ %.gz : % gzip -c $< > $@ @@ -91,7 +92,8 @@ $(N2N_LIB): $(N2N_OBJS) # $(RANLIB) $@ clean: - rm -rf $(N2N_OBJS) $(N2N_LIB) $(APPS) $(DOCS) test *.dSYM *~ + rm -rf $(N2N_OBJS) $(N2N_LIB) $(APPS) $(DOCS) test n2n-decode *.dSYM *~ + $(MAKE) -C tools clean install: edge supernode edge.8.gz supernode.1.gz n2n.7.gz echo "MANDIR=$(MANDIR)" @@ -101,6 +103,7 @@ install: edge supernode edge.8.gz supernode.1.gz n2n.7.gz $(INSTALL_DOC) edge.8.gz $(MAN8DIR)/ $(INSTALL_DOC) supernode.1.gz $(MAN1DIR)/ $(INSTALL_DOC) n2n.7.gz $(MAN7DIR)/ + $(MAKE) -C tools install # Docker builder section DOCKER_IMAGE_NAME=ntop/supernode @@ -136,5 +139,4 @@ push: echo "Please pass TARGET_ARCHITECTURE, see README.md."; \ fi -.PHONY: steps build push # End Docker builder section diff --git a/README.md b/README.md index 7e15dfc..2c80601 100644 --- a/README.md +++ b/README.md @@ -133,7 +133,7 @@ two edge nodes, but it will now that edge A is talking with edge B. Recently AES encryption support has been implemented, which increases both security and performance, so it is recommended to enable it on all the edge nodes by specifying the `-A` option. -A benchmark of the encryption methods is available when compiled from source with `./benchmark`. +A benchmark of the encryption methods is available when compiled from source with `tools/n2n-benchmark`. Contribution ------------ diff --git a/android/edge_android.c b/android/edge_android.c index 68376a8..00aec91 100644 --- a/android/edge_android.c +++ b/android/edge_android.c @@ -165,6 +165,9 @@ int start_edge(const n2n_edge_cmd_t* cmd) traceLevel = traceLevel < 0 ? 0 : traceLevel; /* TRACE_ERROR */ traceLevel = traceLevel > 4 ? 4 : traceLevel; /* TRACE_DEBUG */ + /* Random seed */ + srand(time(NULL)); + if (-1 == edge_init(&eee) ) { traceEvent( TRACE_ERROR, "Failed in edge_init" ); diff --git a/configure.seed b/configure.seed index 1faea83..0f8b596 100644 --- a/configure.seed +++ b/configure.seed @@ -24,6 +24,19 @@ else N2N_LIBS=-lcrypto fi +AC_CHECK_LIB([pcap], [pcap_open_live], pcap=true) + +if test x$pcap != x; then + AC_DEFINE([N2N_HAVE_PCAP], [], [Have PCAP library]) + ADDITIONAL_TOOLS="$ADDITIONAL_TOOLS n2n-decode" +fi + +AC_CHECK_LIB([pcap], [pcap_set_immediate_mode], pcap_immediate_mode=true) + +if test x$pcap_immediate_mode != x; then + AC_DEFINE([HAVE_PCAP_IMMEDIATE_MODE], [], [Have pcap_immediate_mode]) +fi + MACHINE=`uname -m` SYSTEM=`uname -s` @@ -60,8 +73,10 @@ AC_SUBST(GIT_REVISION) AC_SUBST(GIT_RELEASE) AC_SUBST(N2N_DEFINES) AC_SUBST(N2N_LIBS) +AC_SUBST(ADDITIONAL_TOOLS) AC_CONFIG_HEADERS(config.h) AC_CONFIG_FILES(Makefile) +AC_CONFIG_FILES(tools/Makefile) AC_OUTPUT diff --git a/doc/NEW_FEATURES.txt b/doc/NEW_FEATURES.txt index 9efc587..a4ab58f 100644 --- a/doc/NEW_FEATURES.txt +++ b/doc/NEW_FEATURES.txt @@ -3,4 +3,3 @@ Between 2.0.x and 2.1.x * Better ming Windows build support. * Added -E flag to allow multicast ethernet traffic. - diff --git a/edge.8 b/edge.8 index 0ef1c35..2e112c5 100644 --- a/edge.8 +++ b/edge.8 @@ -4,7 +4,7 @@ edge \- n2n edge node daemon .SH SYNOPSIS .B edge [\-d ] \-a \-c {\-k |\-K } -[\-s ] \-l +[\-s ] \-l [\-L ] [\-p ] [\-u ] [\-g ] [-f] [\-m ] [\-r] [\-v] .SH DESCRIPTION N2N is a peer-to-peer VPN system. Edge is the edge node daemon for n2n which @@ -111,6 +111,26 @@ are used in multicast ethernet and IPv6 neighbour discovery. If this option is not present these multicast packets are discarded as most users do not need or understand them. .TP +\-L +set the TTL for the hole punching packet. This is an advanced flag to make +sure that the registration packet is dropped immediately when it goes out of +local nat so that it will not trigger some firewall behavior on target peer. +Actually, the registration packet is only expected to make local nat UDP hole +and is not expected to reach the target peer, see +https://tools.ietf.org/html/rfc5389. To achieve this, the flag should be set as +nat level + 1. For example, if we have 2 layer nat in local, we should set -L 3. +Usually we know exactly how much nat layers in local. +If we are not sure how much nat layers in local, we can use traceroute on +Linux to check. The following example shows a local single layer nat because on +second jump it shows a public ip address. In this case it should set -L 2. + +$ /usr/sbin/traceroute -w1 8.8.8.8 +traceroute to 8.8.8.8 (8.8.8.8), 30 hops max, 60 byte packets + 1 192.168.3.1 (192.168.3.1) 0.464 ms 0.587 ms 0.719 ms + 2 112.65.17.217 (112.65.17.217) 5.269 ms 7.031 ms 8.666 ms + +But this method does not always work due to various local network device policy. +.TP \-v more verbose logging (may be specified several times for more verbosity). .SH ENVIRONMENT diff --git a/edge.c b/edge.c index d65e4bf..0567f8f 100644 --- a/edge.c +++ b/edge.c @@ -140,10 +140,10 @@ static void help() { "-l \n" " " "[-p ] [-M ] " -#ifdef __linux__ +#ifndef __APPLE__ "[-D] " #endif - "[-r] [-E] [-v] [-i ] [-t ] [-A] [-h]\n\n"); + "[-r] [-E] [-v] [-i ] [-L ] [-t ] [-A] [-h]\n\n"); #if defined(N2N_CAN_NAME_IFACE) printf("-d | tun device name\n"); @@ -155,6 +155,7 @@ static void help() { printf("-s | Edge interface netmask in dotted decimal notation (255.255.255.0).\n"); printf("-l | Supernode IP:port\n"); printf("-i | Registration interval, for NAT hole punching (default 20 seconds)\n"); + printf("-L | TTL for registration packet when UDP NAT hole punching through supernode (default 0 for not set )\n"); printf("-p | Fixed local UDP port.\n"); #ifndef WIN32 printf("-u | User ID (numeric) to use when privileges are dropped.\n"); @@ -166,8 +167,8 @@ static void help() { printf("-m | Fix MAC address for the TAP interface (otherwise it may be random)\n" " | eg. -m 01:02:03:04:05:06\n"); printf("-M | Specify n2n MTU of edge interface (default %d).\n", DEFAULT_MTU); -#ifdef __linux__ - printf("-D | Enable PMTU discovery. PMTU discovery can reduce fragmentation but" +#ifndef __APPLE__ + printf("-D | Enable PMTU discovery. PMTU discovery can reduce fragmentation but\n" " | causes connections stall when not properly supported.\n"); #endif printf("-r | Enable packet forwarding through n2n community.\n"); @@ -257,7 +258,7 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e break; } -#ifdef __linux__ +#ifndef __APPLE__ case 'D' : /* enable PMTU discovery */ { conf->disable_pmtu_discovery = 0; @@ -303,6 +304,10 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e conf->register_interval = atoi(optargument); break; + case 'L': /* supernode registration interval */ + conf->register_ttl = atoi(optarg); + break; + #if defined(N2N_CAN_NAME_IFACE) case 'd': /* TUNTAP name */ { @@ -393,7 +398,7 @@ static int loadFromCLI(int argc, char *argv[], n2n_edge_conf_t *conf, n2n_priv_c u_char c; while((c = getopt_long(argc, argv, - "K:k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:S" + "k:a:bc:Eu:g:m:M:s:d:l:p:fvhrt:i:SDL:" #ifdef N2N_HAVE_AES "A" #endif @@ -672,6 +677,9 @@ int main(int argc, char* argv[]) { traceEvent(TRACE_NORMAL, "Starting n2n edge %s %s", PACKAGE_VERSION, PACKAGE_BUILDDATE); + /* Random seed */ + srand(time(NULL)); + if(0 == strcmp("dhcp", ec.ip_mode)) { traceEvent(TRACE_NORMAL, "Dynamic IP address assignment enabled."); @@ -713,7 +721,7 @@ int main(int argc, char* argv[]) { #ifndef WIN32 if((ec.userid != 0) || (ec.groupid != 0)) { - traceEvent(TRACE_NORMAL, "Interface up. Dropping privileges to uid=%d, gid=%d", + traceEvent(TRACE_NORMAL, "Dropping privileges to uid=%d, gid=%d", (signed int)ec.userid, (signed int)ec.groupid); /* Finished with the need for root privileges. Drop to unprivileged user. */ diff --git a/edge_utils.c b/edge_utils.c index 585b4a5..acae39e 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -144,6 +144,49 @@ static const char* transop_str(enum n2n_transform tr) { /* ************************************** */ +/** Destination 01:00:5E:00:00:00 - 01:00:5E:7F:FF:FF is multicast ethernet. + */ +static int is_ethMulticast(const void * buf, size_t bufsize) { + int retval = 0; + + /* Match 01:00:5E:00:00:00 - 01:00:5E:7F:FF:FF */ + if(bufsize >= sizeof(ether_hdr_t)) { + /* copy to aligned memory */ + ether_hdr_t eh; + memcpy(&eh, buf, sizeof(ether_hdr_t)); + + if((0x01 == eh.dhost[0]) && + (0x00 == eh.dhost[1]) && + (0x5E == eh.dhost[2]) && + (0 == (0x80 & eh.dhost[3]))) + retval = 1; /* This is an ethernet multicast packet [RFC1112]. */ + } + + return retval; +} + +/* ************************************** */ + +/** Destination MAC 33:33:0:00:00:00 - 33:33:FF:FF:FF:FF is reserved for IPv6 + * neighbour discovery. + */ +static int is_ip6_discovery(const void * buf, size_t bufsize) { + int retval = 0; + + if(bufsize >= sizeof(ether_hdr_t)) { + /* copy to aligned memory */ + ether_hdr_t eh; + + memcpy(&eh, buf, sizeof(ether_hdr_t)); + + if((0x33 == eh.dhost[0]) && (0x33 == eh.dhost[1])) + retval = 1; /* This is an IPv6 multicast packet [RFC2464]. */ + } + return retval; +} + +/* ************************************** */ + /** Initialise an edge to defaults. * * This also initialises the NULL transform operation opstruct. @@ -171,10 +214,6 @@ n2n_edge_t* edge_init(const tuntap_dev *dev, const n2n_edge_conf_t *conf, int *r memcpy(&eee->device, dev, sizeof(*dev)); eee->start_time = time(NULL); - /* REVISIT: BbMaj7 : Should choose something with less predictability - * particularly for embedded targets with no real-time clock. */ - srand(eee->start_time); - eee->known_peers = NULL; eee->pending_peers = NULL; eee->sup_attempts = N2N_EDGE_SUP_ATTEMPTS; @@ -388,9 +427,43 @@ static void register_with_new_peer(n2n_edge_t * eee, HASH_COUNT(eee->pending_peers)); /* trace Sending REGISTER */ - send_register(eee, &(scan->sock), mac); if(from_supernode) { + /* UDP NAT hole punching through supernode. Send to peer first(punch local UDP hole) + * and then ask supernode to forward. Supernode then ask peer to ack. Some nat device + * drop and block ports with incoming UDP packet if out-come traffic does not exist. + * So we can alternatively set TTL so that the packet sent to peer never really reaches + * The register_ttl is basically nat level + 1. Set it to 1 means host like DMZ. + */ + if (eee->conf.register_ttl == 1) { + /* We are DMZ host or port is directly accessible. Just let peer to send back the ack */ +#ifndef WIN32 + } else if(eee->conf.register_ttl > 1) { + /* Setting register_ttl usually implies that the edge knows the internal net topology + * clearly, we can apply aggressive port prediction to support incoming Symmetric NAT + */ + int curTTL = 0; + socklen_t lenTTL = sizeof(int); + n2n_sock_t sock = scan->sock; + int alter = 16; /* TODO: set by command line or more reliable prediction method */ + + getsockopt(eee->udp_sock, IPPROTO_IP, IP_TTL, (void *)(char *)&curTTL, &lenTTL); + setsockopt(eee->udp_sock, IPPROTO_IP, IP_TTL, + (void *)(char *)&eee->conf.register_ttl, + sizeof(eee->conf.register_ttl)); + for (; alter > 0; alter--, sock.port++) + { + send_register(eee, &sock, mac); + } + setsockopt(eee->udp_sock, IPPROTO_IP, IP_TTL, (void *)(char *)&curTTL, sizeof(curTTL)); +#endif + } else { /* eee->conf.register_ttl <= 0 */ + /* Normal STUN */ + send_register(eee, &(scan->sock), mac); + } send_register(eee, &(eee->supernode), mac); + } else { + /* P2P register, send directly */ + send_register(eee, &(scan->sock), mac); } register_with_local_peers(eee); @@ -873,14 +946,20 @@ static int handle_PACKET(n2n_edge_t * eee, rx_transop_id = (n2n_transform_t)pkt->transform; if(rx_transop_id == eee->conf.transop_id) { + uint8_t is_multicast; eth_payload = decodebuf; eh = (ether_hdr_t*)eth_payload; eth_size = eee->transop.rev(&eee->transop, eth_payload, N2N_PKT_BUF_SIZE, payload, psize, pkt->srcMac); ++(eee->transop.rx_cnt); /* stats */ + is_multicast = (is_ip6_discovery(eth_payload, eth_size) || is_ethMulticast(eth_payload, eth_size)); - if(!(eee->conf.allow_routing)) { + if(eee->conf.drop_multicast && is_multicast) { + traceEvent(TRACE_INFO, "Dropping RX multicast"); + return(-1); + } else if((!eee->conf.allow_routing) && (!is_multicast)) { + /* Check if it is a routed packet */ if((ntohs(eh->type) == 0x0800) && (eth_size >= ETH_FRAMESIZE + IP4_MIN_SIZE)) { uint32_t *dst = (uint32_t*)ð_payload[ETH_FRAMESIZE + IP4_DSTOFFSET]; u_int8_t *dst_mac = (u_int8_t*)eth_payload; @@ -1058,49 +1137,6 @@ static void readFromMgmtSocket(n2n_edge_t * eee, int * keep_running) { /* ************************************** */ -/** Destination MAC 33:33:0:00:00:00 - 33:33:FF:FF:FF:FF is reserved for IPv6 - * neighbour discovery. - */ -static int is_ip6_discovery(const void * buf, size_t bufsize) { - int retval = 0; - - if(bufsize >= sizeof(ether_hdr_t)) { - /* copy to aligned memory */ - ether_hdr_t eh; - - memcpy(&eh, buf, sizeof(ether_hdr_t)); - - if((0x33 == eh.dhost[0]) && (0x33 == eh.dhost[1])) - retval = 1; /* This is an IPv6 multicast packet [RFC2464]. */ - } - return retval; -} - -/* ************************************** */ - -/** Destination 01:00:5E:00:00:00 - 01:00:5E:7F:FF:FF is multicast ethernet. - */ -static int is_ethMulticast(const void * buf, size_t bufsize) { - int retval = 0; - - /* Match 01:00:5E:00:00:00 - 01:00:5E:7F:FF:FF */ - if(bufsize >= sizeof(ether_hdr_t)) { - /* copy to aligned memory */ - ether_hdr_t eh; - memcpy(&eh, buf, sizeof(ether_hdr_t)); - - if((0x01 == eh.dhost[0]) && - (0x00 == eh.dhost[1]) && - (0x5E == eh.dhost[2]) && - (0 == (0x80 & eh.dhost[3]))) - retval = 1; /* This is an ethernet multicast packet [RFC1112]. */ - } - - return retval; -} - -/* ************************************** */ - static int check_query_peer_info(n2n_edge_t *eee, time_t now, n2n_mac_t mac) { struct peer_info *scan; @@ -1340,7 +1376,7 @@ static void readFromTAPSocket(n2n_edge_t * eee) { ) ) { - traceEvent(TRACE_DEBUG, "Dropping multicast"); + traceEvent(TRACE_INFO, "Dropping TX multicast"); } else { @@ -1465,6 +1501,16 @@ static void readFromIPSocket(n2n_edge_t * eee, int in_sock) { if(is_valid_peer_sock(&pkt.sock)) orig_sender = &(pkt.sock); + if(!from_supernode) { + /* This is a P2P packet from the peer. We purge a pending + * registration towards the possibly nat-ted peer address as we now have + * a valid channel. We still use check_peer_registration_needed in + * handle_PACKET to double check this. + */ + traceEvent(TRACE_DEBUG, "Got P2P packet"); + find_and_remove_peer(&eee->pending_peers, pkt.srcMac); + } + traceEvent(TRACE_INFO, "Rx PACKET from %s (sender=%s) [%u B]", sock_to_cstr(sockbuf1, &sender), sock_to_cstr(sockbuf2, orig_sender), @@ -1799,6 +1845,8 @@ void edge_term(n2n_edge_t * eee) { /* ************************************** */ static int edge_init_sockets(n2n_edge_t *eee, int udp_local_port, int mgmt_port, uint8_t tos) { + int sockopt; + if(udp_local_port > 0) traceEvent(TRACE_NORMAL, "Binding to local port %d", udp_local_port); @@ -1808,10 +1856,9 @@ static int edge_init_sockets(n2n_edge_t *eee, int udp_local_port, int mgmt_port, return(-1); } -#ifdef __linux__ if(tos) { /* https://www.tucny.com/Home/dscp-tos */ - int sockopt = tos; + sockopt = tos; if(setsockopt(eee->udp_sock, IPPROTO_IP, IP_TOS, &sockopt, sizeof(sockopt)) == 0) traceEvent(TRACE_NORMAL, "TOS set to 0x%x", tos); @@ -1819,14 +1866,14 @@ static int edge_init_sockets(n2n_edge_t *eee, int udp_local_port, int mgmt_port, traceEvent(TRACE_ERROR, "Could not set TOS 0x%x[%d]: %s", tos, errno, strerror(errno)); } - if(eee->conf.disable_pmtu_discovery) { - int sockopt = 0; +#ifdef IP_PMTUDISC_DO + sockopt = (eee->conf.disable_pmtu_discovery) ? IP_PMTUDISC_DONT : IP_PMTUDISC_DO; - if(setsockopt(eee->udp_sock, IPPROTO_IP, IP_MTU_DISCOVER, &sockopt, sizeof(sockopt)) < 0) - traceEvent(TRACE_WARNING, "Could not disable PMTU discovery[%d]: %s", errno, strerror(errno)); - else - traceEvent(TRACE_DEBUG, "PMTU discovery disabled"); - } + if(setsockopt(eee->udp_sock, IPPROTO_IP, IP_MTU_DISCOVER, &sockopt, sizeof(sockopt)) < 0) + traceEvent(TRACE_WARNING, "Could not %s PMTU discovery[%d]: %s", + (eee->conf.disable_pmtu_discovery) ? "disable" : "enable", errno, strerror(errno)); + else + traceEvent(TRACE_DEBUG, "PMTU discovery %s", (eee->conf.disable_pmtu_discovery) ? "disabled" : "enabled"); #endif eee->udp_mgmt_sock = open_socket(mgmt_port, 0 /* bind LOOPBACK */); diff --git a/example_edge_embed.c b/example_edge_embed.c index 954d5b1..477c209 100644 --- a/example_edge_embed.c +++ b/example_edge_embed.c @@ -35,6 +35,9 @@ int main(int argc, char* argv[]) { /* Increase tracelevel to see what's happening */ setTraceLevel(10); + /* Random seed */ + srand(time(NULL)); + /* NOTE diff --git a/n2n.c b/n2n.c index 3559ea4..b3479ce 100644 --- a/n2n.c +++ b/n2n.c @@ -64,6 +64,7 @@ SOCKET open_socket(int local_port, int bind_any) { static int traceLevel = 2 /* NORMAL */; static int useSyslog = 0, syslog_opened = 0; +static FILE *traceFile = NULL; int getTraceLevel() { return(traceLevel); @@ -77,10 +78,17 @@ void setUseSyslog(int use_syslog) { useSyslog= use_syslog; } +void setTraceFile(FILE *f) { + traceFile = f; +} + #define N2N_TRACE_DATESIZE 32 void traceEvent(int eventTraceLevel, char* file, int line, char * format, ...) { va_list va_ap; + if(traceFile == NULL) + traceFile = stdout; + if(eventTraceLevel <= traceLevel) { char buf[1024]; char out_buf[1280]; @@ -145,16 +153,16 @@ void traceEvent(int eventTraceLevel, char* file, int line, char * format, ...) { } __android_log_write(eventTraceLevel, "n2n", out_buf); #else - printf("%s\n", out_buf); - fflush(stdout); + fprintf(traceFile, "%s\n", out_buf); + fflush(traceFile); #endif /* #ifdef __ANDROID_NDK__ */ } #else /* this is the WIN32 code */ for(i=strlen(file)-1; i>0; i--) if(file[i] == '\\') { i++; break; }; snprintf(out_buf, sizeof(out_buf), "%s [%s:%d] %s%s", theDate, &file[i], line, extra_msg, buf); - printf("%s\n", out_buf); - fflush(stdout); + fprintf(traceFile, "%s\n", out_buf); + fflush(traceFile); #endif } diff --git a/n2n.h b/n2n.h index 388e741..e5018db 100644 --- a/n2n.h +++ b/n2n.h @@ -42,8 +42,10 @@ #undef N2N_HAVE_DAEMON #undef N2N_HAVE_SETUID #else +#ifndef CMAKE_BUILD #include "config.h" #endif +#endif #define PACKAGE_BUILDDATE (__DATE__ " " __TIME__) @@ -215,6 +217,7 @@ typedef struct n2n_edge_conf { uint8_t tos; /** TOS for sent packets */ char *encrypt_key; int register_interval; /**< Interval for supernode registration, also used for UDP NAT hole punching. */ + int register_ttl; /**< TTL for registration packet when UDP NAT hole punching through supernode. */ int local_port; int mgmt_port; } n2n_edge_conf_t; @@ -261,6 +264,7 @@ int n2n_transop_aes_cbc_init(const n2n_edge_conf_t *conf, n2n_trans_op_t *ttt); /* Log */ void setTraceLevel(int level); void setUseSyslog(int use_syslog); +void setTraceFile(FILE *f); int getTraceLevel(); void traceEvent(int eventTraceLevel, char* file, int line, char * format, ...); diff --git a/n2n_wire.h b/n2n_wire.h index daa9359..15aff9e 100644 --- a/n2n_wire.h +++ b/n2n_wire.h @@ -106,7 +106,9 @@ typedef struct n2n_auth typedef struct n2n_common { + /* NOTE: wire representation is different! */ /* int version; */ + uint8_t ttl; uint8_t pc; uint16_t flags; diff --git a/packages/rpm/Makefile.in b/packages/rpm/Makefile.in index 716c05b..d7d677a 100644 --- a/packages/rpm/Makefile.in +++ b/packages/rpm/Makefile.in @@ -10,7 +10,7 @@ all: clean pkg pkg: rpmbuild -bb ./n2n.spec - @./rpm-sign.exp $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(RPM_PKG) + @@RPM_SIGN_CMD@ $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(RPM_PKG) @echo "" @echo "Package contents:" @rpm -qpl $(HOME)/rpmbuild/RPMS/$(PLATFORM)/$(RPM_PKG) diff --git a/packages/rpm/configure b/packages/rpm/configure index 4fbdb80..cd797dd 100755 --- a/packages/rpm/configure +++ b/packages/rpm/configure @@ -583,10 +583,11 @@ PACKAGE_URL='' ac_subst_vars='LTLIBOBJS LIBOBJS -N2N_VERSION_SHORT -GIT_COMMITS +RPM_SIGN_CMD DATE EXTN +GIT_COMMITS +N2N_VERSION_SHORT MACHINE APP target_alias @@ -1668,6 +1669,10 @@ ac_compiler_gnu=$ac_cv_c_compiler_gnu +# NOTE: this file is not actually used. You need to edit configure as well! +N2N_VERSION_SHORT=`grep N2N_VERSION_SHORT ../../Makefile | head -1| cut -d "=" -f 2` +GIT_COMMITS=`grep GIT_COMMITS ../../Makefile | head -1| cut -d "=" -f 2` + MACHINE=`uname -m` SHORT_MACHINE=`uname -m | cut -b1-3` @@ -1685,7 +1690,7 @@ else if test $SHORT_MACHINE = "mip"; then EXTN="mips" EXTRA_DEPS="" - else + else EXTN="i386" fi fi @@ -1693,10 +1698,17 @@ else fi APP=n2n -N2N_VERSION_SHORT=`grep N2N_VERSION_SHORT ../../Makefile | head -1| cut -d "=" -f 2` -GIT_COMMITS=`grep GIT_COMMITS ../../Makefile | head -1| cut -d "=" -f 2` DATE=`date -R` +CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 3|cut -d '.' -f 1` +if test $CENTOS_RELEASE = "release"; then + CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 4|cut -d '.' -f 1` +fi + +RPM_SIGN_CMD="rpm --addsign" +if test "$CENTOS_RELEASE" -ne 8; then + RPM_SIGN_CMD="./rpm-sign.exp" +fi diff --git a/packages/rpm/configure.in b/packages/rpm/configure.in index 6b99c15..cce9205 100644 --- a/packages/rpm/configure.in +++ b/packages/rpm/configure.in @@ -31,12 +31,23 @@ fi APP=n2n DATE=`date -R` +CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 3|cut -d '.' -f 1` +if test $CENTOS_RELEASE = "release"; then + CENTOS_RELEASE=`cat /etc/centos-release | cut -d ' ' -f 4|cut -d '.' -f 1` +fi + +RPM_SIGN_CMD="rpm --addsign" +if test "$CENTOS_RELEASE" -ne 8; then + RPM_SIGN_CMD="./rpm-sign.exp" +fi + AC_SUBST(APP) AC_SUBST(MACHINE) AC_SUBST(N2N_VERSION_SHORT) AC_SUBST(GIT_COMMITS) AC_SUBST(EXTN) AC_SUBST(DATE) +AC_SUBST(RPM_SIGN_CMD) AC_CONFIG_FILES(n2n.spec) AC_CONFIG_FILES(../etc/systemd/system/edge.service) diff --git a/tools/Makefile.in b/tools/Makefile.in new file mode 100644 index 0000000..bcb3e02 --- /dev/null +++ b/tools/Makefile.in @@ -0,0 +1,45 @@ +CC?=gcc +DEBUG?=-g3 +#OPTIMIZATION?=-O2 +WARN?=-Wall + +INSTALL=install +INSTALL_PROG=$(INSTALL) -m755 +MKDIR=mkdir -p + +PREFIX?=$(DESTDIR)/usr +ifeq ($(OS),Darwin) +SBINDIR=$(PREFIX)/local/sbin +else +SBINDIR=$(PREFIX)/sbin +endif + +LIBS_EDGE_OPT=@N2N_LIBS@ +LIBS_EDGE+=$(LIBS_EDGE_OPT) +HEADERS=../n2n_wire.h ../n2n.h ../twofish.h ../n2n_transforms.h +CFLAGS+=-I.. +LDFLAGS+=-L.. +CFLAGS+=$(DEBUG) $(OPTIMIZATION) $(WARN) + +N2N_LIB=../libn2n.a + +TOOLS=n2n-benchmark +TOOLS+=@ADDITIONAL_TOOLS@ + +.PHONY: all clean install +all: $(TOOLS) + +n2n-benchmark: benchmark.c $(N2N_LIB) $(HEADERS) + $(CC) $(CFLAGS) $< $(N2N_LIB) $(LIBS_EDGE) -o $@ + +n2n-decode: n2n_decode.c $(N2N_LIB) $(HEADERS) + $(CC) $(CFLAGS) $< $(N2N_LIB) $(LIBS_EDGE) -lpcap -o $@ + +.c.o: $(HEADERS) ../Makefile Makefile + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -rf $(TOOLS) $(N2N_LIB) *.o *.dSYM *~ + +install: $(TOOLS) + $(INSTALL_PROG) $(TOOLS) $(SBINDIR)/ diff --git a/benchmark.c b/tools/benchmark.c similarity index 100% rename from benchmark.c rename to tools/benchmark.c diff --git a/tools/n2n_decode.c b/tools/n2n_decode.c new file mode 100644 index 0000000..6caf85c --- /dev/null +++ b/tools/n2n_decode.c @@ -0,0 +1,372 @@ +/** + * (C) 2019 - ntop.org and contributors + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not see see + * + */ + +#include +#include "n2n.h" + +#define SNAPLEN 1500 +#define TIMEOUT 200 + +/* *************************************************** */ + +static int aes_mode = 0; +static int running = 1; +static char *ifname = NULL; +static n2n_edge_conf_t conf; +static n2n_trans_op_t transop; +static pcap_t *handle; +static pcap_dumper_t *dumper; + +/* *************************************************** */ + +static void help() { + fprintf(stderr, "n2n-decode -i ifname -k key -c community [-B bpf] [-w fname] [-v]" +#ifdef N2N_HAVE_AES + " [-A]" +#endif + "\n"); + fprintf(stderr, "-i | Specify the capture interface name.\n"); + fprintf(stderr, "-c | Specify the community.\n"); + fprintf(stderr, "-k | Specify the encryption key.\n"); +#ifdef N2N_HAVE_AES + fprintf(stderr, "-A | Use AES CBC decryption (default=use twofish).\n"); +#endif + fprintf(stderr, "-B | Use set a BPF filter for the capture.\n"); + fprintf(stderr, "-w | Write decoded PCAP to file.\n"); + fprintf(stderr, "-v | Increase verbosity level.\n"); + + exit(0); +} + +/* *************************************************** */ + +#ifdef WIN32 +BOOL WINAPI term_handler(DWORD sig) +#else +static void term_handler(int sig) +#endif +{ + static int called = 0; + + if(called) { + traceEvent(TRACE_NORMAL, "Ok I am leaving now"); + _exit(0); + } else { + traceEvent(TRACE_NORMAL, "Shutting down..."); + called = 1; + } + + running = 0; +#ifdef WIN32 + return(TRUE); +#endif +} + +/* *************************************************** */ + +static void write_packet(const u_char *packet, struct pcap_pkthdr *hdr) { + pcap_dump((unsigned char*)dumper, hdr, packet); + pcap_dump_flush(dumper); +} + +/* *************************************************** */ + +static int decode_encrypted_packet(const u_char *packet, struct pcap_pkthdr *header, + n2n_PACKET_t *pkt, int encrypted_offset) { + uint8_t decoded_packet[encrypted_offset + N2N_PKT_BUF_SIZE]; + int decoded_eth_size; + int transop_shift; + + switch(pkt->transform) { + case N2N_TRANSFORM_ID_NULL: + /* Not encrypted, dump it */ + write_packet(packet, header); + break; + case N2N_TRANSFORM_ID_TWOFISH: + if(aes_mode) { + traceEvent(TRACE_INFO, "Skipping twofish encrypted packet"); + return(-1); + } + break; + case N2N_TRANSFORM_ID_AESCBC: + if(!aes_mode) { + traceEvent(TRACE_INFO, "Skipping AES encrypted packet"); + return(-1); + } + break; + default: + traceEvent(TRACE_INFO, "Skipping unknown transform packet: %d", pkt->transform); + return(-2); + } + + decoded_eth_size = transop.rev(&transop, decoded_packet+encrypted_offset, N2N_PKT_BUF_SIZE, packet + encrypted_offset, + header->caplen - encrypted_offset, pkt->srcMac); + + transop_shift = (header->caplen - encrypted_offset) - decoded_eth_size; + + if(transop_shift >= 0) { + int transform_id_offset = encrypted_offset - 2; + + /* Copy the initial part of the packet */ + memcpy(decoded_packet, packet, encrypted_offset); + + /* Change the packet transform to NULL as there is now plaintext data */ + *((u_int16_t*)(decoded_packet + transform_id_offset)) = htons(N2N_TRANSFORM_ID_NULL); + + // TODO fix IP and UDP chechsums + write_packet(decoded_packet, header); + return(0); + } + + traceEvent(TRACE_INFO, "Something was wrong in the decoding"); + return(-3); +} + +/* *************************************************** */ + +#define ETH_SIZE 14 +#define UDP_SIZE 8 +#define MIN_IP_SIZE 20 +#define MIN_LEN (ETH_SIZE + UDP_SIZE + MIN_IP_SIZE + sizeof(n2n_common_t)) + +static int run_packet_loop() { + struct pcap_pkthdr header; + const u_char *packet; + + traceEvent(TRACE_NORMAL, "Capturing packets on %s...", ifname); + + while(running) { + n2n_common_t common; + n2n_PACKET_t pkt; + uint ipsize, common_offset; + size_t idx, rem; + + memset(&common, 0, sizeof(common)); + memset(&pkt, 0, sizeof(pkt)); + + packet = pcap_next(handle, &header); + + if(!packet) + continue; + + if(header.caplen < MIN_LEN) { + traceEvent(TRACE_INFO, "Skipping packet too small: size=%d", header.caplen); + continue; + } + + if(ntohs(*(uint16_t*)(packet + 12)) != 0x0800) { + traceEvent(TRACE_INFO, "Skipping non IPv4 packet"); + continue; + } + + if(packet[ETH_SIZE + 9] != IPPROTO_UDP) { + traceEvent(TRACE_INFO, "Skipping non UDP packet"); + continue; + } + + ipsize = (packet[ETH_SIZE] & 0x0F) * 4; + common_offset = ETH_SIZE + ipsize + UDP_SIZE; + + idx = common_offset; + rem = header.caplen - idx; + + if(decode_common(&common, packet, &rem, &idx) == -1) { + traceEvent(TRACE_INFO, "Skipping packet, decode common failed"); + continue; + } + + if(strncmp((char*)conf.community_name, (char*)common.community, N2N_COMMUNITY_SIZE) != 0) { + traceEvent(TRACE_INFO, "Skipping packet with non-matching community"); + continue; + } + + switch(common.pc) { + case n2n_ping: + case n2n_register: + case n2n_deregister: + case n2n_register_ack: + case n2n_register_super: + case n2n_register_super_ack: + case n2n_register_super_nak: + case n2n_federation: + case n2n_peer_info: + case n2n_query_peer: + write_packet(packet, &header); + break; + case n2n_packet: + decode_PACKET(&pkt, &common, packet, &rem, &idx); + decode_encrypted_packet(packet, &header, &pkt, idx); + break; + default: + traceEvent(TRACE_INFO, "Skipping packet with unknown type: %d", common.pc); + continue; + } + } + + return(0); +} + +/* *************************************************** */ + +int main(int argc, char* argv[]) { + u_char c; + struct bpf_program fcode; + char *bpf_filter = NULL, *out_fname = NULL; + char errbuf[PCAP_ERRBUF_SIZE]; + int rv = 0; + FILE *outf = stdout; + + /* Trace to stderr to leave stdout for the PCAP dump if "-w -" is used */ + setTraceFile(stderr); + + /* Init configuration */ + edge_init_conf_defaults(&conf); + + while((c = getopt(argc, argv, + "k:i:B:w:c:v" +#ifdef N2N_HAVE_AES + "A" +#endif + )) != '?') { + if(c == 255) break; + + switch(c) { + case 'c': + strncpy((char*)conf.community_name, optarg, sizeof(conf.community_name)-1); + break; + case 'i': + ifname = strdup(optarg); + break; + case 'k': + conf.encrypt_key = strdup(optarg); + break; + case 'B': + bpf_filter = strdup(optarg); + break; +#ifdef N2N_HAVE_AES + case 'A': + aes_mode = 1; + break; +#endif + case 'w': + if(strcmp(optarg, "-") != 0) + out_fname = strdup(optarg); + break; + case 'v': /* verbose */ + setTraceLevel(getTraceLevel() + 1); + break; + default: + help(); + } + } + + if((ifname == NULL) || (conf.encrypt_key == NULL) || (conf.community_name[0] == '\0')) + help(); + +#ifdef N2N_HAVE_AES + if(aes_mode) + n2n_transop_aes_cbc_init(&conf, &transop); + else +#endif + n2n_transop_twofish_init(&conf, &transop); + + if((handle = pcap_create(ifname, errbuf)) == NULL) { + traceEvent(TRACE_ERROR, "Cannot open device %s: %s", ifname, errbuf); + return(1); + } + + if((pcap_set_timeout(handle, TIMEOUT) != 0) || + (pcap_set_snaplen(handle, SNAPLEN) != 0)) { + traceEvent(TRACE_ERROR, "Error while setting timeout/snaplen"); + return(1); + } + +#ifdef HAVE_PCAP_IMMEDIATE_MODE + /* The timeout is not honored unless immediate mode is set. + * See https://github.com/mfontanini/libtins/issues/180 */ + if(pcap_set_immediate_mode(handle, 1) != 0) { + traceEvent(TRACE_ERROR, "Could not set PCAP immediate mode"); + return(1); + } +#endif + + if(pcap_activate(handle) != 0) { + traceEvent(TRACE_ERROR, "pcap_activate failed: %s", pcap_geterr(handle)); + } + + if(pcap_datalink(handle) != DLT_EN10MB) { + traceEvent(TRACE_ERROR, "Device %s doesn't provide Ethernet headers - not supported", ifname); + return(2); + } + + if(bpf_filter) { + bpf_u_int32 net, mask; + + if(pcap_lookupnet(ifname, &net, &mask, errbuf) == -1) { + traceEvent(TRACE_WARNING, "Couldn't get netmask for device %s: %s", ifname, errbuf); + net = 0; + mask = 0; + } + + if((pcap_compile(handle, &fcode, bpf_filter, 1, net) < 0) + || (pcap_setfilter(handle, &fcode) < 0)) { + traceEvent(TRACE_ERROR, "Could not set BPF filter: %s", pcap_geterr(handle)); + return(3); + } + } + + if(out_fname) { + outf = fopen(out_fname, "wb"); + + if(outf == NULL) { + traceEvent(TRACE_ERROR, "Could not open %s for write[%d]: %s", errno, strerror(errno)); + return(4); + } + } + + dumper = pcap_dump_fopen(handle, outf); + + if(dumper == NULL) { + traceEvent(TRACE_ERROR, "Could dump file: %s", pcap_geterr(handle)); + return(5); + } + +#ifdef __linux__ + signal(SIGTERM, term_handler); + signal(SIGINT, term_handler); +#endif +#ifdef WIN32 + SetConsoleCtrlHandler(term_handler, TRUE); +#endif + + rv = run_packet_loop(); + + /* Cleanup */ + pcap_close(handle); + + if(conf.encrypt_key) free(conf.encrypt_key); + if(bpf_filter) free(bpf_filter); + if(ifname) free(ifname); + + if(out_fname) { + fclose(outf); + free(out_fname); + } + + return(rv); +} diff --git a/tuntap_linux.c b/tuntap_linux.c index c44b6db..ae10445 100644 --- a/tuntap_linux.c +++ b/tuntap_linux.c @@ -20,38 +20,76 @@ #ifdef __linux__ -/* *************************************************** */ +#include +#include +#include + +/* ********************************** */ -static void read_mac(char *ifname, n2n_mac_t mac_addr) { - int _sock, res; +static int setup_ifname(int fd, const char *ifname, const char *ipaddr, + const char *netmask, uint8_t *mac, int mtu) { struct ifreq ifr; - macstr_t mac_addr_buf; - - memset (&ifr,0,sizeof(struct ifreq)); - - /* Dummy socket, just to make ioctls with */ - _sock=socket(PF_INET, SOCK_DGRAM, 0); - strcpy(ifr.ifr_name, ifname); - res = ioctl(_sock,SIOCGIFHWADDR,&ifr); - - if(res < 0) { - perror ("Get hw addr"); - traceEvent(TRACE_ERROR, "Unable to read interfce %s MAC", ifname); - } else - memcpy(mac_addr, ifr.ifr_ifru.ifru_hwaddr.sa_data, 6); - - traceEvent(TRACE_NORMAL, "Interface %s has MAC %s", - ifname, - macaddr_str(mac_addr_buf, mac_addr )); - close(_sock); + + memset(&ifr, 0, sizeof(ifr)); + + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + + ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER; + memcpy(ifr.ifr_hwaddr.sa_data, mac, 6); + + if(ioctl(fd, SIOCSIFHWADDR, &ifr) == -1) { + traceEvent(TRACE_ERROR, "ioctl(SIOCSIFHWADDR) failed [%d]: %s", errno, strerror(errno)); + return(-1); + } + + ifr.ifr_addr.sa_family = AF_INET; + + /* Interface Address */ + inet_pton(AF_INET, ipaddr, &((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr); + if(ioctl(fd, SIOCSIFADDR, &ifr) == -1) { + traceEvent(TRACE_ERROR, "ioctl(SIOCSIFADDR) failed [%d]: %s", errno, strerror(errno)); + return(-2); + } + + /* Netmask */ + if(netmask && (((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr.s_addr != 0)) { + inet_pton(AF_INET, netmask, &((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr); + if(ioctl(fd, SIOCSIFNETMASK, &ifr) == -1) { + traceEvent(TRACE_ERROR, "ioctl(SIOCSIFNETMASK, %s) failed [%d]: %s", netmask, errno, strerror(errno)); + return(-3); + } + } + + /* MTU */ + ifr.ifr_mtu = mtu; + if(ioctl(fd, SIOCSIFMTU, &ifr) == -1) { + traceEvent(TRACE_ERROR, "ioctl(SIOCSIFMTU) failed [%d]: %s", errno, strerror(errno)); + return(-4); + } + + /* Set up and running */ + if(ioctl(fd, SIOCGIFFLAGS, &ifr) == -1) { + traceEvent(TRACE_ERROR, "ioctl(SIOCGIFFLAGS) failed [%d]: %s", errno, strerror(errno)); + return(-5); + } + + ifr.ifr_flags |= (IFF_UP | IFF_RUNNING); + + if(ioctl(fd, SIOCSIFFLAGS, &ifr) == -1) { + traceEvent(TRACE_ERROR, "ioctl(SIOCSIFFLAGS) failed [%d]: %s", errno, strerror(errno)); + return(-6); + } + + return(0); } /* ********************************** */ /** @brief Open and configure the TAP device for packet read/write. * - * This routine creates the interface via the tuntap driver then uses ifconfig - * to configure address/mask and MTU. + * This routine creates the interface via the tuntap driver and then + * configures it. * * @param device - [inout] a device info holder object * @param dev - user-defined name for the new iface, @@ -71,10 +109,15 @@ int tuntap_open(tuntap_dev *device, const char * device_mac, int mtu) { char *tuntap_device = "/dev/net/tun"; -#define N2N_LINUX_SYSTEMCMD_SIZE 128 - char buf[N2N_LINUX_SYSTEMCMD_SIZE]; + int ioctl_fd; struct ifreq ifr; int rc; + int nl_fd; + char nl_buf[8192]; /* >= 8192 to avoid truncation, see "man 7 netlink" */ + struct iovec iov; + struct sockaddr_nl sa; + int up_and_running = 0; + struct msghdr msg; device->fd = open(tuntap_device, O_RDWR); if(device->fd < 0) { @@ -97,32 +140,95 @@ int tuntap_open(tuntap_dev *device, /* Store the device name for later reuse */ strncpy(device->dev_name, ifr.ifr_name, MIN(IFNAMSIZ, N2N_IFNAMSIZ) ); - if ( device_mac && device_mac[0] != '\0' ) - { - /* Set the hw address before bringing the if up. */ - snprintf(buf, sizeof(buf), "/sbin/ifconfig %s hw ether %s", - ifr.ifr_name, device_mac ); - system(buf); - traceEvent(TRACE_INFO, "Setting MAC: %s", buf); - } + if(device_mac && device_mac[0]) { + /* Use the user-provided MAC */ + str2mac(device->mac_addr, device_mac); + } else { + /* Set an explicit random MAC to know the exact MAC in use. Manually + * reading the MAC address is not safe as it may change internally + * also after the TAP interface UP status has been notified. */ + int i; - if ( 0 == strncmp( "dhcp", address_mode, 5 ) ) - { - snprintf(buf, sizeof(buf), "/sbin/ifconfig %s %s mtu %d up", - ifr.ifr_name, device_ip, mtu); - } - else - { - snprintf(buf, sizeof(buf), "/sbin/ifconfig %s %s netmask %s mtu %d up", - ifr.ifr_name, device_ip, device_mask, mtu); + for(i = 0; i < 6; i++) + device->mac_addr[i] = rand(); + + device->mac_addr[0] &= ~0x01; /* Clear multicast bit */ + device->mac_addr[0] |= 0x02; /* Set locally-assigned bit */ + } + + /* Initialize Netlink socket */ + if((nl_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE)) == -1) { + traceEvent(TRACE_ERROR, "netlink socket creation failed [%d]: %s", errno, strerror(errno)); + return -1; + } + + iov.iov_base = nl_buf; + iov.iov_len = sizeof(nl_buf); + + memset(&sa, 0, sizeof(sa)); + sa.nl_family = PF_NETLINK; + sa.nl_groups = RTMGRP_LINK; + sa.nl_pid = getpid(); + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(sa); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + /* Subscribe to interface events */ + if(bind(nl_fd, (struct sockaddr*)&sa, sizeof(sa)) == -1) { + traceEvent(TRACE_ERROR, "netlink socket bind failed [%d]: %s", errno, strerror(errno)); + return -1; + } + + if((ioctl_fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) { + traceEvent(TRACE_ERROR, "socket creation failed [%d]: %s", errno, strerror(errno)); + close(nl_fd); + return -1; + } + + if(setup_ifname(ioctl_fd, device->dev_name, device_ip, device_mask, device->mac_addr, mtu) < 0) { + close(nl_fd); + close(ioctl_fd); + close(device->fd); + return -1; + } + + close(ioctl_fd); + + /* Wait for the up and running notification */ + traceEvent(TRACE_INFO, "Waiting for TAP interface to be up and running..."); + + while(!up_and_running) { + ssize_t len = recvmsg(nl_fd, &msg, 0); + struct nlmsghdr *nh; + + for(nh = (struct nlmsghdr *)nl_buf; NLMSG_OK(nh, len); nh = NLMSG_NEXT(nh, len)) { + if(nh->nlmsg_type == NLMSG_ERROR) { + traceEvent(TRACE_DEBUG, "nh->nlmsg_type == NLMSG_ERROR"); + break; + } + + if(nh->nlmsg_type == NLMSG_DONE) + break; + + if(nh->nlmsg_type == NETLINK_GENERIC) { + struct ifinfomsg *ifi = NLMSG_DATA(nh); + + /* NOTE: skipping interface name check, assuming it's our TAP */ + if((ifi->ifi_flags & IFF_UP) && (ifi->ifi_flags & IFF_RUNNING)) { + up_and_running = 1; + traceEvent(TRACE_INFO, "Interface is up and running"); + break; + } + } } + } - system(buf); - traceEvent(TRACE_INFO, "Bringing up: %s", buf); + close(nl_fd); device->ip_addr = inet_addr(device_ip); device->device_mask = inet_addr(device_mask); - read_mac(dev, device->mac_addr); return(device->fd); } @@ -149,34 +255,22 @@ void tuntap_close(struct tuntap_dev *tuntap) { /* Fill out the ip_addr value from the interface. Called to pick up dynamic * address changes. */ void tuntap_get_address(struct tuntap_dev *tuntap) { - FILE * fp=NULL; - ssize_t nread=0; - char buf[N2N_LINUX_SYSTEMCMD_SIZE]; - - /* Would rather have a more direct way to get the inet address but a netlink - * socket is overkill and probably less portable than ifconfig and sed. */ - - /* If the interface has no address (0.0.0.0) there will be no inet addr - * line and the returned string will be empty. */ - snprintf( buf, sizeof(buf), - "/sbin/ifconfig %s | /bin/sed -e '/inet addr:/!d' -e 's/^.*inet addr://' -e 's/ .*$//'", - tuntap->dev_name); - fp = popen(buf, "r"); - - if (fp) { - memset(buf, 0, N2N_LINUX_SYSTEMCMD_SIZE); /* make sure buf is NULL terminated. */ - nread = fread(buf, N2N_LINUX_SYSTEMCMD_SIZE-1, 1, fp); - fclose(fp); - fp = NULL; - - traceEvent(TRACE_INFO, "ifconfig address = %s", buf); - - if(nread > 0) { - buf[nread] = '\0'; - tuntap->ip_addr = inet_addr(buf); - } + struct ifreq ifr; + int fd; + + if((fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP)) < 0) { + traceEvent(TRACE_ERROR, "socket creation failed [%d]: %s", errno, strerror(errno)); + return; } -} + ifr.ifr_addr.sa_family = AF_INET; + strncpy(ifr.ifr_name, tuntap->dev_name, IFNAMSIZ); + ifr.ifr_name[IFNAMSIZ-1] = '\0'; + + if(ioctl(fd, SIOCGIFADDR, &ifr) != -1) + tuntap->ip_addr = ((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr.s_addr; + + close(fd); +} #endif /* #ifdef __linux__ */ diff --git a/tuntap_osx.c b/tuntap_osx.c index e746769..8e85e86 100644 --- a/tuntap_osx.c +++ b/tuntap_osx.c @@ -46,7 +46,7 @@ int tuntap_open(tuntap_dev *device /* ignored */, } if(device->fd < 0) { - traceEvent(TRACE_ERROR, "Unable to open tap device %s", tap_device); + traceEvent(TRACE_ERROR, "Unable to open any tap devices /dev/tap0 through /dev/tap254. Is this user properly authorized to access those descriptors?"); traceEvent(TRACE_ERROR, "Please read https://github.com/ntop/n2n/blob/dev/doc/n2n_on_MacOS.txt"); return(-1); } else {