Skip to content

Instantly share code, notes, and snippets.

@nerdalert
Created November 22, 2022 16:16
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/9e36c87b6caae027219262015c5e1699 to your computer and use it in GitHub Desktop.
Save nerdalert/9e36c87b6caae027219262015c5e1699 to your computer and use it in GitHub Desktop.

Symmetric NAT discovery

package main

import (
	"fmt"
	"net"
	"strconv"
	"time"

	"github.com/pion/stun"
	log "github.com/sirupsen/logrus"
)

const (
	stunServer1  = "stun1.l.google.com:19302"
	stunServer2  = "stun2.l.google.com:19302"
	WgListenPort = 51820
)

func main() {

	isSymmetric, err := IsSymmetricNAT()
	if err != nil {
		log.Error(err)
	}

	log.Infof("Symmetric NAT is -> %t", isSymmetric)
}

// 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, WgListenPort)
	if err != nil {
		return false, fmt.Errorf("failed to queury the STUN server %s", stunServer1)
	}
	secondStun, err := StunRequest(stunServer2, WgListenPort)
	if err != nil {
		return false, fmt.Errorf("failed to queury the STUN server %s", stunServer1)
	}
	if firstStun != secondStun {
		return true, nil
	}

	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) {
	lAddr := &net.UDPAddr{
		Port: srcPort,
	}
	d := &net.Dialer{
		Timeout:   3 * time.Second,
		LocalAddr: lAddr,
	}
	log.Debugf("dialing stun server %s", stunServer)
	conn, err := d.Dial("udp4", stunServer)
	if err != nil {
		log.Errorf("%v", err)
		return "", fmt.Errorf("failed to dial stun server %s: %v", stunServer, err)
	}

	stunResults, err := stunDialer(&conn)
	if err != nil {
		return "", 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.Infof("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