aboutsummaryrefslogtreecommitdiff
path: root/server
diff options
context:
space:
mode:
authorDavid Fifield <david@bamsoftware.com>2023-05-21 10:08:09 -0600
committerDavid Fifield <david@bamsoftware.com>2023-06-08 13:24:22 -0600
commit9edaee65470a1483bbdbe984e5e15a885f1e95d2 (patch)
tree9af46150255bce39f2655d4543dcdd13bb388fcc /server
parent130b63ccddb64b10a881b150c93c0122e0698a9e (diff)
downloadsnowflake-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.go14
-rw-r--r--server/dial_linux.go45
-rw-r--r--server/server.go4
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)