nat.go 3.63 KB
Newer Older
Juan Batiz-Benet's avatar
Juan Batiz-Benet committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168
package nat

import (
	"fmt"
	"strconv"
	"strings"
	"time"

	ma "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr"
	manet "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/go-multiaddr-net"

	nat "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/fd/go-nat"
	goprocess "github.com/jbenet/go-ipfs/Godeps/_workspace/src/github.com/jbenet/goprocess"
	eventlog "github.com/jbenet/go-ipfs/thirdparty/eventlog"
)

var log = eventlog.Logger("nat")

const MappingDuration = time.Second * 60

func DiscoverGateway() nat.NAT {
	nat, err := nat.DiscoverGateway()
	if err != nil {
		log.Debug("DiscoverGateway error:", err)
		return nil
	}
	addr, err := nat.GetDeviceAddress()
	if err != nil {
		log.Debug("DiscoverGateway address error:", err)
	} else {
		log.Debug("DiscoverGateway address:", addr)
	}
	return nat
}

type Mapping interface {
	NAT() nat.NAT
	Protocol() string
	InternalPort() int
	ExternalPort() int
}

type mapping struct {
	// keeps republishing
	nat     nat.NAT
	proto   string
	intport int
	extport int
	proc    goprocess.Process
}

func (m *mapping) NAT() nat.NAT {
	return m.nat
}
func (m *mapping) Protocol() string {
	return m.proto
}
func (m *mapping) InternalPort() int {
	return m.intport
}
func (m *mapping) ExternalPort() int {
	return m.extport
}

// NewMapping attemps to construct a mapping on protocl and internal port
func NewMapping(nat nat.NAT, protocol string, internalPort int) (Mapping, error) {
	log.Debugf("Attempting port map: %s/%d", protocol, internalPort)
	eport, err := nat.AddPortMapping(protocol, internalPort, "http", MappingDuration)
	if err != nil {
		return nil, err
	}

	m := &mapping{
		nat:     nat,
		proto:   protocol,
		intport: internalPort,
		extport: eport,
	}

	m.proc = goprocess.Go(func(worker goprocess.Process) {
		for {
			select {
			case <-worker.Closing():
				return
			case <-time.After(MappingDuration / 3):
				eport, err := m.NAT().AddPortMapping(protocol, internalPort, "http", MappingDuration)
				if err != nil {
					log.Warningf("failed to renew port mapping: %s", err)
					continue
				}
				if eport != m.extport {
					log.Warningf("failed to renew same port mapping: ch %d -> %d", m.extport, eport)
				}
			}
		}
	})

	return m, nil
}

func (m *mapping) Close() error {
	return m.proc.Close()
}

func MapAddr(n nat.NAT, maddr ma.Multiaddr) (ma.Multiaddr, error) {
	if n == nil {
		return nil, fmt.Errorf("no nat available")
	}

	ip, err := n.GetExternalAddress()
	if err != nil {
		return nil, err
	}

	ipmaddr, err := manet.FromIP(ip)
	if err != nil {
		return nil, fmt.Errorf("error parsing ip")
	}

	network, addr, err := manet.DialArgs(maddr)
	if err != nil {
		return nil, fmt.Errorf("DialArgs failed on addr:", maddr.String())
	}

	switch network {
	case "tcp", "tcp4", "tcp6":
		network = "tcp"
	case "udp", "udp4", "udp6":
		network = "udp"
	default:
		return nil, fmt.Errorf("transport not supported by NAT: %s", network)
	}

	port := strings.Split(addr, ":")[1]
	intport, err := strconv.Atoi(port)
	if err != nil {
		return nil, err
	}

	m, err := NewMapping(n, "tcp", intport)
	if err != nil {
		return nil, err
	}

	tcp, err := ma.NewMultiaddr(fmt.Sprintf("/tcp/%d", m.ExternalPort()))
	if err != nil {
		return nil, err
	}

	maddr2 := ipmaddr.Encapsulate(tcp)
	log.Debugf("NAT Mapping: %s --> %s", maddr, maddr2)
	return maddr2, nil
}

func MapAddrs(addrs []ma.Multiaddr) []ma.Multiaddr {
	nat := DiscoverGateway()

	var advertise []ma.Multiaddr
	for _, maddr := range addrs {
		maddr2, err := MapAddr(nat, maddr)
		if err != nil || maddr2 == nil {
			log.Debug("failed to map addr:", maddr, err)
			continue
		}
		advertise = append(advertise, maddr2)
	}
	return advertise
}