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
 | |
| }
 |