Skip to content

Instantly share code, notes, and snippets.

@wesl-ee
Last active April 15, 2022 15:20
Show Gist options
  • Save wesl-ee/5ab9c40c1926849bc3a77ff71ee4a9df to your computer and use it in GitHub Desktop.
Save wesl-ee/5ab9c40c1926849bc3a77ff71ee4a9df to your computer and use it in GitHub Desktop.
UDP firewall holepunching / NAT traversal demo in Go
/* UDP firewall holepunching / NAT traversal demo in Go
*
* Very simple, send a UDP packet out to a server and await a response. Should
* work even if you are behind a NAT as many home routers will open a firewall
* "hole" for egress datagrams; this way we can connect directly to a server
* without port-forwarding (we cannot do this over TCP, a connection-oriented
* protocol).
*
* Usage (server): go build && ./udp-nat-traversal
* Usage (client): go build && ./udp-nat-traversal 127.0.0.1
*
* https://en.wikipedia.org/wiki/UDP_hole_punching
*
* ~ Wesley Coakley
*/
package main
import (
"fmt"
"net"
"os"
"strconv"
)
// Send a UDP packet out
func doClient(remote string, port int) {
msgBuf := make([]byte, 1024)
// Resolve the passed address as UDP4
toAddr, err := net.ResolveUDPAddr("udp4", remote + ":" + strconv.Itoa(port))
if err != nil {
fmt.Printf("Could not resolve %s:%d\n", remote, port)
return
}
fmt.Printf("Trying to punch a hole to %s:%d\n", remote, port)
// Initiate the transaction (force IPv4 to demo firewall punch)
conn, err := net.DialUDP("udp4", nil, toAddr)
defer conn.Close()
if err != nil {
fmt.Printf("Unable to connect to %s:%d\n", remote, port)
return
}
// Initiate the transaction, creating the hole
msg := "ただいま~"
fmt.Fprintf(conn, msg)
fmt.Printf("Sent a UDP packet to %s:%d\n\tSent: %s\n", remote, port, msg)
// Await a response through our firewall hole
msgLen, fromAddr, err := conn.ReadFromUDP(msgBuf)
if err != nil {
fmt.Printf("Error reading UDP response!\n")
return
}
fmt.Printf("Received a UDP packet back from %s:%d\n\tResponse: %s\n",
fromAddr.IP, fromAddr.Port, msgBuf[:msgLen])
fmt.Println("Success: NAT traversed! ^-^")
}
func doServer(port int) {
msgBuf := make([]byte, 1024)
// Initiatlize a UDP listener
ln, err := net.ListenUDP("udp4", &net.UDPAddr{Port: port})
if err != nil {
fmt.Printf("Unable to listen on :%d\n", port)
return
}
fmt.Printf("Listening on :%d\n", port)
for {
fmt.Println("---")
// Await incoming packets
rcvLen, addr, err := ln.ReadFrom(msgBuf)
if err != nil {
fmt.Println("Transaction was initiated but encountered an error!")
continue
}
fmt.Printf("Received a packet from: %s\n\tSays: %s\n",
addr.String(), msgBuf[:rcvLen])
// Let the client confirm a hole was punched through to us
reply := "お帰り~"
copy(msgBuf, []byte(reply))
_, err = ln.WriteTo(msgBuf[:len(reply)], addr)
if err != nil {
fmt.Println("Socket closed unexpectedly!")
}
fmt.Printf("Sent reply to %s\n\tReply: %s\n",
addr.String(), msgBuf[:len(reply)])
}
}
func main() {
port := 9199
if len(os.Args) > 1 {
// Act as a client and initiate the transaction
remoteServer := os.Args[1]
doClient(remoteServer, port)
return
}
// Act as a server and respond to incoming UDP messages
doServer(port)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment