On 16 Jan 2025, at 13:55, Juliusz Chroboczek wrote: > >> Ignore this - there is a whole general class ... > > The wonders of debugging ICE issues ;-) Been there, done that, given up. Right - on https://github.com/dirkx/galene/tree/fix_bind_turn_ip is a sketch for the ICE T-Shirt. The main concept of this sketch is to augment the IP specified for TURN into a range of gradually more precise versions: auto :1234 simply set the port, still bind to 0.0.0.0 1.2.3.4:1234 bind specifically to that all the way to 1.2.3.4:9999/4.3.2.1:1234 Where 1.2.3.4 is the external/visible/relay address (on port 9999) and galene actually bind()s/listen()s to 4.3.2.1, port 1234. With 1.2.3.4:9999/:1234 (i.e no listen address) going to the 0.0.0.0/bind anything. :1234, :1234/:4321 do not - they go, as before to scan of the public/non RFC1918 addresses. Does it make sense to spend a few hours to making this draft a bit more real & systematically tested ? Once thing I've not quite understanding yet is why the relay generator just needs the relay IP but not the relay port. Dw. commit dac7ae531fb1ed9ec15c484de562674f1e73822b Author: Dirk-Willem van Gulik Date: Thu Jan 16 18:00:36 2025 +0100 First draft of exposed/internal address separation. diff --git a/INSTALL b/INSTALL index c9bccaa..5843f47 100644 --- a/INSTALL +++ b/INSTALL @@ -210,8 +210,18 @@ Galène includes an IPv4-only TURN server, which is controlled by the the one given in the option; this is useful when running behind NAT with port forwarding set up. + * If an IP address is in the form [][:][/[][:] then + the left side is taken as the external (i.e. exposed and coded into + a turn URI) pair. And the additional right hand side as the internal + IP/port pair that is mapped to those external addresses. So for + example a configuration of 203.0.113.1:1194/10.11.0.1:2194 is for + a TURN server that is exposed (e.g. redirected from a firewall) + as 203.0.113.1:1194 but is known in the DMZ or on the other side + of NAT as running on 10.11.0.1:2194. + * the default value is `auto`, which behaves like `:1194` if there is no - `data/ice-servers.json` file, and like `""` otherwise. + `data/ice-servers.json` file, and like `""` otherwise. In this case + galene will bind to the ANY address. If the server is not accessible from the Internet, e.g. because of NAT or because it is behind a restrictive firewall, then you should configure diff --git a/turnserver/turnserver.go b/turnserver/turnserver.go index 1dcebe0..9ab37fe 100644 --- a/turnserver/turnserver.go +++ b/turnserver/turnserver.go @@ -8,21 +8,101 @@ import ( "net" "strconv" "sync" + "fmt" + "strings" "github.com/pion/turn/v2" "github.com/pion/webrtc/v3" ) +const DEFAULT_PORT = 1194 + var username string var password string var Address string +// Generalize a TCP/IP address & port pair. When the +// port is '0' - an `off' is assumed. +// +type UDPTCPAddr struct { + IP net.IP + Port int + Zone string // IPv6 scoped addressing zone +} + +// General inside/outside address; where the two are +// the same in the cannonical simple case; but, for example +// in a DMZ or when NAT-ting; the internal address is +// that what the turns server listens on (i.e bind()); whereas +// any turn:// URI's and so on are constructed with the +// outside address. The term 'Paired' is taken from NAT. +// +type PairedAddr struct { + exposedAddr UDPTCPAddr // Or Relay Address + internalAddr UDPTCPAddr +} + +func (addr PairedAddr) String() string { + return fmt.Sprintf("%v/%v", addr.exposedAddr, addr.internalAddr); +} + +func ResolveUDPTCPAddr(address string) (*UDPTCPAddr, error) { + // We're doing a 'cheat' here; and rely on the UDP translator; as we know + // that UDP/TCP are indentical with regard to port/addr structure. + addr, err := net.ResolveUDPAddr("udp", address) + if err != nil { + return nil, err + } + // Complete the cheat by just copying out the address details; but not the network familly + r := UDPTCPAddr { addr.IP, addr.Port, addr.Zone } + return &r, nil +} + +func NewUDPTCPAddr(Address string) (*UDPTCPAddr, error) { + if Address == "" || Address == "off" { + return &UDPTCPAddr{}, nil + } + ad := Address + if Address == "auto" { + ad = fmt.Sprintf(":%v", DEFAULT_PORT) + } else + if strings.Index(ad,":") == -1 { + ad = fmt.Sprintf("%v:%v", ad, DEFAULT_PORT) + } + addr, err := ResolveUDPTCPAddr(ad) + if err != nil { + return nil, err + } + return addr, nil +} + +func NewPairedAddr(str string) (*PairedAddr, error) { + i := strings.Index(str,"/") + left :=str + if i > 1 { + left = str[0:i] + str = str[i+1:] + } + exposedAddr, err := NewUDPTCPAddr(left) + if err != nil { + return nil, err + } + internalAddr, err := NewUDPTCPAddr(str) + if err != nil { + return nil, err + } + + e := PairedAddr { *exposedAddr, *internalAddr } + return &e, nil +} + var server struct { mu sync.Mutex addresses []net.Addr server *turn.Server } +// Remove any RFC 1918 addreses from a list of addresses. func publicAddresses() ([]net.IP, error) { addrs, err := net.InterfaceAddrs() if err != nil { @@ -57,34 +137,38 @@ func listener(a net.IP, port int, relay net.IP) (*turn.PacketConnConfig, *turn.L var lc *turn.ListenerConfig - s := net.JoinHostPort(a.String(), strconv.Itoa(port)) + as := a.String() + if a == nil { as = "" } + s := net.JoinHostPort(as, strconv.Itoa(port)) - var g turn.RelayAddressGenerator + var g turn.RelayAddressGenerator + raddr := a.String() if relay == nil || relay.IsUnspecified() { g = &turn.RelayAddressGeneratorNone{ Address: a.String(), } } else { + raddr = relay.String() g = &turn.RelayAddressGeneratorStatic{ RelayAddress: relay, Address: a.String(), } } - p, err := net.ListenPacket("udp4", s) + p, err := net.ListenPacket("udp", s) if err == nil { pcc = &turn.PacketConnConfig{ PacketConn: p, RelayAddressGenerator: g, } + log.Printf("TURN: listener on udp:%v, visible address: %v",s,raddr) } else { log.Printf("TURN: listenPacket(%v): %v", s, err) } - l, err := net.Listen("tcp4", s) + l, err := net.Listen("tcp", s) if err == nil { lc = &turn.ListenerConfig{ Listener: l, RelayAddressGenerator: g, } + log.Printf("TURN: listener on tcp:%v, visible address: %v",s,raddr) } else { log.Printf("TURN: listen(%v): %v", s, err) } @@ -99,20 +183,13 @@ func Start() error { if server.server != nil { return nil } - - if Address == "" { - return errors.New("built-in TURN server disabled") - } - - ad := Address - if Address == "auto" { - ad = ":1194" + addressPair, err := NewPairedAddr(Address) + if err != nil { + return errors.New(fmt.Sprintf("TURN: Address error: %v", err)) + } + if addressPair.internalAddr.Port == 0 { + return errors.New("TURN: built-in TURN server disabled") } - addr, err := net.ResolveUDPAddr("udp4", ad) - if err != nil { - return err - } - username = "galene" buf := make([]byte, 6) _, err = rand.Read(buf) @@ -126,26 +203,29 @@ func Start() error { var lcs []turn.ListenerConfig var pccs []turn.PacketConnConfig - - if addr.IP != nil && !addr.IP.IsUnspecified() { - a := addr.IP.To4() + if addressPair.exposedAddr.IP != nil && !addressPair.exposedAddr.IP.IsUnspecified() { + a := addressPair.exposedAddr.IP.To4() if a == nil { - return errors.New("couldn't parse address") + return errors.New("couldn't parse address/not an IPv4 address") } - pcc, lc := listener(net.IP{0, 0, 0, 0}, addr.Port, a) + pcc, lc := listener(addressPair.internalAddr.IP, addressPair.internalAddr.Port, addressPair.exposedAddr.IP) if pcc != nil { pccs = append(pccs, *pcc) server.addresses = append(server.addresses, &net.UDPAddr{ - IP: a, - Port: addr.Port, + IP: addressPair.exposedAddr.IP, + Port: addressPair.exposedAddr.Port, }) + log.Printf("TURN: External address udp:%v:%v", + addressPair.exposedAddr.IP, addressPair.exposedAddr.Port) } if lc != nil { lcs = append(lcs, *lc) server.addresses = append(server.addresses, &net.TCPAddr{ - IP: a, - Port: addr.Port, + IP: addressPair.exposedAddr.IP, + Port: addressPair.exposedAddr.Port, }) + log.Printf("TURN: External address tcp:%v:%v", + addressPair.exposedAddr.IP, addressPair.exposedAddr.Port) } } else { as, err := publicAddresses() @@ -158,24 +238,26 @@ func Start() error { } for _, a := range as { - pcc, lc := listener(a, addr.Port, nil) + pcc, lc := listener(a, addressPair.internalAddr.Port, nil) if pcc != nil { pccs = append(pccs, *pcc) server.addresses = append(server.addresses, &net.UDPAddr{ IP: a, - Port: addr.Port, + Port: addressPair.exposedAddr.Port, }, ) + log.Printf("TURN: external address udp:%v:%v", a, addressPair.exposedAddr.Port) } if lc != nil { lcs = append(lcs, *lc) server.addresses = append(server.addresses, &net.TCPAddr{ IP: a, - Port: addr.Port, + Port: addressPair.exposedAddr.Port, }, ) + log.Printf("TURN: external address tcp:%v:%v", a, addressPair.exposedAddr.Port) } } } @@ -183,8 +265,7 @@ func Start() error { if len(pccs) == 0 && len(lcs) == 0 { return errors.New("couldn't establish any listeners") } - - log.Printf("Starting built-in TURN server on %v", addr.String()) + log.Printf("TURN: Starting built-in TURN server") server.server, err = turn.NewServer(turn.ServerConfig{ Realm: "galene.org", diff --git a/turnserver/turnserver_test.go b/turnserver/turnserver_test.go new file mode 100644 index 0000000..efbb870 --- /dev/null +++ b/turnserver/turnserver_test.go @@ -0,0 +1,39 @@ +package turnserver + +import ( + "testing" +) + +func TestParseAddr(t *testing.T) { + a := []struct{ p, g string }{ + {"", "{ 0 }/{ 0 }"}, + {"off", "{ 0 }/{ 0 }"}, + {"auto", "{ 1194 }/{ 1194 }"}, + {":1234", "{ 1234 }/{ 1234 }"}, + {":1234/:4321", "{ 1234 }/{ 4321 }"}, + {"10.11.0.1:1234", "{10.11.0.1 1234 }/{10.11.0.1 1234 }"}, + {"10.11.0.1:1234/:4321", "{10.11.0.1 1234 }/{ 4321 }"}, + {"10.11.0.1:1234/1.2.3.4:4321", "{10.11.0.1 1234 }/{1.2.3.4 4321 }"}, + {"always-1-2-3-4.webweaving.org", "{1.2.3.4 1194 }/{1.2.3.4 1194 }"}, + {"always-1-2-3-4.webweaving.org:4321", "{1.2.3.4 4321 }/{1.2.3.4 4321 }"}, + {"always-1-2-3-4.webweaving.org:4321/:1234", "{1.2.3.4 4321 }/{ 1234 }"}, + {"always-1-2-3-4.webweaving.org:4321/127.0.0.1:1234", "{1.2.3.4 4321 }/{127.0.0.1 1234 }"}, + {"always-1-2-3-4.webweaving.org:4321/127.0.0.1", "{1.2.3.4 4321 }/{127.0.0.1 1194 }"}, +/* Only works on an pure IPv4 machine + {"localhost:1234/1.2.3.4:4321", "{127.0.0.1 1234 }/{1.2.3.4 4321 }"}, + {"always-1-2-3-4.webweaving.org:4321/localhost", "{1.2.3.4 4321 }/{127.0.0.1 1194 }"}, + {"always-1-2-3-4.webweaving.org:4321/localhost:1234", "{1.2.3.4 4321 }/{127.0.0.1 1234 }"}, +*/ + } + + for _, pg := range a { + g, err := NewPairedAddr(pg.p) + if err != nil { + t.Errorf("Error: '%v' (not expected)", err) + } + if g.String() != pg.g { + t.Errorf("'%v', got '%v', expected '%v'", + pg.p, g, pg.g) + } + } +}