mirror of https://github.com/ntop/n2n.git
Logan008
5 years ago
committed by
GitHub
9 changed files with 473 additions and 34 deletions
@ -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. |
@ -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. |
@ -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) |
Loading…
Reference in new issue