Last active
April 15, 2022 15:20
-
-
Save wesl-ee/5ab9c40c1926849bc3a77ff71ee4a9df to your computer and use it in GitHub Desktop.
UDP firewall holepunching / NAT traversal demo in Go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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