Go语言使用UDP做一个NAT打洞

首先需要一个公网服务器用来做转发,运行server服务

package main

import (
	"fmt"
	"net"
	"sync"
)

var clients = make(map[string]*net.UDPAddr)
var mu sync.Mutex

func main() {
	addr, err := net.ResolveUDPAddr("udp6", ":8172")
	if err != nil {
		fmt.Println("Error resolving address:", err)
		return
	}
	fmt.Println(addr.String())
	conn, err := net.ListenUDP("udp", addr)
	if err != nil {
		fmt.Println("Error listening:", err)
		return
	}
	defer conn.Close()

	fmt.Println("Server started at", addr)

	for {
		buffer := make([]byte, 1024)
		n, clientAddr, err := conn.ReadFromUDP(buffer)
		if err != nil {
			continue
		}
		mu.Lock()
		clients[clientAddr.String()] = clientAddr
		mu.Unlock()

		fmt.Println("Received from", clientAddr, ":", string(buffer[:n]))

		// Notify other clients of new client
		for addr := range clients {
			if addr != clientAddr.String() {
				// Send the new client's address to all other clients
				conn.WriteToUDP([]byte(clientAddr.String()), clients[addr])
			}
		}
	}
}

以上程序会将每一个UDP连接存储到Map中,当有新的连接进入的时候就会把map中的地址转发给它,客户端解析这个地址以后就给它发送UDP请求就可以了。

package main

import (
	"fmt"
	"net"
	"strings"
	"time"
)

func main() {
	serverAddr, err := net.ResolveUDPAddr("udp6", "117.50.172.13:8172")
	if err != nil {
		fmt.Println("Error resolving server address:", err)
		return
	}

	// 创建未连接的 UDP 套接字
	conn, err := net.ListenUDP("udp6", nil)
	if err != nil {
		fmt.Println("Error listening:", err)
		return
	}
	defer conn.Close()

	// Send initial message to register with server
	_, err = conn.WriteToUDP([]byte("REGISTER"), serverAddr)
	if err != nil {
		fmt.Println("Error sending register:", err)
		return
	}

	go func() {
		for {
			_, err = conn.WriteToUDP([]byte("REGISTER"), serverAddr)
			if err != nil {
				fmt.Println("Error sending register:", err)
			}
			time.Sleep(1 * time.Second)
		}
	}()

	// Listen for other clients' addresses
	go func() {
		buffer := make([]byte, 1024)
		for {
			n, addr, err := conn.ReadFromUDP(buffer)
			if err != nil {
				fmt.Println("Error reading from UDP:", err)
				continue
			}
			clientAddr := string(buffer[:n])
			fmt.Println("Received address from server:", clientAddr)
			if !strings.Contains(clientAddr, ".") {
				// 返回响应消息
				conn.WriteToUDP([]byte("RESPONSE"), addr)
				fmt.Println("获取到了别的客户端发送的消息:", clientAddr, addr.String())
				time.Sleep(1 * time.Second)
				continue
			}
			// Extract the IP and port from the received address
			targetAddr, err := net.ResolveUDPAddr("udp", clientAddr)
			if err != nil {
				fmt.Println("Error resolving address:", err)
				continue
			}
			// Send a message to the other client
			fmt.Println("Sending message to:", targetAddr.String())
			if _, err := conn.WriteToUDP([]byte("Hello from client!"), targetAddr); err != nil {
				fmt.Println("Error sending message:", err)
			}
		}
	}()

	// Keep the client running
	for {
		time.Sleep(5 * time.Second)
	}
}

这样就可以打洞成功了,但是需要注意的是两个客户端都是对称 NAT的情况下,以上程序是不能运行的。

因为在对称 NAT 中,对于每个内部设备与外部设备之间的连接,会创建一个唯一的映射。也就是说,如果同一个内部设备与不同的外部设备建立连接,它将使用不同的外部 IP 地址和端口。

评论列表
0/1000
共 0 评论