github.com/gnolang/gno@v0.0.0-20240520182011-228e9d0192ce/tm2/pkg/p2p/upnp/probe.go (about) 1 package upnp 2 3 import ( 4 "fmt" 5 "log/slog" 6 "net" 7 "time" 8 ) 9 10 type UPNPCapabilities struct { 11 PortMapping bool 12 Hairpin bool 13 } 14 15 func makeUPNPListener(intPort int, extPort int, logger *slog.Logger) (NAT, net.Listener, net.IP, error) { 16 nat, err := Discover() 17 if err != nil { 18 return nil, nil, nil, fmt.Errorf("NAT upnp could not be discovered: %w", err) 19 } 20 logger.Info(fmt.Sprintf("ourIP: %v", nat.(*upnpNAT).ourIP)) 21 22 ext, err := nat.GetExternalAddress() 23 if err != nil { 24 return nat, nil, nil, fmt.Errorf("external address error: %w", err) 25 } 26 logger.Info(fmt.Sprintf("External address: %v", ext)) 27 28 port, err := nat.AddPortMapping("tcp", extPort, intPort, "Tendermint UPnP Probe", 0) 29 if err != nil { 30 return nat, nil, ext, fmt.Errorf("port mapping error: %w", err) 31 } 32 logger.Info(fmt.Sprintf("Port mapping mapped: %v", port)) 33 34 // also run the listener, open for all remote addresses. 35 listener, err := net.Listen("tcp", fmt.Sprintf(":%v", intPort)) 36 if err != nil { 37 return nat, nil, ext, fmt.Errorf("error establishing listener: %w", err) 38 } 39 return nat, listener, ext, nil 40 } 41 42 func testHairpin(listener net.Listener, extAddr string, logger *slog.Logger) (supportsHairpin bool) { 43 // Listener 44 go func() { 45 inConn, err := listener.Accept() 46 if err != nil { 47 logger.Info(fmt.Sprintf("Listener.Accept() error: %v", err)) 48 return 49 } 50 logger.Info(fmt.Sprintf("Accepted incoming connection: %v -> %v", inConn.LocalAddr(), inConn.RemoteAddr())) 51 buf := make([]byte, 1024) 52 n, err := inConn.Read(buf) 53 if err != nil { 54 logger.Info(fmt.Sprintf("Incoming connection read error: %v", err)) 55 return 56 } 57 logger.Info(fmt.Sprintf("Incoming connection read %v bytes: %X", n, buf)) 58 if string(buf) == "test data" { 59 supportsHairpin = true 60 return 61 } 62 }() 63 64 // Establish outgoing 65 outConn, err := net.Dial("tcp", extAddr) 66 if err != nil { 67 logger.Info(fmt.Sprintf("Outgoing connection dial error: %v", err)) 68 return 69 } 70 71 n, err := outConn.Write([]byte("test data")) 72 if err != nil { 73 logger.Info(fmt.Sprintf("Outgoing connection write error: %v", err)) 74 return 75 } 76 logger.Info(fmt.Sprintf("Outgoing connection wrote %v bytes", n)) 77 78 // Wait for data receipt 79 time.Sleep(1 * time.Second) 80 return supportsHairpin 81 } 82 83 func Probe(logger *slog.Logger) (caps UPNPCapabilities, err error) { 84 logger.Info("Probing for UPnP!") 85 86 intPort, extPort := 8001, 8001 87 88 nat, listener, ext, err := makeUPNPListener(intPort, extPort, logger) 89 if err != nil { 90 return 91 } 92 caps.PortMapping = true 93 94 // Deferred cleanup 95 defer func() { 96 if err := nat.DeletePortMapping("tcp", intPort, extPort); err != nil { 97 logger.Error(fmt.Sprintf("Port mapping delete error: %v", err)) 98 } 99 if err := listener.Close(); err != nil { 100 logger.Error(fmt.Sprintf("Listener closing error: %v", err)) 101 } 102 }() 103 104 supportsHairpin := testHairpin(listener, fmt.Sprintf("%v:%v", ext, extPort), logger) 105 if supportsHairpin { 106 caps.Hairpin = true 107 } 108 109 return 110 }