github.com/devwanda/aphelion-staking@v0.33.9/p2p/upnp/probe.go (about)

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