Skip to content

Instantly share code, notes, and snippets.

@nerdalert
Last active August 6, 2023 19:26
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nerdalert/4570a070b978ebc66ddcce105bf654b7 to your computer and use it in GitHub Desktop.
Save nerdalert/4570a070b978ebc66ddcce105bf654b7 to your computer and use it in GitHub Desktop.
package main
import (
"flag"
"fmt"
"net"
"strconv"
"github.com/libp2p/go-reuseport"
"github.com/pion/stun"
log "github.com/sirupsen/logrus"
)
const (
stunServer1 = "stun1.l.google.com:19302"
stunServer2 = "stun2.l.google.com:19302"
)
var sourcePort int
var stunServer string
func init() {
flag.IntVar(&sourcePort, "source-port", 0, "Source port to use")
flag.StringVar(&stunServer, "stun-server", "", "STUN server address")
}
func main() {
flag.Parse()
if sourcePort == 0 {
log.Fatalf("--source-port option is required")
}
isSymmetric, err := IsSymmetricNAT()
if err != nil {
log.Error(err)
}
if isSymmetric {
log.Infof("IS Symmetric NAT")
}
if !isSymmetric {
log.Infof("IS NOT Symmetric NAT")
}
}
// IsSymmetricNAT attempts to infer if the node is behind a symmetric
// nat device by querying two STUN servers. If the requests return
// different ports, then it is likely the node is behind a symmetric nat.
func IsSymmetricNAT() (bool, error) {
firstStun, err := StunRequest(stunServer1, sourcePort)
if err != nil {
return false, fmt.Errorf("failed to queury the STUN server %s", stunServer1)
}
log.Infof("STUN Result from %s => [ %s ]", stunServer1, firstStun)
secondStun, err := StunRequest(stunServer2, sourcePort)
if err != nil {
return false, fmt.Errorf("failed to queury the STUN server %s", stunServer1)
}
if firstStun != secondStun {
return true, nil
}
log.Infof("STUN Result from %s => [ %s ]", stunServer2, secondStun)
return false, nil
}
// StunRequest initiate a connection to a STUN server sourced from the wg src port
func StunRequest(stunServer string, srcPort int) (string, error) {
log.Debugf("dialing stun server %s", stunServer)
conn, err := reuseport.Dial("udp4", fmt.Sprintf(":%d", srcPort), stunServer)
if err != nil {
log.Errorf("stun dialing timed out %v", err)
return "", fmt.Errorf("failed to dial stun server %s: %w", stunServer, err)
}
defer conn.Close()
stunResults, err := stunDialer(&conn)
if err != nil {
return "", fmt.Errorf("stun dialing timed out %v", err)
}
return stunResults, nil
}
func stunDialer(conn *net.Conn) (string, error) {
c, err := stun.NewClient(*conn)
if err != nil {
log.Errorf("%v", err)
}
var xorAddr stun.XORMappedAddress
if err = c.Do(stun.MustBuild(stun.TransactionID, stun.BindingRequest), func(res stun.Event) {
if res.Error != nil {
log.Println(res.Error)
return
}
if getErr := xorAddr.GetFrom(res.Message); getErr != nil {
log.Println(getErr)
if err := c.Close(); err != nil {
log.Println(err)
return
}
return
}
log.Debugf("Stun address and port is: %s:%d", xorAddr.IP, xorAddr.Port)
}); err != nil {
return "", err
}
if err := c.Close(); err != nil {
return "", err
}
stunAddress := net.JoinHostPort(xorAddr.IP.String(), strconv.Itoa(xorAddr.Port))
if err != nil {
return "", err
}
return stunAddress, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment