144 lines
3.4 KiB
Go
144 lines
3.4 KiB
Go
|
// Copyright (C) 2015 The Syncthing Authors.
|
||
|
//
|
||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||
|
// You can obtain one at https://mozilla.org/MPL/2.0/.
|
||
|
|
||
|
package upnp
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"net"
|
||
|
"sync"
|
||
|
"time"
|
||
|
)
|
||
|
|
||
|
type MappingChangeSubscriber func(*Mapping, []Address, []Address)
|
||
|
|
||
|
type Mapping struct {
|
||
|
protocol Protocol
|
||
|
address Address
|
||
|
|
||
|
extAddresses map[string]Address // NAT ID -> Address
|
||
|
expires time.Time
|
||
|
subscribers []MappingChangeSubscriber
|
||
|
mut sync.RWMutex
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) setAddress(id string, address Address) {
|
||
|
m.mut.Lock()
|
||
|
if existing, ok := m.extAddresses[id]; !ok || !existing.Equal(address) {
|
||
|
log.Infof("New NAT port mapping: external %s address %s to local address %s.", m.protocol, address, m.address)
|
||
|
m.extAddresses[id] = address
|
||
|
}
|
||
|
m.mut.Unlock()
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) removeAddress(id string) {
|
||
|
m.mut.Lock()
|
||
|
addr, ok := m.extAddresses[id]
|
||
|
if ok {
|
||
|
log.Infof("Removing NAT port mapping: external %s address %s, NAT %s is no longer available.", m.protocol, addr, id)
|
||
|
delete(m.extAddresses, id)
|
||
|
}
|
||
|
m.mut.Unlock()
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) clearAddresses() {
|
||
|
m.mut.Lock()
|
||
|
var removed []Address
|
||
|
for id, addr := range m.extAddresses {
|
||
|
log.Infof("Clearing mapping %s: ID: %s Address: %s", m, id, addr)
|
||
|
removed = append(removed, addr)
|
||
|
delete(m.extAddresses, id)
|
||
|
}
|
||
|
m.expires = time.Time{}
|
||
|
m.mut.Unlock()
|
||
|
if len(removed) > 0 {
|
||
|
m.notify(nil, removed)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) notify(added, removed []Address) {
|
||
|
m.mut.RLock()
|
||
|
for _, subscriber := range m.subscribers {
|
||
|
subscriber(m, added, removed)
|
||
|
}
|
||
|
m.mut.RUnlock()
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) addressMap() map[string]Address {
|
||
|
m.mut.RLock()
|
||
|
addrMap := m.extAddresses
|
||
|
m.mut.RUnlock()
|
||
|
return addrMap
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) Protocol() Protocol {
|
||
|
return m.protocol
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) Address() Address {
|
||
|
return m.address
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) ExternalAddresses() []Address {
|
||
|
m.mut.RLock()
|
||
|
addrs := make([]Address, 0, len(m.extAddresses))
|
||
|
for _, addr := range m.extAddresses {
|
||
|
addrs = append(addrs, addr)
|
||
|
}
|
||
|
m.mut.RUnlock()
|
||
|
return addrs
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) OnChanged(subscribed MappingChangeSubscriber) {
|
||
|
m.mut.Lock()
|
||
|
m.subscribers = append(m.subscribers, subscribed)
|
||
|
m.mut.Unlock()
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) String() string {
|
||
|
return fmt.Sprintf("%s %s", m.protocol, m.address)
|
||
|
}
|
||
|
|
||
|
func (m *Mapping) GoString() string {
|
||
|
return m.String()
|
||
|
}
|
||
|
|
||
|
// Checks if the mappings local IP address matches the IP address of the gateway
|
||
|
// For example, if we are explicitly listening on 192.168.0.12, there is no
|
||
|
// point trying to acquire a mapping on a gateway to which the local IP is
|
||
|
// 10.0.0.1. Fallback to true if any of the IPs is not there.
|
||
|
func (m *Mapping) validGateway(ip net.IP) bool {
|
||
|
if m.address.IP == nil || ip == nil || m.address.IP.IsUnspecified() || ip.IsUnspecified() {
|
||
|
return true
|
||
|
}
|
||
|
return m.address.IP.Equal(ip)
|
||
|
}
|
||
|
|
||
|
// Address is essentially net.TCPAddr yet is more general, and has a few helper
|
||
|
// methods which reduce boilerplate code.
|
||
|
type Address struct {
|
||
|
IP net.IP
|
||
|
Port int
|
||
|
}
|
||
|
|
||
|
func (a Address) Equal(b Address) bool {
|
||
|
return a.Port == b.Port && a.IP.Equal(b.IP)
|
||
|
}
|
||
|
|
||
|
func (a Address) String() string {
|
||
|
var ipStr string
|
||
|
if a.IP == nil {
|
||
|
ipStr = net.IPv4zero.String()
|
||
|
} else {
|
||
|
ipStr = a.IP.String()
|
||
|
}
|
||
|
return net.JoinHostPort(ipStr, fmt.Sprintf("%d", a.Port))
|
||
|
}
|
||
|
|
||
|
func (a Address) GoString() string {
|
||
|
return a.String()
|
||
|
}
|