diff options
author | David Fifield <david@bamsoftware.com> | 2023-05-21 10:08:09 -0600 |
---|---|---|
committer | David Fifield <david@bamsoftware.com> | 2023-06-08 13:24:22 -0600 |
commit | 9edaee65470a1483bbdbe984e5e15a885f1e95d2 (patch) | |
tree | 9af46150255bce39f2655d4543dcdd13bb388fcc /server | |
parent | 130b63ccddb64b10a881b150c93c0122e0698a9e (diff) | |
download | snowflake-9edaee65470a1483bbdbe984e5e15a885f1e95d2.tar.gz snowflake-9edaee65470a1483bbdbe984e5e15a885f1e95d2.zip |
Use IP_BIND_ADDRESS_NO_PORT when dialing the ORPort on Linux.
When the orport-srcaddr option is set, we bind to a source IP address
before dialing the ORPort/ExtORPort. tor similarly binds to a source IP
address when OutboundBindAddress is set in torrc. Since tor 0.4.7.13,
tor sets IP_BIND_ADDRESS_NO_PORT, and because problems arise when some
programs use IP_BIND_ADDRESS_NO_PORT and some do not, we also have to
start using IP_BIND_ADDRESS_NO_PORT when we upgrade tor
(tpo/anti-censorship/pluggable-transports/snowflake#40270).
Related: tpo/anti-censorship/pluggable-transports/snowflake#40198
Diffstat (limited to 'server')
-rw-r--r-- | server/dial.go | 14 | ||||
-rw-r--r-- | server/dial_linux.go | 45 | ||||
-rw-r--r-- | server/server.go | 4 |
3 files changed, 62 insertions, 1 deletions
diff --git a/server/dial.go b/server/dial.go new file mode 100644 index 0000000..7e514db --- /dev/null +++ b/server/dial.go @@ -0,0 +1,14 @@ +//go:build !linux +// +build !linux + +package main + +import "syscall" + +// dialerControl does nothing. +// +// On Linux, this function would set the IP_BIND_ADDRESS_NO_PORT socket option +// in preparation for a future bind-before-connect. +func dialerControl(network, address string, c syscall.RawConn) error { + return nil +} diff --git a/server/dial_linux.go b/server/dial_linux.go new file mode 100644 index 0000000..b5a53c1 --- /dev/null +++ b/server/dial_linux.go @@ -0,0 +1,45 @@ +//go:build linux +// +build linux + +package main + +import ( + "syscall" + + "golang.org/x/sys/unix" +) + +// dialerControl prepares a syscall.RawConn for a future bind-before-connect by +// setting the IP_BIND_ADDRESS_NO_PORT socket option. +// +// On Linux, setting the IP_BIND_ADDRESS_NO_PORT socket option helps conserve +// ephemeral ports when binding to a specific IP addresses before connecting +// (bind before connect), by not assigning the port number when bind is called, +// but waiting until connect. But problems arise if there are multiple processes +// doing bind-before-connect, and some of them use IP_BIND_ADDRESS_NO_PORT and +// some of them do not. When there is a mix, the ones that do will have their +// ephemeral ports reserved by the ones that do not, leading to EADDRNOTAVAIL +// errors. +// +// tor does bind-before-connect when the OutboundBindAddress option is set in +// torrc. Since version 0.4.7.13 (January 2023), tor sets +// IP_BIND_ADDRESS_NO_PORT unconditionally on platforms that support it, and +// therefore we must do the same, to avoid EADDRNOTAVAIL errors. +// +// # References +// +// https://bugs.torproject.org/tpo/anti-censorship/pluggable-transports/snowflake/40201#note_2839472 +// https://forum.torproject.net/t/tor-relays-inet-csk-bind-conflict/5757/10 +// https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections/ +// https://blog.cloudflare.com/the-quantum-state-of-a-tcp-port/ +// https://forum.torproject.net/t/stable-release-0-4-5-16-and-0-4-7-13/6216 +func dialerControl(network, address string, c syscall.RawConn) error { + var sockErr error + err := c.Control(func(fd uintptr) { + sockErr = syscall.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_BIND_ADDRESS_NO_PORT, 1) + }) + if err == nil { + err = sockErr + } + return err +} diff --git a/server/server.go b/server/server.go index 186f4cf..eba933e 100644 --- a/server/server.go +++ b/server/server.go @@ -76,7 +76,9 @@ func handleConn(conn net.Conn, orPortSrcAddr *net.IPNet) error { addr := conn.RemoteAddr().String() statsChannel <- addr != "" - dialer := net.Dialer{} + dialer := net.Dialer{ + Control: dialerControl, + } if orPortSrcAddr != nil { // Use a random source IP address in the given range. ip, err := randIPAddr(orPortSrcAddr) |