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  }