package checker import ( "fmt" "net" "time" "github.com/pion/turn/v4" ) // stunBindingResult holds the outcome of a STUN Binding test. type stunBindingResult struct { RTT time.Duration ReflexiveAddr net.Addr IsPrivateMapped bool Err error } // runSTUNBinding sends a STUN Binding Request to the remote and returns the // reflexive (XOR-MAPPED) address along with the RTT. We construct a tiny // turn.Client with no credentials; its SendBindingRequestTo path drives a // vanilla STUN exchange (RFC 5389) and works on UDP/TCP/TLS/DTLS through // the dialed PacketConn we hand it. func runSTUNBinding(d *dialedConn, timeout time.Duration) stunBindingResult { cfg := &turn.ClientConfig{ Conn: d.pc, STUNServerAddr: d.remoteAddr.String(), RTO: timeout, Software: "happyDomain-checker-stun-turn", } client, err := turn.NewClient(cfg) if err != nil { return stunBindingResult{Err: fmt.Errorf("turn.NewClient: %w", err)} } defer client.Close() if err := client.Listen(); err != nil { return stunBindingResult{Err: fmt.Errorf("client.Listen: %w", err)} } start := time.Now() addr, err := client.SendBindingRequestTo(d.remoteAddr) if err != nil { return stunBindingResult{Err: err} } res := stunBindingResult{ RTT: time.Since(start), ReflexiveAddr: addr, } if udpAddr, ok := addr.(*net.UDPAddr); ok { res.IsPrivateMapped = isPrivate(udpAddr.IP) } else if tcpAddr, ok := addr.(*net.TCPAddr); ok { res.IsPrivateMapped = isPrivate(tcpAddr.IP) } return res } func isPrivate(ip net.IP) bool { if ip == nil { return false } return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsUnspecified() }