aboutsummaryrefslogblamecommitdiffstats
path: root/p2p/nat/natpmp.go
blob: c2f9408914227a58b31e93c83679c46a1caf3eea (plain) (tree)
1
2
3
4
5
6
7
8
9
                                         
                                                
  
                                                                                  



                                                                              
                                                                             
                                                                 
                                                               


                                                                           
                                                                                  
 


















































































































                                                                                                            
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.

package nat

import (
    "fmt"
    "net"
    "strings"
    "time"

    "github.com/jackpal/go-nat-pmp"
)

// natPMPClient adapts the NAT-PMP protocol implementation so it conforms to
// the common interface.
type pmp struct {
    gw net.IP
    c  *natpmp.Client
}

func (n *pmp) String() string {
    return fmt.Sprintf("NAT-PMP(%v)", n.gw)
}

func (n *pmp) ExternalIP() (net.IP, error) {
    response, err := n.c.GetExternalAddress()
    if err != nil {
        return nil, err
    }
    return response.ExternalIPAddress[:], nil
}

func (n *pmp) AddMapping(protocol string, extport, intport int, name string, lifetime time.Duration) error {
    if lifetime <= 0 {
        return fmt.Errorf("lifetime must not be <= 0")
    }
    // Note order of port arguments is switched between our
    // AddMapping and the client's AddPortMapping.
    _, err := n.c.AddPortMapping(strings.ToLower(protocol), intport, extport, int(lifetime/time.Second))
    return err
}

func (n *pmp) DeleteMapping(protocol string, extport, intport int) (err error) {
    // To destroy a mapping, send an add-port with an internalPort of
    // the internal port to destroy, an external port of zero and a
    // time of zero.
    _, err = n.c.AddPortMapping(strings.ToLower(protocol), intport, 0, 0)
    return err
}

func discoverPMP() Interface {
    // run external address lookups on all potential gateways
    gws := potentialGateways()
    found := make(chan *pmp, len(gws))
    for i := range gws {
        gw := gws[i]
        go func() {
            c := natpmp.NewClient(gw)
            if _, err := c.GetExternalAddress(); err != nil {
                found <- nil
            } else {
                found <- &pmp{gw, c}
            }
        }()
    }
    // return the one that responds first.
    // discovery needs to be quick, so we stop caring about
    // any responses after a very short timeout.
    timeout := time.NewTimer(1 * time.Second)
    defer timeout.Stop()
    for _ = range gws {
        select {
        case c := <-found:
            if c != nil {
                return c
            }
        case <-timeout.C:
            return nil
        }
    }
    return nil
}

var (
    // LAN IP ranges
    _, lan10, _  = net.ParseCIDR("10.0.0.0/8")
    _, lan176, _ = net.ParseCIDR("172.16.0.0/12")
    _, lan192, _ = net.ParseCIDR("192.168.0.0/16")
)

// TODO: improve this. We currently assume that (on most networks)
// the router is X.X.X.1 in a local LAN range.
func potentialGateways() (gws []net.IP) {
    ifaces, err := net.Interfaces()
    if err != nil {
        return nil
    }
    for _, iface := range ifaces {
        ifaddrs, err := iface.Addrs()
        if err != nil {
            return gws
        }
        for _, addr := range ifaddrs {
            switch x := addr.(type) {
            case *net.IPNet:
                if lan10.Contains(x.IP) || lan176.Contains(x.IP) || lan192.Contains(x.IP) {
                    ip := x.IP.Mask(x.Mask).To4()
                    if ip != nil {
                        ip[3] = ip[3] | 0x01
                        gws = append(gws, ip)
                    }
                }
            }
        }
    }
    return gws
}