197 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			197 lines
		
	
	
		
			4.2 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								package p2p
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"math/rand"
							 | 
						||
| 
								 | 
							
									"net"
							 | 
						||
| 
								 | 
							
									"strconv"
							 | 
						||
| 
								 | 
							
									"time"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const (
							 | 
						||
| 
								 | 
							
									DialerTimeout             = 180 //seconds
							 | 
						||
| 
								 | 
							
									KeepAlivePeriod           = 60  //minutes
							 | 
						||
| 
								 | 
							
									portMappingUpdateInterval = 900 // seconds = 15 mins
							 | 
						||
| 
								 | 
							
									upnpDiscoverAttempts      = 3
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Dialer is not an interface in net, so we define one
							 | 
						||
| 
								 | 
							
								// *net.Dialer conforms to this
							 | 
						||
| 
								 | 
							
								type Dialer interface {
							 | 
						||
| 
								 | 
							
									Dial(network, address string) (net.Conn, error)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type Network interface {
							 | 
						||
| 
								 | 
							
									Start() error
							 | 
						||
| 
								 | 
							
									Listener(net.Addr) (net.Listener, error)
							 | 
						||
| 
								 | 
							
									Dialer(net.Addr) (Dialer, error)
							 | 
						||
| 
								 | 
							
									NewAddr(string, int) (addr net.Addr, err error)
							 | 
						||
| 
								 | 
							
									ParseAddr(string) (addr net.Addr, err error)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type NAT interface {
							 | 
						||
| 
								 | 
							
									GetExternalAddress() (addr net.IP, err error)
							 | 
						||
| 
								 | 
							
									AddPortMapping(protocol string, externalPort, internalPort int, description string, timeout int) (mappedExternalPort int, err error)
							 | 
						||
| 
								 | 
							
									DeletePortMapping(protocol string, externalPort, internalPort int) (err error)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type TCPNetwork struct {
							 | 
						||
| 
								 | 
							
									nat     NAT
							 | 
						||
| 
								 | 
							
									natType NATType
							 | 
						||
| 
								 | 
							
									quit    chan chan bool
							 | 
						||
| 
								 | 
							
									ports   chan string
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type NATType int
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const (
							 | 
						||
| 
								 | 
							
									NONE = iota
							 | 
						||
| 
								 | 
							
									UPNP
							 | 
						||
| 
								 | 
							
									PMP
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								const (
							 | 
						||
| 
								 | 
							
									portMappingTimeout = 1200 // 20 mins
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func NewTCPNetwork(natType NATType) (net *TCPNetwork) {
							 | 
						||
| 
								 | 
							
									return &TCPNetwork{
							 | 
						||
| 
								 | 
							
										natType: natType,
							 | 
						||
| 
								 | 
							
										ports:   make(chan string),
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) Dialer(addr net.Addr) (Dialer, error) {
							 | 
						||
| 
								 | 
							
									return &net.Dialer{
							 | 
						||
| 
								 | 
							
										Timeout: DialerTimeout * time.Second,
							 | 
						||
| 
								 | 
							
										// KeepAlive: KeepAlivePeriod * time.Minute,
							 | 
						||
| 
								 | 
							
										LocalAddr: addr,
							 | 
						||
| 
								 | 
							
									}, nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) Listener(addr net.Addr) (net.Listener, error) {
							 | 
						||
| 
								 | 
							
									if self.natType == UPNP {
							 | 
						||
| 
								 | 
							
										_, port, _ := net.SplitHostPort(addr.String())
							 | 
						||
| 
								 | 
							
										if self.quit == nil {
							 | 
						||
| 
								 | 
							
											self.quit = make(chan chan bool)
							 | 
						||
| 
								 | 
							
											go self.updatePortMappings()
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										self.ports <- port
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return net.Listen(addr.Network(), addr.String())
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) Start() (err error) {
							 | 
						||
| 
								 | 
							
									switch self.natType {
							 | 
						||
| 
								 | 
							
									case NONE:
							 | 
						||
| 
								 | 
							
									case UPNP:
							 | 
						||
| 
								 | 
							
										nat, uerr := upnpDiscover(upnpDiscoverAttempts)
							 | 
						||
| 
								 | 
							
										if uerr != nil {
							 | 
						||
| 
								 | 
							
											err = fmt.Errorf("UPNP failed: ", uerr)
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											self.nat = nat
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									case PMP:
							 | 
						||
| 
								 | 
							
										err = fmt.Errorf("PMP not implemented")
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										err = fmt.Errorf("Invalid NAT type: %v", self.natType)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) Stop() {
							 | 
						||
| 
								 | 
							
									q := make(chan bool)
							 | 
						||
| 
								 | 
							
									self.quit <- q
							 | 
						||
| 
								 | 
							
									<-q
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) addPortMapping(lport int) (err error) {
							 | 
						||
| 
								 | 
							
									_, err = self.nat.AddPortMapping("TCP", lport, lport, "p2p listen port", portMappingTimeout)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										logger.Errorf("unable to add port mapping on %v: %v", lport, err)
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										logger.Debugf("succesfully added port mapping on %v", lport)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) updatePortMappings() {
							 | 
						||
| 
								 | 
							
									timer := time.NewTimer(portMappingUpdateInterval * time.Second)
							 | 
						||
| 
								 | 
							
									lports := []int{}
							 | 
						||
| 
								 | 
							
								out:
							 | 
						||
| 
								 | 
							
									for {
							 | 
						||
| 
								 | 
							
										select {
							 | 
						||
| 
								 | 
							
										case port := <-self.ports:
							 | 
						||
| 
								 | 
							
											int64lport, _ := strconv.ParseInt(port, 10, 16)
							 | 
						||
| 
								 | 
							
											lport := int(int64lport)
							 | 
						||
| 
								 | 
							
											if err := self.addPortMapping(lport); err != nil {
							 | 
						||
| 
								 | 
							
												lports = append(lports, lport)
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										case <-timer.C:
							 | 
						||
| 
								 | 
							
											for lport := range lports {
							 | 
						||
| 
								 | 
							
												if err := self.addPortMapping(lport); err != nil {
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										case errc := <-self.quit:
							 | 
						||
| 
								 | 
							
											errc <- true
							 | 
						||
| 
								 | 
							
											break out
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									timer.Stop()
							 | 
						||
| 
								 | 
							
									for lport := range lports {
							 | 
						||
| 
								 | 
							
										if err := self.nat.DeletePortMapping("TCP", lport, lport); err != nil {
							 | 
						||
| 
								 | 
							
											logger.Debugf("unable to remove port mapping on %v: %v", lport, err)
							 | 
						||
| 
								 | 
							
										} else {
							 | 
						||
| 
								 | 
							
											logger.Debugf("succesfully removed port mapping on %v", lport)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) NewAddr(host string, port int) (net.Addr, error) {
							 | 
						||
| 
								 | 
							
									ip, err := self.lookupIP(host)
							 | 
						||
| 
								 | 
							
									if err == nil {
							 | 
						||
| 
								 | 
							
										return &net.TCPAddr{
							 | 
						||
| 
								 | 
							
											IP:   ip,
							 | 
						||
| 
								 | 
							
											Port: port,
							 | 
						||
| 
								 | 
							
										}, nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil, err
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (self *TCPNetwork) ParseAddr(address string) (net.Addr, error) {
							 | 
						||
| 
								 | 
							
									host, port, err := net.SplitHostPort(address)
							 | 
						||
| 
								 | 
							
									if err == nil {
							 | 
						||
| 
								 | 
							
										iport, _ := strconv.Atoi(port)
							 | 
						||
| 
								 | 
							
										addr, e := self.NewAddr(host, iport)
							 | 
						||
| 
								 | 
							
										return addr, e
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil, err
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func (*TCPNetwork) lookupIP(host string) (ip net.IP, err error) {
							 | 
						||
| 
								 | 
							
									if ip = net.ParseIP(host); ip != nil {
							 | 
						||
| 
								 | 
							
										return
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									var ips []net.IP
							 | 
						||
| 
								 | 
							
									ips, err = net.LookupIP(host)
							 | 
						||
| 
								 | 
							
									if err != nil {
							 | 
						||
| 
								 | 
							
										logger.Warnln(err)
							 | 
						||
| 
								 | 
							
										return
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if len(ips) == 0 {
							 | 
						||
| 
								 | 
							
										err = fmt.Errorf("No IP addresses available for %v", host)
							 | 
						||
| 
								 | 
							
										logger.Warnln(err)
							 | 
						||
| 
								 | 
							
										return
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if len(ips) > 1 {
							 | 
						||
| 
								 | 
							
										// Pick a random IP address, simulating round-robin DNS.
							 | 
						||
| 
								 | 
							
										rand.Seed(time.Now().UTC().UnixNano())
							 | 
						||
| 
								 | 
							
										ip = ips[rand.Intn(len(ips))]
							 | 
						||
| 
								 | 
							
									} else {
							 | 
						||
| 
								 | 
							
										ip = ips[0]
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return
							 | 
						||
| 
								 | 
							
								}
							 |