From 438fcdfc5ff63304e4aaff4dd709ad9026a8e038 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Fri, 20 Jul 2018 10:04:04 +0100 Subject: [PATCH 01/16] Build for arm64 --- .circleci/config.yml | 3 ++- contrib/deb/generate.sh | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 3dbe743..73c0787 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -31,7 +31,8 @@ jobs: PKGARCH=i386 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-i386 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-i386; PKGARCH=mipsel sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mipsel && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mipsel; PKGARCH=mips sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-mips && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-mips; - PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armh && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf; + PKGARCH=armhf sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-armhf && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-armhf; + PKGARCH=arm64 sh contrib/deb/generate.sh && mv yggdrasil /tmp/upload/$CINAME-$CIVERSION-linux-arm64 && mv yggdrasilctl /tmp/upload/$CINAME-$CIVERSION-yggdrasilctl-linux-arm64; sudo alien --to-rpm yggdrasil*.deb --scripts --keep-version && mv *.rpm /tmp/upload/; mv *.deb /tmp/upload/ diff --git a/contrib/deb/generate.sh b/contrib/deb/generate.sh index beee3d5..9d5064b 100644 --- a/contrib/deb/generate.sh +++ b/contrib/deb/generate.sh @@ -26,8 +26,9 @@ elif [ $PKGARCH = "i386" ]; then GOARCH=386 GOOS=linux ./build elif [ $PKGARCH = "mipsel" ]; then GOARCH=mipsle GOOS=linux ./build elif [ $PKGARCH = "mips" ]; then GOARCH=mips64 GOOS=linux ./build elif [ $PKGARCH = "armhf" ]; then GOARCH=arm GOOS=linux GOARM=7 ./build +elif [ $PKGARCH = "arm64" ]; then GOARCH=arm64 GOOS=linux ./build else - echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf" + echo "Specify PKGARCH=amd64,i386,mips,mipsel,armhf,arm64" exit -1 fi From 996a593fa270a345b91032867f87b2369ef7ba02 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Fri, 20 Jul 2018 23:02:25 -0500 Subject: [PATCH 02/16] Sort dotgraph links by integer value --- src/yggdrasil/admin.go | 35 +++++++++++++++++++++-------------- src/yggdrasil/tun_linux.go | 2 +- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/admin.go b/src/yggdrasil/admin.go index 0a7314c..b0d487a 100644 --- a/src/yggdrasil/admin.go +++ b/src/yggdrasil/admin.go @@ -605,9 +605,16 @@ func (a *admin) getResponse_dot() []byte { name string key string parent string + port switchPort options string } infos := make(map[string]nodeInfo) + // Get coords as a slice of strings, FIXME? this looks very fragile + coordSlice := func(coords string) []string { + tmp := strings.Replace(coords, "[", "", -1) + tmp = strings.Replace(tmp, "]", "", -1) + return strings.Split(tmp, " ") + } // First fill the tree with all known nodes, no parents addInfo := func(nodes []admin_nodeInfo, options string, tag string) { for _, node := range nodes { @@ -621,6 +628,14 @@ func (a *admin) getResponse_dot() []byte { } else { info.name = n["ip"].(string) } + coordsSplit := coordSlice(info.key) + if len(coordsSplit) != 0 { + portStr := coordsSplit[len(coordsSplit)-1] + portUint, err := strconv.ParseUint(portStr, 10, 64) + if err == nil { + info.port = switchPort(portUint) + } + } infos[info.key] = info } } @@ -628,12 +643,6 @@ func (a *admin) getResponse_dot() []byte { addInfo(sessions, "fillcolor=\"#acf3fd\" style=filled fontname=\"sans serif\"", "Open session") // blue addInfo(peers, "fillcolor=\"#ffffb5\" style=filled fontname=\"sans serif\"", "Connected peer") // yellow addInfo(append([]admin_nodeInfo(nil), *self), "fillcolor=\"#a5ff8a\" style=filled fontname=\"sans serif\"", "This node") // green - // Get coords as a slice of strings, FIXME? this looks very fragile - coordSlice := func(coords string) []string { - tmp := strings.Replace(coords, "[", "", -1) - tmp = strings.Replace(tmp, "]", "", -1) - return strings.Split(tmp, " ") - } // Now go through and create placeholders for any missing nodes for _, info := range infos { // This is ugly string manipulation @@ -665,10 +674,12 @@ func (a *admin) getResponse_dot() []byte { keys = append(keys, info.key) } // sort - less := func(i, j int) bool { + sort.SliceStable(keys, func(i, j int) bool { return keys[i] < keys[j] - } - sort.Slice(keys, less) + }) + sort.SliceStable(keys, func(i, j int) bool { + return infos[keys[i]].port < infos[keys[j]].port + }) // Now print it all out var out []byte put := func(s string) { @@ -686,11 +697,7 @@ func (a *admin) getResponse_dot() []byte { if info.key == info.parent { continue } // happens for the root, skip it - coordsSplit := coordSlice(key) - if len(coordsSplit) == 0 { - continue - } - port := coordsSplit[len(coordsSplit)-1] + port := fmt.Sprint(info.port) style := "fontname=\"sans serif\"" if infos[info.parent].name == "?" || infos[info.key].name == "?" { style = "fontname=\"sans serif\" style=dashed color=\"#999999\" fontcolor=\"#999999\"" diff --git a/src/yggdrasil/tun_linux.go b/src/yggdrasil/tun_linux.go index a1f8abd..aa9e791 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/yggdrasil/tun_linux.go @@ -34,7 +34,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) // that the MTU gets rounded down to 65521 instead of causing a panic. if iftapmode { if tun.mtu > 65535-tun_ETHER_HEADER_LENGTH { - tun.mtu = 65535-tun_ETHER_HEADER_LENGTH + tun.mtu = 65535 - tun_ETHER_HEADER_LENGTH } } // Friendly output From 9cbcaf39acb97868bf34866acb4f784adbd2a335 Mon Sep 17 00:00:00 2001 From: Arceliar Date: Sat, 21 Jul 2018 18:59:29 -0500 Subject: [PATCH 03/16] Use coords for queue stream IDs in the switch, and append protocol/port information to coords when sending, to designate different streams --- src/yggdrasil/session.go | 25 ++++++++++++++++++++++++- src/yggdrasil/switch.go | 23 ++++++----------------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 8631ff2..f1ee92a 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -425,10 +425,33 @@ func (sinfo *sessionInfo) doSend(bs []byte) { if !sinfo.init { return } // To prevent using empty session keys + // Now we append something to the coords + // Specifically, we append a 0, and then arbitrary data + // The 0 ensures that the destination node switch forwards to the self peer (router) + // The rest is ignored, but it's still part as the coords, so it affects switch queues + // This helps separate traffic streams (protocol + source + dest port) are queued independently + var coords []byte + addUint64 := func(bs []byte) { + // Converts bytes to a uint64 + // Converts that back to variable length bytes + // Appends it to coords + var u uint64 + for _, b := range bs { + u <<= 8 + u |= uint64(b) + } + coords = append(coords, wire_encode_uint64(u)...) + } + coords = append(coords, sinfo.coords...) // Start with the real coords + coords = append(coords, 0) // Add an explicit 0 for the destination's self peer + addUint64(bs[6:7]) // Byte 6, next header type (e.g. TCP vs UDP) + // TODO parse headers, in case the next header isn't TCP/UDP for some reason + addUint64(bs[40:42]) // Bytes 40-41, source port for TCP/UDP + addUint64(bs[42:44]) // Bytes 42-43, destination port for TCP/UDP payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) defer util_putBytes(payload) p := wire_trafficPacket{ - Coords: sinfo.coords, + Coords: coords, Handle: sinfo.theirHandle, Nonce: *nonce, Payload: payload, diff --git a/src/yggdrasil/switch.go b/src/yggdrasil/switch.go index 6fe50bf..63380da 100644 --- a/src/yggdrasil/switch.go +++ b/src/yggdrasil/switch.go @@ -529,24 +529,13 @@ func switch_getPacketCoords(packet []byte) []byte { } // Returns a unique string for each stream of traffic -// Equal to type+coords+handle for traffic packets -// Equal to type+coords+toKey+fromKey for protocol traffic packets +// Equal to coords +// The sender may append arbitrary info to the end of coords (as long as it's begins with a 0x00) to designate separate traffic streams +// Currently, it's the IPv6 next header type and the first 2 uint16 of the next header +// This is equivalent to the TCP/UDP protocol numbers and the source / dest ports +// TODO figure out if something else would make more sense (other transport protocols?) func switch_getPacketStreamID(packet []byte) string { - pType, pTypeLen := wire_decode_uint64(packet) - _, coordLen := wire_decode_coords(packet[pTypeLen:]) - end := pTypeLen + coordLen - switch { - case pType == wire_Traffic: - end += handleLen // handle - case pType == wire_ProtocolTraffic: - end += 2 * boxPubKeyLen - default: - end = 0 - } - if end > len(packet) { - end = len(packet) - } - return string(packet[:end]) + return string(switch_getPacketCoords(packet)) } // Handle an incoming packet From 3f4295f8cd3fd1b1bad98069e85776b9218f33e5 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 22 Jul 2018 12:00:40 +0100 Subject: [PATCH 04/16] Only split queues based on port number for TCP/UDP/SCTP, rely only on protocol number for other protos to prevent issues with IPIP, GRE, etc --- src/yggdrasil/session.go | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index f1ee92a..c605333 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -445,9 +445,16 @@ func (sinfo *sessionInfo) doSend(bs []byte) { coords = append(coords, sinfo.coords...) // Start with the real coords coords = append(coords, 0) // Add an explicit 0 for the destination's self peer addUint64(bs[6:7]) // Byte 6, next header type (e.g. TCP vs UDP) - // TODO parse headers, in case the next header isn't TCP/UDP for some reason - addUint64(bs[40:42]) // Bytes 40-41, source port for TCP/UDP - addUint64(bs[42:44]) // Bytes 42-43, destination port for TCP/UDP + // Is the next header TCP, UDP, SCTP for finding source port? + // 0x06 (6) = TCP, 0x11 (17) = UDP, 0x84 (132) = SCTP + // TODO: Perhaps improve this for other protocols + // TODO: Consider that the Next Header could be an IPv6 Extension Header instead + if bs[6:7][0] == 0x06 || bs[6:7][0] == 0x11 || bs[6:7][0] == 0x84 { + if len(bs) > 44 { + addUint64(bs[40:42]) // Bytes 40-41, source port for TCP/UDP/SCTP + addUint64(bs[42:44]) // Bytes 42-43, destination port for TCP/UDP/SCTP + } + } payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) defer util_putBytes(payload) p := wire_trafficPacket{ From 81fde1a80598cf375ccfd7f555f98c3614ae20db Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 22 Jul 2018 18:16:03 +0100 Subject: [PATCH 05/16] Use flow label instead of TCP/UDP/SCTP source/destination ports --- src/yggdrasil/session.go | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index c605333..34da823 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -429,30 +429,20 @@ func (sinfo *sessionInfo) doSend(bs []byte) { // Specifically, we append a 0, and then arbitrary data // The 0 ensures that the destination node switch forwards to the self peer (router) // The rest is ignored, but it's still part as the coords, so it affects switch queues - // This helps separate traffic streams (protocol + source + dest port) are queued independently + // This helps separate traffic streams (coords, flowlabel) to be queued independently var coords []byte - addUint64 := func(bs []byte) { - // Converts bytes to a uint64 - // Converts that back to variable length bytes - // Appends it to coords - var u uint64 - for _, b := range bs { - u <<= 8 - u |= uint64(b) - } - coords = append(coords, wire_encode_uint64(u)...) - } coords = append(coords, sinfo.coords...) // Start with the real coords - coords = append(coords, 0) // Add an explicit 0 for the destination's self peer - addUint64(bs[6:7]) // Byte 6, next header type (e.g. TCP vs UDP) - // Is the next header TCP, UDP, SCTP for finding source port? - // 0x06 (6) = TCP, 0x11 (17) = UDP, 0x84 (132) = SCTP - // TODO: Perhaps improve this for other protocols - // TODO: Consider that the Next Header could be an IPv6 Extension Header instead - if bs[6:7][0] == 0x06 || bs[6:7][0] == 0x11 || bs[6:7][0] == 0x84 { - if len(bs) > 44 { - addUint64(bs[40:42]) // Bytes 40-41, source port for TCP/UDP/SCTP - addUint64(bs[42:44]) // Bytes 42-43, destination port for TCP/UDP/SCTP + flowlabel := int(bs[1:2][0]&0x0f)<<16 | int(bs[2:3][0])<<8 | int(bs[3:4][0]) + if flowlabel > 0 { + coords = append(coords, 0) // Add an explicit 0 for the destination's self peer + if flowlabel>>16 > 0 { + coords = append(coords, byte(flowlabel>>16)) + } + if flowlabel>>8 > 0 { + coords = append(coords, byte(flowlabel>>8)) + } + if flowlabel>>0 > 0 { + coords = append(coords, byte(flowlabel>>0)) } } payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) From 38e8b036d2edeff80a498cb935590ec62183dd35 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Sun, 22 Jul 2018 18:33:53 +0100 Subject: [PATCH 06/16] Use addUint64 instead to not interfere with coordinate parsing --- src/yggdrasil/session.go | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 34da823..417760b 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -431,20 +431,22 @@ func (sinfo *sessionInfo) doSend(bs []byte) { // The rest is ignored, but it's still part as the coords, so it affects switch queues // This helps separate traffic streams (coords, flowlabel) to be queued independently var coords []byte - coords = append(coords, sinfo.coords...) // Start with the real coords - flowlabel := int(bs[1:2][0]&0x0f)<<16 | int(bs[2:3][0])<<8 | int(bs[3:4][0]) - if flowlabel > 0 { - coords = append(coords, 0) // Add an explicit 0 for the destination's self peer - if flowlabel>>16 > 0 { - coords = append(coords, byte(flowlabel>>16)) - } - if flowlabel>>8 > 0 { - coords = append(coords, byte(flowlabel>>8)) - } - if flowlabel>>0 > 0 { - coords = append(coords, byte(flowlabel>>0)) + addUint64 := func(bs []byte) { + // Converts bytes to a uint64 + // Converts that back to variable length bytes + // Appends it to coords + var u uint64 + for _, b := range bs { + u <<= 8 + u |= uint64(b) } + coords = append(coords, wire_encode_uint64(u)...) } + coords = append(coords, sinfo.coords...) // Start with the real coords + coords = append(coords, 0) // Then target the local switchport + flowlabel := append([]byte(nil), bs[1:4]...) + flowlabel[0] &= 0x0f + addUint64(flowlabel) payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) defer util_putBytes(payload) p := wire_trafficPacket{ From d17155257722235fd77b0b0acfa0fe5bb5e87812 Mon Sep 17 00:00:00 2001 From: cathugger Date: Sun, 29 Jul 2018 14:30:13 +0000 Subject: [PATCH 07/16] Make TCP read timeouts configurable. This should be helpful on high-latency networks, like Tor or I2P. Also gofmt. --- src/yggdrasil/config/config.go | 1 + src/yggdrasil/core.go | 2 +- src/yggdrasil/tcp.go | 35 +++++++++++++++++++++------------- src/yggdrasil/tun_linux.go | 2 +- 4 files changed, 25 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/config/config.go b/src/yggdrasil/config/config.go index 5b03e23..2498193 100644 --- a/src/yggdrasil/config/config.go +++ b/src/yggdrasil/config/config.go @@ -5,6 +5,7 @@ type NodeConfig struct { Listen string `comment:"Listen address for peer connections. Default is to listen for all\nTCP connections over IPv4 and IPv6 with a random port."` AdminListen string `comment:"Listen address for admin connections Default is to listen for local\nconnections either on TCP/9001 or a UNIX socket depending on your\nplatform. Use this value for yggdrasilctl -endpoint=X."` Peers []string `comment:"List of connection strings for static peers in URI format, i.e.\ntcp://a.b.c.d:e or socks://a.b.c.d:e/f.g.h.i:j"` + ReadTimeout int32 `comment:"Read timeout for connections, specified in milliseconds. If less than 6000 and not negative, 6000 (the default) is used. If negative, reads won't time out."` AllowedEncryptionPublicKeys []string `comment:"List of peer encryption public keys to allow or incoming TCP\nconnections from. If left empty/undefined then all connections\nwill be allowed by default."` EncryptionPublicKey string `comment:"Your public encryption key. Your peers may ask you for this to put\ninto their AllowedEncryptionPublicKeys configuration."` EncryptionPrivateKey string `comment:"Your private encryption key. DO NOT share this with anyone!"` diff --git a/src/yggdrasil/core.go b/src/yggdrasil/core.go index a0d5a11..35ba2ce 100644 --- a/src/yggdrasil/core.go +++ b/src/yggdrasil/core.go @@ -97,7 +97,7 @@ func (c *Core) Start(nc *config.NodeConfig, log *log.Logger) error { c.init(&boxPub, &boxPriv, &sigPub, &sigPriv) c.admin.init(c, nc.AdminListen) - if err := c.tcp.init(c, nc.Listen); err != nil { + if err := c.tcp.init(c, nc.Listen, nc.ReadTimeout); err != nil { c.log.Println("Failed to start TCP interface") return err } diff --git a/src/yggdrasil/tcp.go b/src/yggdrasil/tcp.go index 3372fe6..d023918 100644 --- a/src/yggdrasil/tcp.go +++ b/src/yggdrasil/tcp.go @@ -28,7 +28,8 @@ import ( ) const tcp_msgSize = 2048 + 65535 // TODO figure out what makes sense -const tcp_timeout = 6 * time.Second +const default_tcp_timeout = 6 * time.Second +const tcp_ping_interval = (default_tcp_timeout * 2 / 3) // Wrapper function for non tcp/ip connections. func setNoDelay(c net.Conn, delay bool) { @@ -40,11 +41,12 @@ func setNoDelay(c net.Conn, delay bool) { // The TCP listener and information about active TCP connections, to avoid duplication. type tcpInterface struct { - core *Core - serv net.Listener - mutex sync.Mutex // Protecting the below - calls map[string]struct{} - conns map[tcpInfo](chan struct{}) + core *Core + serv net.Listener + tcp_timeout time.Duration + mutex sync.Mutex // Protecting the below + calls map[string]struct{} + conns map[tcpInfo](chan struct{}) } // This is used as the key to a map that tracks existing connections, to prevent multiple connections to the same keys and local/remote address pair from occuring. @@ -72,9 +74,14 @@ func (iface *tcpInterface) connectSOCKS(socksaddr, peeraddr string) { } // Initializes the struct. -func (iface *tcpInterface) init(core *Core, addr string) (err error) { +func (iface *tcpInterface) init(core *Core, addr string, readTimeout int32) (err error) { iface.core = core + iface.tcp_timeout = time.Duration(readTimeout) * time.Millisecond + if iface.tcp_timeout >= 0 && iface.tcp_timeout < default_tcp_timeout { + iface.tcp_timeout = default_tcp_timeout + } + iface.serv, err = net.Listen("tcp", addr) if err == nil { iface.calls = make(map[string]struct{}) @@ -113,7 +120,7 @@ func (iface *tcpInterface) call(saddr string, socksaddr *string) { iface.calls[saddr] = struct{}{} defer func() { // Block new calls for a little while, to mitigate livelock scenarios - time.Sleep(tcp_timeout) + time.Sleep(default_tcp_timeout) time.Sleep(time.Duration(rand.Intn(1000)) * time.Millisecond) iface.mutex.Lock() delete(iface.calls, saddr) @@ -168,8 +175,9 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { if err != nil { return } - timeout := time.Now().Add(tcp_timeout) - sock.SetReadDeadline(timeout) + if iface.tcp_timeout > 0 { + sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout)) + } _, err = sock.Read(metaBytes) if err != nil { return @@ -254,7 +262,7 @@ func (iface *tcpInterface) handler(sock net.Conn, incoming bool) { atomic.AddUint64(&p.bytesSent, uint64(len(tcp_msg)+len(msgLen)+len(msg))) util_putBytes(msg) } - timerInterval := tcp_timeout * 2 / 3 + timerInterval := tcp_ping_interval timer := time.NewTimer(timerInterval) defer timer.Stop() for { @@ -321,8 +329,9 @@ func (iface *tcpInterface) reader(sock net.Conn, in func([]byte)) error { bs := make([]byte, 2*tcp_msgSize) frag := bs[:0] for { - timeout := time.Now().Add(tcp_timeout) - sock.SetReadDeadline(timeout) + if iface.tcp_timeout > 0 { + sock.SetReadDeadline(time.Now().Add(iface.tcp_timeout)) + } n, err := sock.Read(bs[len(frag):]) if n > 0 { frag = bs[:len(frag)+n] diff --git a/src/yggdrasil/tun_linux.go b/src/yggdrasil/tun_linux.go index a1f8abd..aa9e791 100644 --- a/src/yggdrasil/tun_linux.go +++ b/src/yggdrasil/tun_linux.go @@ -34,7 +34,7 @@ func (tun *tunDevice) setup(ifname string, iftapmode bool, addr string, mtu int) // that the MTU gets rounded down to 65521 instead of causing a panic. if iftapmode { if tun.mtu > 65535-tun_ETHER_HEADER_LENGTH { - tun.mtu = 65535-tun_ETHER_HEADER_LENGTH + tun.mtu = 65535 - tun_ETHER_HEADER_LENGTH } } // Friendly output From 11b0a82c4a6ad71fce12a7e227dc2356025e62da Mon Sep 17 00:00:00 2001 From: cathugger Date: Sun, 29 Jul 2018 22:09:16 +0000 Subject: [PATCH 08/16] Simpler flowlabel parsing; avoid using 0 flowlabel. --- src/yggdrasil/session.go | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 417760b..647bb0c 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -425,28 +425,28 @@ func (sinfo *sessionInfo) doSend(bs []byte) { if !sinfo.init { return } // To prevent using empty session keys - // Now we append something to the coords - // Specifically, we append a 0, and then arbitrary data - // The 0 ensures that the destination node switch forwards to the self peer (router) - // The rest is ignored, but it's still part as the coords, so it affects switch queues - // This helps separate traffic streams (coords, flowlabel) to be queued independently + var coords []byte - addUint64 := func(bs []byte) { - // Converts bytes to a uint64 - // Converts that back to variable length bytes - // Appends it to coords - var u uint64 - for _, b := range bs { - u <<= 8 - u |= uint64(b) - } - coords = append(coords, wire_encode_uint64(u)...) + // Read IPv6 flowlabel field (20 bits). + // XXX(cathugger): is len(bs) validated there? + flowlabel := uint(bs[1]&0x0f)<<16 | uint(bs[2])<<8 | uint(bs[3]) + if flowlabel != 0 { + // Now we append something to the coords + // Specifically, we append a 0, and then arbitrary data + // The 0 ensures that the destination node switch forwards to the self peer (router) + // The rest is ignored, but it's still part as the coords, so it affects switch queues + // This helps separate traffic streams (coords, flowlabel) to be queued independently + + coords = append(coords, sinfo.coords...) // Start with the real coords + coords = append(coords, 0) // Then target the local switchport + coords = append(coords, wire_encode_uint64(uint64(flowlabel))...) // Then variable-length encoded flowlabel + } else { + // 0 value means that flowlabels aren't being generated by OS. + // To save bytes, we're not including it, therefore we won't need self-port override either. + // So just use sinfo.coords directly to avoid golang GC allocations. + // XXX: investigate where flowlabels aren't included, and attempt to look into TCP/UDP/SCTP/DCCP headers' sport/dport fields? + coords = sinfo.coords } - coords = append(coords, sinfo.coords...) // Start with the real coords - coords = append(coords, 0) // Then target the local switchport - flowlabel := append([]byte(nil), bs[1:4]...) - flowlabel[0] &= 0x0f - addUint64(flowlabel) payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) defer util_putBytes(payload) p := wire_trafficPacket{ From fec71008984994d4849287608f41164f5c953de2 Mon Sep 17 00:00:00 2001 From: cathugger Date: Mon, 30 Jul 2018 00:01:37 +0000 Subject: [PATCH 09/16] Clean up / clarify coords sending code. --- src/yggdrasil/session.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 647bb0c..4a44887 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -428,7 +428,7 @@ func (sinfo *sessionInfo) doSend(bs []byte) { var coords []byte // Read IPv6 flowlabel field (20 bits). - // XXX(cathugger): is len(bs) validated there? + // Assumes packet at least contains IPv6 header. flowlabel := uint(bs[1]&0x0f)<<16 | uint(bs[2])<<8 | uint(bs[3]) if flowlabel != 0 { // Now we append something to the coords @@ -437,14 +437,15 @@ func (sinfo *sessionInfo) doSend(bs []byte) { // The rest is ignored, but it's still part as the coords, so it affects switch queues // This helps separate traffic streams (coords, flowlabel) to be queued independently - coords = append(coords, sinfo.coords...) // Start with the real coords - coords = append(coords, 0) // Then target the local switchport - coords = append(coords, wire_encode_uint64(uint64(flowlabel))...) // Then variable-length encoded flowlabel + coords = append(coords, sinfo.coords...) // Start with the real coords + coords = append(coords, 0) // Then target the local switchport + coords = wire_put_uint64(uint64(flowlabel), coords) // Then variable-length encoded flowlabel } else { // 0 value means that flowlabels aren't being generated by OS. // To save bytes, we're not including it, therefore we won't need self-port override either. // So just use sinfo.coords directly to avoid golang GC allocations. - // XXX: investigate where flowlabels aren't included, and attempt to look into TCP/UDP/SCTP/DCCP headers' sport/dport fields? + // Recent enough Linux kernel supports flowlabels out of the box so this will be rare. + // XXX Attempt to look into TCP/UDP/SCTP/DCCP headers' sport/dport fields there? coords = sinfo.coords } payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) From 36dcab9300bbf8b0cf1aa53e1de6561344a3dc3c Mon Sep 17 00:00:00 2001 From: cathugger Date: Mon, 30 Jul 2018 01:58:52 +0000 Subject: [PATCH 10/16] optimize wire_put_uint64; use protokey for flowlabel fallback. --- src/yggdrasil/session.go | 15 ++++++++++++--- src/yggdrasil/wire.go | 20 ++++++++------------ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 4a44887..7a1f19d 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -440,12 +440,21 @@ func (sinfo *sessionInfo) doSend(bs []byte) { coords = append(coords, sinfo.coords...) // Start with the real coords coords = append(coords, 0) // Then target the local switchport coords = wire_put_uint64(uint64(flowlabel), coords) // Then variable-length encoded flowlabel + } else if len(bs) >= 48 /* min UDP len, others are bigger */ && + (bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84) /* TCP UDP SCTP */ { + // if flowlabel was unspecified (0), try to use known protocols' ports + // protokey: proto | sport | dport + pkey := uint64(bs[6])<<32 /* proto */ | + uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | + uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ + coords = append(coords, sinfo.coords...) // Start with the real coords + coords = append(coords, 0) // Then target the local switchport + coords = wire_put_uint64(pkey, coords) // Then variable-length encoded protokey } else { - // 0 value means that flowlabels aren't being generated by OS. + // flowlabel was unspecified (0) and protocol unrecognised. // To save bytes, we're not including it, therefore we won't need self-port override either. // So just use sinfo.coords directly to avoid golang GC allocations. - // Recent enough Linux kernel supports flowlabels out of the box so this will be rare. - // XXX Attempt to look into TCP/UDP/SCTP/DCCP headers' sport/dport fields there? + // Recent enough Linux and BSDs support flowlabels (auto_flowlabel) out of the box so this will be rare. coords = sinfo.coords } payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) diff --git a/src/yggdrasil/wire.go b/src/yggdrasil/wire.go index e92b4fc..d05624e 100644 --- a/src/yggdrasil/wire.go +++ b/src/yggdrasil/wire.go @@ -25,19 +25,15 @@ func wire_encode_uint64(elem uint64) []byte { // Encode uint64 using a variable length scheme. // Similar to binary.Uvarint, but big-endian. -func wire_put_uint64(elem uint64, out []byte) []byte { - bs := make([]byte, 0, 10) - bs = append(bs, byte(elem&0x7f)) - for e := elem >> 7; e > 0; e >>= 7 { - bs = append(bs, byte(e|0x80)) +func wire_put_uint64(e uint64, out []byte) []byte { + var b [10]byte + i := len(b) - 1 + b[i] = byte(e & 0x7f) + for e >>= 7; e != 0; e >>= 7 { + i-- + b[i] = byte(e | 0x80) } - // Now reverse bytes, because we set them in the wrong order - // TODO just put them in the right place the first time... - last := len(bs) - 1 - for idx := 0; idx < len(bs)/2; idx++ { - bs[idx], bs[last-idx] = bs[last-idx], bs[idx] - } - return append(out, bs...) + return append(out, b[i:]...) } // Returns the length of a wire encoded uint64 of this value. From 68a482ed92fa052ba67b8defdf1812ca2f1aac18 Mon Sep 17 00:00:00 2001 From: cathugger Date: Mon, 30 Jul 2018 02:15:57 +0000 Subject: [PATCH 11/16] Simplify flowkey stuff further. --- src/yggdrasil/session.go | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 7a1f19d..9a4dff3 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -426,30 +426,30 @@ func (sinfo *sessionInfo) doSend(bs []byte) { return } // To prevent using empty session keys - var coords []byte // Read IPv6 flowlabel field (20 bits). // Assumes packet at least contains IPv6 header. - flowlabel := uint(bs[1]&0x0f)<<16 | uint(bs[2])<<8 | uint(bs[3]) - if flowlabel != 0 { + flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) + if flowkey == 0 /* not specified */ && + len(bs) >= 48 /* min UDP len, others are bigger */ && + (bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84) /* TCP UDP SCTP */ { + // if flowlabel was unspecified (0), try to use known protocols' ports + // protokey: proto | sport | dport + flowkey = uint64(bs[6])<<32 /* proto */ | + uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | + uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ + } + var coords []byte + if flowkey != 0 { // Now we append something to the coords // Specifically, we append a 0, and then arbitrary data // The 0 ensures that the destination node switch forwards to the self peer (router) // The rest is ignored, but it's still part as the coords, so it affects switch queues // This helps separate traffic streams (coords, flowlabel) to be queued independently - coords = append(coords, sinfo.coords...) // Start with the real coords - coords = append(coords, 0) // Then target the local switchport - coords = wire_put_uint64(uint64(flowlabel), coords) // Then variable-length encoded flowlabel - } else if len(bs) >= 48 /* min UDP len, others are bigger */ && - (bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84) /* TCP UDP SCTP */ { - // if flowlabel was unspecified (0), try to use known protocols' ports - // protokey: proto | sport | dport - pkey := uint64(bs[6])<<32 /* proto */ | - uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | - uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ - coords = append(coords, sinfo.coords...) // Start with the real coords - coords = append(coords, 0) // Then target the local switchport - coords = wire_put_uint64(pkey, coords) // Then variable-length encoded protokey + // TODO could we avoid allocations there and put this work into wire_trafficPacket.encode()? + coords = append(coords, sinfo.coords...) // Start with the real coords + coords = append(coords, 0) // Then target the local switchport + coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey } else { // flowlabel was unspecified (0) and protocol unrecognised. // To save bytes, we're not including it, therefore we won't need self-port override either. From ebb4ec7c33f55214a7c0a4d2d80a0e940ce04e7a Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 30 Jul 2018 11:46:44 +0100 Subject: [PATCH 12/16] Clean up the flow a bit (partly because I am allergic to huge compounded if statements) --- src/yggdrasil/session.go | 49 +++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 9a4dff3..ac1e393 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -423,40 +423,37 @@ func (sinfo *sessionInfo) doWorker() { func (sinfo *sessionInfo) doSend(bs []byte) { defer util_putBytes(bs) if !sinfo.init { + // To prevent using empty session keys return - } // To prevent using empty session keys - + } + coords := sinfo.coords // Read IPv6 flowlabel field (20 bits). // Assumes packet at least contains IPv6 header. flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) - if flowkey == 0 /* not specified */ && - len(bs) >= 48 /* min UDP len, others are bigger */ && - (bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84) /* TCP UDP SCTP */ { - // if flowlabel was unspecified (0), try to use known protocols' ports - // protokey: proto | sport | dport - flowkey = uint64(bs[6])<<32 /* proto */ | - uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | - uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ + // Check if the flowlabel was specified + if flowkey == 0 { + // Does the packet meet the minimum UDP packet size? (others are bigger) + if len(bs) >= 48 { + // Is the protocol TCP, UDP, SCTP? + if bs[6] == 0x06 || bs[6] == 0x11 || bs[6] == 0x84 { + // if flowlabel was unspecified (0), try to use known protocols' ports + // protokey: proto | sport | dport + flowkey = uint64(bs[6])<<32 /* proto */ | + uint64(bs[40])<<24 | uint64(bs[41])<<16 /* sport */ | + uint64(bs[42])<<8 | uint64(bs[43]) /* dport */ + } + } } - var coords []byte + // If we have a flowkey, either through the IPv6 flowlabel field or through + // known TCP/UDP/SCTP proto-sport-dport triplet, then append it to the coords. + // Appending extra coords after a 0 ensures that we still target the local router + // but lets us send extra data (which is otherwise ignored) to help separate + // traffic streams into independent queues if flowkey != 0 { - // Now we append something to the coords - // Specifically, we append a 0, and then arbitrary data - // The 0 ensures that the destination node switch forwards to the self peer (router) - // The rest is ignored, but it's still part as the coords, so it affects switch queues - // This helps separate traffic streams (coords, flowlabel) to be queued independently - - // TODO could we avoid allocations there and put this work into wire_trafficPacket.encode()? - coords = append(coords, sinfo.coords...) // Start with the real coords - coords = append(coords, 0) // Then target the local switchport + coords = append(coords, 0) // First target the local switchport coords = wire_put_uint64(flowkey, coords) // Then variable-length encoded flowkey - } else { - // flowlabel was unspecified (0) and protocol unrecognised. - // To save bytes, we're not including it, therefore we won't need self-port override either. - // So just use sinfo.coords directly to avoid golang GC allocations. - // Recent enough Linux and BSDs support flowlabels (auto_flowlabel) out of the box so this will be rare. - coords = sinfo.coords } + // Prepare the payload payload, nonce := boxSeal(&sinfo.sharedSesKey, bs, &sinfo.myNonce) defer util_putBytes(payload) p := wire_trafficPacket{ From c4e6894d6af8c6b21598524be0ae89245621d334 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Mon, 30 Jul 2018 13:34:32 +0100 Subject: [PATCH 13/16] Copy sinfo.coords for safety --- src/yggdrasil/session.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index ac1e393..bc9d21f 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -426,7 +426,8 @@ func (sinfo *sessionInfo) doSend(bs []byte) { // To prevent using empty session keys return } - coords := sinfo.coords + var coords []byte + coords = append(coords, sinfo.coords...) // Read IPv6 flowlabel field (20 bits). // Assumes packet at least contains IPv6 header. flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) From 67b8a7a53d87dddbd182b8d812adf79846f1b7a3 Mon Sep 17 00:00:00 2001 From: cathugger Date: Mon, 30 Jul 2018 12:43:34 +0000 Subject: [PATCH 14/16] Ensure no memory allocations happen at hot path --- src/yggdrasil/session.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index bc9d21f..0abf524 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -72,7 +72,8 @@ func (s *sessionInfo) update(p *sessionPing) bool { if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU } - s.coords = append([]byte{}, p.Coords...) + // allocate enough space for additional coords + s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) now := time.Now() s.time = now s.tstamp = p.Tstamp @@ -426,8 +427,8 @@ func (sinfo *sessionInfo) doSend(bs []byte) { // To prevent using empty session keys return } - var coords []byte - coords = append(coords, sinfo.coords...) + // code isn't multithreaded so appending to this is safe + coords := sinfo.coords // Read IPv6 flowlabel field (20 bits). // Assumes packet at least contains IPv6 header. flowkey := uint64(bs[1]&0x0f)<<16 | uint64(bs[2])<<8 | uint64(bs[3]) From b4db89ea9def1e851c67d90f493ce57f55c322ba Mon Sep 17 00:00:00 2001 From: cathugger Date: Mon, 30 Jul 2018 13:44:46 +0000 Subject: [PATCH 15/16] Avoid unnecessarily allocating coords slice if it's unchanged. --- src/yggdrasil/session.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/yggdrasil/session.go b/src/yggdrasil/session.go index 0abf524..7a33226 100644 --- a/src/yggdrasil/session.go +++ b/src/yggdrasil/session.go @@ -4,7 +4,10 @@ package yggdrasil // It's responsible for keeping track of open sessions to other nodes // The session information consists of crypto keys and coords -import "time" +import ( + "bytes" + "time" +) // All the information we know about an active session. // This includes coords, permanent and ephemeral keys, handles and nonces, various sorts of timing information for timeout and maintenance, and some metadata for the admin API. @@ -72,8 +75,10 @@ func (s *sessionInfo) update(p *sessionPing) bool { if p.MTU >= 1280 || p.MTU == 0 { s.theirMTU = p.MTU } - // allocate enough space for additional coords - s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) + if !bytes.Equal(s.coords, p.Coords) { + // allocate enough space for additional coords + s.coords = append(make([]byte, 0, len(p.Coords)+11), p.Coords...) + } now := time.Now() s.time = now s.tstamp = p.Tstamp From c6dbc307ae9fd009a614f5d2ed5db5b0f7361d95 Mon Sep 17 00:00:00 2001 From: Neil Alexander Date: Tue, 31 Jul 2018 10:04:22 +0100 Subject: [PATCH 16/16] Update changelog for 0.2.6 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8d4ebb..a74c063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,15 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - in case of vulnerabilities. --> +## [0.2.6] - 2018-07-31 +### Added +- Configurable TCP timeouts to assist in peering over Tor/I2P +- Prefer IPv6 flow label when extending coordinates to sort backpressure queues +- `arm64` builds through CircleCI + +### Changed +- Sort dot graph links by integer value + ## [0.2.5] - 2018-07-19 ### Changed - Make `yggdrasilctl` less case sensitive