diff --git a/README.md b/README.md index dd8fbbd..7e15dfc 100644 --- a/README.md +++ b/README.md @@ -104,11 +104,11 @@ Running edge as a service edge can also be run as a service instead of cli: -1. Edit `/etc/n2n/edge` with your custom options. See `/etc/n2n/edge.conf.sample`. +1. Edit `/etc/n2n/edge.conf` with your custom options. See `/etc/n2n/edge.conf.sample`. 2. Start the service: `sudo systemctl start edge` 3. Optionally enable edge start on boot: `sudo systemctl enable edge` -You can run multiple edge service instances by creating `/etc/n2n/edge-instance1` and +You can run multiple edge service instances by creating `/etc/n2n/edge-instance1.conf` and starting it with `sudo systemctl start edge@instance1`. IPv6 Support diff --git a/doc/MTU.md b/doc/MTU.md new file mode 100644 index 0000000..f9f512e --- /dev/null +++ b/doc/MTU.md @@ -0,0 +1,59 @@ +MTU +--- + +The MTU of the VPN interface is set to a lower value (rather than the standard +1500 B value) to avoid excessive fragmentation on the datagram sent on internet. +This is required because n2n adds additional headers to the packets received from +the VPN interface. The size of the final frame sent through the internet interface +must have a size <= the internet interface MTU (usually 1500 B). + +As a fragmentation example, suppose that a 3000 B TCP segment should be sent through +the VPN. If the VPN interface MTU is set to 1500, the packet will be split into two +fragments of 1500 B each. However, n2n will add its headers to each fragment, so +each fragment becomes a 1540 B packet. The internet interface mtu, which is 1500 B, +will fragment each packet again in two further fragments (e.g. 1500 + 50 B), so a +total of 4 fragments will be sent over internet. On the other hand, if the VPN interface +MTU was set to 1460 that would result in only 3 fragments sent as the initial segment of +3000 would be split in 1460 + 1460 + 80 B and that would not be further fragmented. + +IP packet fragmentation in general is something to avoid, as described in +http://www.hpl.hp.com/techreports/Compaq-DEC/WRL-87-3.pdf . When possible, +the fragmentation should be moved to the TCP layer by a proper MSS value. +This can be forced by mangling the packet MSS, which is called "MSS clamping" (currently not +implemented in n2n). See https://github.com/gsliepen/tinc/blob/228a03aaa707a5fcced9dd5148a4bdb7e5ef025b/src/route.c#L386 . + +The exact value to use as a clamp value, however, depends on the PMTU, which is the minimum +MTU of the path between two hosts. Knowing the PMTU is also useful for a sender in order to +avoid fragmentation at the IP level. Trying to find the biggest MTU is useful since it allows to +maximize bandwidth. + +PMTU Discovery Failures +----------------------- + +Most operating systems try to periodically discover the PMTU by using a PMTU discovery algorithm. +This involves setting the DF (don't fragment) flag on the IP packets. When a large IP packet exceeds +the MTU of a router in the path, an "ICMP Fragmentation Needed" message should be received, which will +help the OS tune the size of the next IP packets. However, some routers do not report such ICMP message, +which results in packets being silently dropped. The `tracepath` tool can be used to detect the PMTU. + +The main problem when this situation occurs is that the actual PMTU is unknown, so an automatic +solution is not applicable. The user must manually specify a lower MTU for the VPN interface +in order to solve the issue. + +n2n and MTU +----------- + +n2n should work by default in different environments. For this reason, the following solution +has been provided: + +- PMTU discovery is disabled when possible (via the IP_MTU_DISCOVER socket option). This avoid + silently dropping a oversize packet due to the DF flag, however it possibly increments fragmentation on the path. + +- As examplained above, a lower MTU is set on the VPN interface, thus removing excessive fragmentation on + the sender. + +- 1400 B is used instead of 1500 B as the reference value for the internet interface MTU. + This essentially avoids fragmentation when the PMTU is >= 1400 B. + +This is a conservative solution which should make n2n work by default. The user can manually +specify the MTU and re-enable PMTU discovery via the CLI options. diff --git a/doc/Windows.md b/doc/Windows.md index 19bc0f2..b95fdc6 100644 --- a/doc/Windows.md +++ b/doc/Windows.md @@ -42,3 +42,30 @@ NOTE: if cmake has problems finding the installed OpenSSL library, try to downlo "C:\Program Files\CMake\bin\cmake" The compiled exe files should now be available under the Release directory. + +# Run + +The `edge.exe` program reads the `edge.conf` file located into the current directory if no option is provided. + +Here is an example `edge.conf` file: + +``` +-c=mycommunity +-k=mysecretkey + +# supernode IP address +-l=1.2.3.4:5678 + +# edge IP address +-a=192.168.100.1 +``` + +The `supernode.exe` program reads the `supernode.conf` file located into the current directory if no option is provided. + +Here is an example `supernode.conf` file: + +``` +-l=5678 +``` + +See `edge.exe --help` and `supernode.exe --help` for a list of supported options. diff --git a/edge.c b/edge.c index 876468a..d65e4bf 100644 --- a/edge.c +++ b/edge.c @@ -134,13 +134,16 @@ static void help() { "[-f]" #endif /* #ifndef WIN32 */ #ifdef __linux__ -"[-T ]" + "[-T ]" #endif "[-m ] " "-l \n" " " "[-p ] [-M ] " - "[-r] [-E] [-v] [-i ] [-t ] [-b] [-A] [-h]\n\n"); +#ifdef __linux__ + "[-D] " +#endif + "[-r] [-E] [-v] [-i ] [-t ] [-A] [-h]\n\n"); #if defined(N2N_CAN_NAME_IFACE) printf("-d | tun device name\n"); @@ -152,8 +155,6 @@ 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("-b | Periodically resolve supernode IP\n"); - printf(" | (when supernodes are running on dynamic IPs)\n"); printf("-p | Fixed local UDP port.\n"); #ifndef WIN32 printf("-u | User ID (numeric) to use when privileges are dropped.\n"); @@ -165,6 +166,10 @@ 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" + " | causes connections stall when not properly supported.\n"); +#endif printf("-r | Enable packet forwarding through n2n community.\n"); #ifdef N2N_HAVE_AES printf("-A | Use AES CBC for encryption (default=use twofish).\n"); @@ -252,6 +257,14 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e break; } +#ifdef __linux__ + case 'D' : /* enable PMTU discovery */ + { + conf->disable_pmtu_discovery = 0; + break; + } +#endif + case 'k': /* encrypt key */ { if(conf->encrypt_key) free(conf->encrypt_key); @@ -287,7 +300,7 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e } case 'i': /* supernode registration interval */ - conf->register_interval = atoi(optarg); + conf->register_interval = atoi(optargument); break; #if defined(N2N_CAN_NAME_IFACE) @@ -299,12 +312,6 @@ static int setOption(int optkey, char *optargument, n2n_priv_config_t *ec, n2n_e } #endif - case 'b': - { - conf->re_resolve_supernode_ip = 1; - break; - } - case 'p': { conf->local_port = atoi(optargument); @@ -621,9 +628,6 @@ int main(int argc, char* argv[]) { struct passwd *pw = NULL; #endif - if(argc == 1) - help(); - /* Defaults */ edge_init_conf_defaults(&conf); memset(&ec, 0, sizeof(ec)); @@ -646,18 +650,28 @@ int main(int argc, char* argv[]) { snprintf(ec.ip_mode, sizeof(ec.ip_mode), "static"); snprintf(ec.netmask, sizeof(ec.netmask), "255.255.255.0"); - traceEvent(TRACE_NORMAL, "Starting n2n edge %s %s", PACKAGE_VERSION, PACKAGE_BUILDDATE); - if((argc >= 2) && (argv[1][0] != '-')) { rc = loadFromFile(argv[1], &conf, &ec); if(argc > 2) rc = loadFromCLI(argc, argv, &conf, &ec); - } else + } else if(argc > 1) rc = loadFromCLI(argc, argv, &conf, &ec); + else +#ifdef WIN32 + /* Load from current directory */ + rc = loadFromFile("edge.conf", &conf, &ec); +#else + rc = -1; +#endif if(rc < 0) help(); + if(edge_verify_conf(&conf) != 0) + help(); + + traceEvent(TRACE_NORMAL, "Starting n2n edge %s %s", PACKAGE_VERSION, PACKAGE_BUILDDATE); + if(0 == strcmp("dhcp", ec.ip_mode)) { traceEvent(TRACE_NORMAL, "Dynamic IP address assignment enabled."); @@ -665,9 +679,6 @@ int main(int argc, char* argv[]) { } else traceEvent(TRACE_NORMAL, "ip_mode='%s'", ec.ip_mode); - if(edge_verify_conf(&conf) != 0) - help(); - if(!( #ifdef __linux__ (ec.tuntap_dev_name[0] != 0) && diff --git a/edge_utils.c b/edge_utils.c index 2e6bb86..585b4a5 100644 --- a/edge_utils.c +++ b/edge_utils.c @@ -1573,12 +1573,12 @@ static void readFromIPSocket(n2n_edge_t * eee, int in_sock) { } else { - traceEvent(TRACE_WARNING, "Rx REGISTER_SUPER_ACK with wrong or old cookie."); + traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK with wrong or old cookie."); } } else { - traceEvent(TRACE_WARNING, "Rx REGISTER_SUPER_ACK with no outstanding REGISTER_SUPER."); + traceEvent(TRACE_INFO, "Rx REGISTER_SUPER_ACK with no outstanding REGISTER_SUPER."); } break; } case MSG_TYPE_PEER_INFO: { @@ -1818,6 +1818,15 @@ static int edge_init_sockets(n2n_edge_t *eee, int udp_local_port, int mgmt_port, else traceEvent(TRACE_ERROR, "Could not set TOS 0x%x[%d]: %s", tos, errno, strerror(errno)); } + + if(eee->conf.disable_pmtu_discovery) { + int sockopt = 0; + + 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"); + } #endif eee->udp_mgmt_sock = open_socket(mgmt_port, 0 /* bind LOOPBACK */); @@ -1862,6 +1871,7 @@ void edge_init_conf_defaults(n2n_edge_conf_t *conf) { conf->transop_id = N2N_TRANSFORM_ID_NULL; conf->drop_multicast = 1; conf->allow_p2p = 1; + conf->disable_pmtu_discovery = 1; conf->register_interval = REGISTER_SUPER_INTERVAL_DFL; if(getenv("N2N_KEY")) { diff --git a/n2n.h b/n2n.h index e57be34..388e741 100644 --- a/n2n.h +++ b/n2n.h @@ -164,10 +164,11 @@ typedef struct tuntap_dev { * same value if they are to understand each other. */ #define N2N_COMPRESSION_ENABLED 1 -#define DEFAULT_MTU 1390 +#define DEFAULT_MTU 1290 -/** Uncomment this to enable the MTU check */ -//#define MTU_ASSERT_VALUE 1500 +/** Uncomment this to enable the MTU check, then try to ssh to generate a fragmented packet. */ +/** NOTE: see doc/MTU.md for an explanation on the 1400 value */ +//#define MTU_ASSERT_VALUE 1400 /** Common type used to hold stringified IP addresses. */ typedef char ipstr_t[32]; @@ -205,10 +206,10 @@ typedef struct n2n_edge_conf { n2n_sn_name_t sn_ip_array[N2N_EDGE_NUM_SUPERNODES]; n2n_community_t community_name; /**< The community. 16 full octets. */ n2n_transform_t transop_id; /**< The transop to use. */ - uint8_t re_resolve_supernode_ip; uint8_t dyn_ip_mode; /**< Interface IP address is dynamically allocated, eg. DHCP. */ uint8_t allow_routing; /**< Accept packet no to interface address. */ uint8_t drop_multicast; /**< Multicast ethernet addresses. */ + uint8_t disable_pmtu_discovery; /**< Disable the Path MTU discovery. */ uint8_t allow_p2p; /**< Allow P2P connection */ uint8_t sn_num; /**< Number of supernode addresses defined. */ uint8_t tos; /** TOS for sent packets */ diff --git a/sn.c b/sn.c index 96810fa..b39ca11 100644 --- a/sn.c +++ b/sn.c @@ -746,7 +746,9 @@ static void help() { printf("supernode "); printf("-l "); printf("-c "); +#if defined(N2N_HAVE_DAEMON) printf("[-f] "); +#endif printf("[-v] "); printf("\n\n"); @@ -972,20 +974,24 @@ static void term_handler(int sig) int main(int argc, char * const argv[]) { int rc; - if(argc == 1) - help(); - init_sn(&sss_node); if((argc >= 2) && (argv[1][0] != '-')) { rc = loadFromFile(argv[1], &sss_node); if(argc > 2) rc = loadFromCLI(argc, argv, &sss_node); - } else + } else if(argc > 1) rc = loadFromCLI(argc, argv, &sss_node); + else +#ifdef WIN32 + /* Load from current directory */ + rc = loadFromFile("supernode.conf", &sss_node); +#else + rc = -1; +#endif if(rc < 0) - return(-1); + help(); #if defined(N2N_HAVE_DAEMON) if(sss_node.daemon) { @@ -1119,7 +1125,7 @@ static int run_loop(n2n_sn_t * sss) { HASH_ITER(hh, sss->communities, comm, tmp) { purge_expired_registrations( &comm->edges, &last_purge_edges ); - if(comm->edges == NULL) { + if((comm->edges == NULL) && (!sss->lock_communities)) { traceEvent(TRACE_INFO, "Purging idle community %s", comm->community); HASH_DEL(sss->communities, comm); free(comm); diff --git a/wireshark/README.md b/wireshark/README.md new file mode 100644 index 0000000..4c0b6fd --- /dev/null +++ b/wireshark/README.md @@ -0,0 +1,9 @@ +Wireshark Lua plugin to dissect n2n traffic. + +Quick load: + +``` + wireshark -X lua_script:n2n.lua +``` + +NOTE: the dissector only decodes traffic on UDP port 50001. In order to decode n2n traffic on another UDP port you can use the "Decode As..." function of wireshark. diff --git a/wireshark/n2n.lua b/wireshark/n2n.lua new file mode 100644 index 0000000..ff45341 --- /dev/null +++ b/wireshark/n2n.lua @@ -0,0 +1,316 @@ +-- (C) 2019 - ntop.org and contributors + +n2n = Proto("n2n", "n2n Protocol") + +-- ############################################# + +PKT_TYPE_PING = 0 +PKT_TYPE_REGISTER = 1 +PKT_TYPE_DEREGISTER = 2 +PKT_TYPE_PACKET = 3 +PKT_TYPE_REGISTER_ACK = 4 +PKT_TYPE_REGISTER_SUPER = 5 +PKT_TYPE_REGISTER_SUPER_ACK = 6 +PKT_TYPE_REGISTER_SUPER_NAK = 7 +PKT_TYPE_FEDERATION = 8 +PKT_TYPE_PEER_INFO = 9 +PKT_TYPE_QUERY_PEER = 10 + +PKT_TRANSFORM_NULL = 1 +PKT_TRANSFORM_TWOFISH = 2 +PKT_TRANSFORM_AESCBC = 3 + +FLAG_FROM_SUPERNODE = 0x0020 +FLAG_SOCKET = 0x0040 +FLAG_OPTIONS = 0x0080 + +SOCKET_FAMILY_AF_INET = 0x0000 +SOCKET_FAMILY_AF_INET6 = 0x8000 + +-- ############################################# + +version = ProtoField.uint8("n2n.version", "version", base.DEC) +ttl = ProtoField.uint8("n2n.ttl", "ttl", base.DEC) + +packet_type_mask = 0x001f +pkt_type_2_str = { + [PKT_TYPE_PING] = "ping", + [PKT_TYPE_REGISTER] = "register", + [PKT_TYPE_DEREGISTER] = "deregister", + [PKT_TYPE_PACKET] = "packet", + [PKT_TYPE_REGISTER_ACK] = "register_ack", + [PKT_TYPE_REGISTER_SUPER] = "register_super", + [PKT_TYPE_REGISTER_SUPER_ACK] = "register_super_ack", + [PKT_TYPE_REGISTER_SUPER_NAK] = "register_super_nak", + [PKT_TYPE_FEDERATION] = "federation", + [PKT_TYPE_PEER_INFO] = "peer_info", + [PKT_TYPE_QUERY_PEER] = "query_peer", +} +packet_type = ProtoField.uint8("n2n.packet_type", "packetType", base.HEX, pkt_type_2_str, packet_type_mask) + +flags_mask = 0xffe0 +flags = ProtoField.uint16("n2n.flags", "Flags", base.HEX, nil, flags_mask) +from_supernode_flag = ProtoField.uint16("n2n.flags.from_supernode", "from_supernode", base.BOOLEAN, nil, FLAG_FROM_SUPERNODE) +socket_flag = ProtoField.uint16("n2n.flags.socket", "socket", base.BOOLEAN, nil, FLAG_SOCKET) +options_flag = ProtoField.uint16("n2n.flags.options", "options", base.BOOLEAN, nil, FLAG_OPTIONS) +community = ProtoField.string("n2n.community", "Community", base.ASCII) + +-- ############################################# + +src_mac = ProtoField.ether("n2n.src_mac", "Source") +dst_mac = ProtoField.ether("n2n.dst_mac", "Destination") +socket_info = ProtoField.none("n2n.socket", "Socket Info") +socket_family = ProtoField.uint16("n2n.socket.family", "Family", base.HEX, { + [0] = "AF_INET", +}) +socket_port = ProtoField.uint16("n2n.socket.port", "Port") +socket_ipv4 = ProtoField.ipv4("n2n.socket.ipv4", "IPv4") +socket_ipv6 = ProtoField.ipv6("n2n.socket.ipv6", "IPv6") + +-- ############################################# + +peer_info_field = ProtoField.none("n2n.peer_info", "PeerInfo") +peer_info_flags = ProtoField.uint16("n2n.peer_info.flags", "Flags") +peer_info_mac = ProtoField.ether("n2n.peer_info.query_mac", "Query") + +query_peer_field = ProtoField.none("n2n.query_peer", "QueryPeer") + +-- ############################################# + + + +packet_field = ProtoField.none("n2n.packet", "Packet") +packet_transform = ProtoField.uint16("n2n.packet.transform", "Transform", base.HEX, { + [PKT_TRANSFORM_NULL] = "Plaintext", + [PKT_TRANSFORM_TWOFISH] = "TwoFish", + [PKT_TRANSFORM_AESCBC] = "AES CBC", +}) +packet_payload = ProtoField.bytes("n2n.packet.payload", "Payload") + +-- ############################################# + +register_field = ProtoField.none("n2n.register", "Register") +register_cookie = ProtoField.uint32("n2n.register.cookie", "Cookie", base.HEX) + +register_ack_field = ProtoField.none("n2n.register_ack", "RegisterACK") +register_ack_cookie = ProtoField.uint32("n2n.register_ack.cookie", "Cookie", base.HEX) + +register_super_field = ProtoField.none("n2n.register_super", "RegisterSuper") +register_super_cookie = ProtoField.uint32("n2n.register_super.cookie", "Cookie", base.HEX) +register_super_auth_schema = ProtoField.uint16("n2n.register_super.auth.schema", "AuthSchema", base.HEX) +register_super_auth_data = ProtoField.uint16("n2n.register_super.auth.data", "AuthData", base.HEX) + +register_super_ack_field = ProtoField.none("n2n.register_super_ack", "RegisterSuperACK") +register_super_ack_cookie = ProtoField.uint32("n2n.register_super_ack.cookie", "Cookie", base.HEX) +register_super_ack_lifetime = ProtoField.uint16("n2n.register_super_ack.lifetime", "Registration Lifetime", base.DEC) +register_super_ack_num_sn = ProtoField.uint8("n2n.register_super_ack.num_sn", "Num Supernodes", base.DEC) + +-- ############################################# + +n2n.fields = { + version, ttl, packet_type, + flags, from_supernode_flag, socket_flag, options_flag, + community, + + -- Generic + src_mac, dst_mac, + socket_info, socket_family, socket_port, socket_ipv4, socket_ipv6, + + -- PKT_TYPE_REGISTER + register_field, register_cookie, + -- PKT_TYPE_PACKET + packet_field, packet_transform, packet_payload, + -- PKT_TYPE_REGISTER_ACK + register_ack_field, register_ack_cookie, + -- PKT_TYPE_REGISTER_SUPER + register_super_field, register_super_cookie, register_super_auth_schema, register_super_auth_data, + -- PKT_TYPE_REGISTER_SUPER_ACK + register_super_ack_field, register_super_ack_cookie, register_super_ack_lifetime, register_super_ack_num_sn, + -- PKT_TYPE_PEER_INFO + peer_info_field, peer_info_flags, peer_info_mac, + -- PKT_TYPE_QUERY_PEER + query_peer_field, +} + +-- ############################################# + +function dissect_socket(subtree, buffer, offset) + local sock_baselen = 4 + local sock_protolen = 0 + buffer = buffer(offset) + local sock_family = bit.band(buffer(0,4):uint(), 0xFFFF0000) + + if(sock_family == SOCKET_FAMILY_AF_INET) then + sock_protolen = 4 + elseif(sock_family == SOCKET_FAMILY_AF_INET6) then + sock_protolen = 16 + end + + local totlen = sock_baselen + sock_protolen + local socktree = subtree:add(socket_info, buffer(0, totlen)) + + socktree:add(socket_family, buffer(0, 2)) + socktree:add(socket_port, buffer(2, 2)) + + if(sock_family == SOCKET_FAMILY_AF_INET) then + socktree:add(socket_ipv4, buffer(4, sock_protolen)) + elseif(sock_family == SOCKET_FAMILY_AF_INET6) then + socktree:add(socket_ipv6, buffer(4, sock_protolen)) + end + + return offset+totlen, socktree +end + +-- ############################################# + +function dissect_register(subtree, buffer, flags) + local regtree = subtree:add(register_field, buffer) + + regtree:add(register_cookie, buffer(0,4)) + regtree:add(src_mac, buffer(4,6)) + regtree:add(dst_mac, buffer(10,6)) + + if(bit.band(flags, FLAG_SOCKET) == FLAG_SOCKET) then + dissect_socket(regtree, buffer, 16) + end + + return regtree +end + +-- ############################################# + +function dissect_register_ack(subtree, buffer, flags) + local regtree = subtree:add(register_ack_field, buffer) + + regtree:add(register_ack_cookie, buffer(0,4)) + regtree:add(src_mac, buffer(4,6)) + regtree:add(dst_mac, buffer(10,6)) + + return regtree +end + +-- ############################################# + +function dissect_packet(subtree, buffer, flags, pinfo) + local pktree = subtree:add(packet_field, buffer) + + pktree:add(src_mac, buffer(0,6)) + pktree:add(dst_mac, buffer(6,6)) + + if(bit.band(flags, FLAG_SOCKET) == FLAG_SOCKET) then + idx = dissect_socket(pktree, buffer, 12) + else + idx = 12 + end + + pktree:add(packet_transform, buffer(idx,2)) + local payload = pktree:add(packet_payload, buffer(idx+2)) + local transform = buffer(idx,2):uint() + + -- Can only dissect unencrypted data + if(transform == PKT_TRANSFORM_NULL) then + Dissector.get("eth_withoutfcs"):call(buffer(idx+2):tvb(), pinfo, payload) + end + + return pktree +end + +-- ############################################# + +function dissect_register_super(subtree, buffer, flags) + local regtree = subtree:add(register_super_field, buffer) + + regtree:add(register_super_cookie, buffer(0,4)) + regtree:add(src_mac, buffer(4,6)) + regtree:add(register_super_auth_schema, buffer(10,2)) + regtree:add(register_super_auth_data, buffer(12,2)) + + return regtree +end + +-- ############################################# + +function dissect_register_super_ack(subtree, buffer, flags) + local regtree = subtree:add(register_super_ack_field, buffer) + + regtree:add(register_super_ack_cookie, buffer(0,4)) + regtree:add(dst_mac, buffer(4,6)) + regtree:add(register_super_ack_lifetime, buffer(10,2)) + local idx = dissect_socket(regtree, buffer, 12) + regtree:add(register_super_ack_num_sn, buffer(idx, 1)) + + return regtree +end + +-- ############################################# + +function dissect_peer_info(subtree, buffer, flags) + local peertree = subtree:add(peer_info_field, buffer) + + peertree:add(peer_info_flags, buffer(0,2)) + peertree:add(peer_info_mac, buffer(2,6)) + dissect_socket(peertree, buffer, 8) + + return peertree +end + +-- ############################################# + +function dissect_query_peer(subtree, buffer, flags) + local peertree = subtree:add(query_peer_field, buffer) + + peertree:add(src_mac, buffer(0,6)) + peertree:add(dst_mac, buffer(6,6)) + + return peertree +end + +-- ############################################# + +function n2n.dissector(buffer, pinfo, tree) + local length = buffer:len() + if length < 20 then return end + + pinfo.cols.protocol = n2n.name + + local pkt_type = bit.band(buffer(2,2):uint(), packet_type_mask) + local subtree = tree:add(n2n, buffer(), string.format("n2n Protocol, Type: %s", pkt_type_2_str[pkt_type] or "Unknown")) + + -- Common + subtree:add(version, buffer(0,1)) + subtree:add(ttl, buffer(1,1)) + subtree:add(packet_type, buffer(2,2)) + local flags_buffer = buffer(2,2) + local flags_tree = subtree:add(flags, flags_buffer) + subtree:add(community, buffer(4,16)) + + -- Flags + flags_tree:add(from_supernode_flag, flags_buffer) + flags_tree:add(socket_flag, flags_buffer) + flags_tree:add(options_flag, flags_buffer) + + -- Packet specific + local flags = bit.band(buffer(2,2):uint(), flags_mask) + local typebuf = buffer(20) + + if(pkt_type == PKT_TYPE_REGISTER) then + dissect_register(subtree, typebuf, flags) + elseif(pkt_type == PKT_TYPE_REGISTER) then + dissect_register_ack(subtree, typebuf, flags) + elseif(pkt_type == PKT_TYPE_PACKET) then + dissect_packet(subtree, typebuf, flags, pinfo) + elseif(pkt_type == PKT_TYPE_REGISTER_SUPER) then + dissect_register_super(subtree, typebuf, flags) + elseif(pkt_type == PKT_TYPE_REGISTER_SUPER_ACK) then + dissect_register_super_ack(subtree, typebuf, flags) + elseif(pkt_type == PKT_TYPE_PEER_INFO) then + dissect_peer_info(subtree, typebuf, flags) + elseif(pkt_type == PKT_TYPE_QUERY_PEER) then + dissect_query_peer(subtree, typebuf, flags) + end +end + +-- ############################################# + +local udp_port = DissectorTable.get("udp.port") +udp_port:add(50001, n2n)