2014-01-28 18:42:46 -05:00
|
|
|
package portmapper
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"sync"
|
2014-06-19 17:33:51 -04:00
|
|
|
|
2014-07-24 18:19:50 -04:00
|
|
|
"github.com/docker/docker/daemon/networkdriver/portallocator"
|
|
|
|
"github.com/docker/docker/pkg/iptables"
|
2014-09-09 08:47:25 -04:00
|
|
|
"github.com/docker/docker/pkg/log"
|
2014-01-28 18:42:46 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
type mapping struct {
|
|
|
|
proto string
|
2014-08-12 18:04:00 -04:00
|
|
|
userlandProxy UserlandProxy
|
2014-01-28 18:42:46 -05:00
|
|
|
host net.Addr
|
|
|
|
container net.Addr
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
chain *iptables.Chain
|
|
|
|
lock sync.Mutex
|
|
|
|
|
|
|
|
// udp:ip:port
|
|
|
|
currentMappings = make(map[string]*mapping)
|
2014-08-12 18:04:00 -04:00
|
|
|
|
|
|
|
NewProxy = NewProxyCommand
|
2014-01-28 18:42:46 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
ErrUnknownBackendAddressType = errors.New("unknown container address type not supported")
|
|
|
|
ErrPortMappedForIP = errors.New("port is already mapped to ip")
|
|
|
|
ErrPortNotMapped = errors.New("port is not mapped")
|
|
|
|
)
|
|
|
|
|
2014-01-28 19:28:32 -05:00
|
|
|
func SetIptablesChain(c *iptables.Chain) {
|
2014-01-28 18:42:46 -05:00
|
|
|
chain = c
|
|
|
|
}
|
|
|
|
|
2014-07-18 10:57:32 -04:00
|
|
|
func Map(container net.Addr, hostIP net.IP, hostPort int) (host net.Addr, err error) {
|
2014-01-28 18:42:46 -05:00
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
2014-06-19 17:33:51 -04:00
|
|
|
var (
|
|
|
|
m *mapping
|
|
|
|
proto string
|
|
|
|
allocatedHostPort int
|
2014-08-12 18:04:00 -04:00
|
|
|
proxy UserlandProxy
|
2014-06-19 17:33:51 -04:00
|
|
|
)
|
|
|
|
|
2014-01-28 18:42:46 -05:00
|
|
|
switch container.(type) {
|
|
|
|
case *net.TCPAddr:
|
2014-06-19 17:33:51 -04:00
|
|
|
proto = "tcp"
|
|
|
|
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
2014-06-25 16:17:20 -04:00
|
|
|
return nil, err
|
2014-06-19 17:33:51 -04:00
|
|
|
}
|
2014-08-12 18:04:00 -04:00
|
|
|
|
2014-01-28 18:42:46 -05:00
|
|
|
m = &mapping{
|
2014-06-19 17:33:51 -04:00
|
|
|
proto: proto,
|
|
|
|
host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort},
|
2014-01-28 18:42:46 -05:00
|
|
|
container: container,
|
|
|
|
}
|
2014-08-12 18:04:00 -04:00
|
|
|
|
|
|
|
proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port)
|
2014-01-28 18:42:46 -05:00
|
|
|
case *net.UDPAddr:
|
2014-06-19 17:33:51 -04:00
|
|
|
proto = "udp"
|
|
|
|
if allocatedHostPort, err = portallocator.RequestPort(hostIP, proto, hostPort); err != nil {
|
2014-06-25 16:17:20 -04:00
|
|
|
return nil, err
|
2014-06-19 17:33:51 -04:00
|
|
|
}
|
2014-08-12 18:04:00 -04:00
|
|
|
|
2014-01-28 18:42:46 -05:00
|
|
|
m = &mapping{
|
2014-06-19 17:33:51 -04:00
|
|
|
proto: proto,
|
|
|
|
host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort},
|
2014-01-28 18:42:46 -05:00
|
|
|
container: container,
|
|
|
|
}
|
2014-08-12 18:04:00 -04:00
|
|
|
|
|
|
|
proxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port)
|
2014-01-28 18:42:46 -05:00
|
|
|
default:
|
2014-07-18 10:57:32 -04:00
|
|
|
return nil, ErrUnknownBackendAddressType
|
2014-01-28 18:42:46 -05:00
|
|
|
}
|
|
|
|
|
2014-07-18 10:57:32 -04:00
|
|
|
// release the allocated port on any further error during return.
|
|
|
|
defer func() {
|
|
|
|
if err != nil {
|
|
|
|
portallocator.ReleasePort(hostIP, proto, allocatedHostPort)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
2014-01-28 18:42:46 -05:00
|
|
|
key := getKey(m.host)
|
|
|
|
if _, exists := currentMappings[key]; exists {
|
2014-07-18 10:57:32 -04:00
|
|
|
return nil, ErrPortMappedForIP
|
2014-01-28 18:42:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
containerIP, containerPort := getIPAndPort(m.container)
|
2014-06-19 17:33:51 -04:00
|
|
|
if err := forward(iptables.Add, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil {
|
2014-06-26 03:09:19 -04:00
|
|
|
return nil, err
|
2014-01-28 18:42:46 -05:00
|
|
|
}
|
|
|
|
|
2014-09-07 15:33:51 -04:00
|
|
|
cleanup := func() error {
|
2014-06-25 16:17:20 -04:00
|
|
|
// need to undo the iptables rules before we return
|
2014-09-07 15:33:51 -04:00
|
|
|
proxy.Stop()
|
|
|
|
forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort)
|
|
|
|
if err := portallocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-08-12 18:04:00 -04:00
|
|
|
|
2014-09-07 15:33:51 -04:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := proxy.Start(); err != nil {
|
|
|
|
if err := cleanup(); err != nil {
|
|
|
|
return nil, fmt.Errorf("Error during port allocation cleanup: %v", err)
|
|
|
|
}
|
2014-09-08 06:22:50 -04:00
|
|
|
return nil, err
|
2014-01-28 18:42:46 -05:00
|
|
|
}
|
2014-09-08 06:22:50 -04:00
|
|
|
m.userlandProxy = proxy
|
|
|
|
currentMappings[key] = m
|
2014-06-19 17:33:51 -04:00
|
|
|
return m.host, nil
|
2014-01-28 18:42:46 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
func Unmap(host net.Addr) error {
|
|
|
|
lock.Lock()
|
|
|
|
defer lock.Unlock()
|
|
|
|
|
|
|
|
key := getKey(host)
|
|
|
|
data, exists := currentMappings[key]
|
|
|
|
if !exists {
|
|
|
|
return ErrPortNotMapped
|
|
|
|
}
|
|
|
|
|
2014-08-12 18:04:00 -04:00
|
|
|
data.userlandProxy.Stop()
|
|
|
|
|
2014-01-28 18:42:46 -05:00
|
|
|
delete(currentMappings, key)
|
|
|
|
|
|
|
|
containerIP, containerPort := getIPAndPort(data.container)
|
|
|
|
hostIP, hostPort := getIPAndPort(data.host)
|
|
|
|
if err := forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil {
|
2014-09-09 08:47:25 -04:00
|
|
|
log.Errorf("Error on iptables delete: %s", err)
|
2014-01-28 18:42:46 -05:00
|
|
|
}
|
2014-06-19 17:33:51 -04:00
|
|
|
|
|
|
|
switch a := host.(type) {
|
|
|
|
case *net.TCPAddr:
|
2014-08-27 12:24:27 -04:00
|
|
|
return portallocator.ReleasePort(a.IP, "tcp", a.Port)
|
2014-06-19 17:33:51 -04:00
|
|
|
case *net.UDPAddr:
|
2014-08-27 12:24:27 -04:00
|
|
|
return portallocator.ReleasePort(a.IP, "udp", a.Port)
|
2014-06-19 17:33:51 -04:00
|
|
|
}
|
2014-01-28 18:42:46 -05:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getKey(a net.Addr) string {
|
|
|
|
switch t := a.(type) {
|
|
|
|
case *net.TCPAddr:
|
|
|
|
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp")
|
|
|
|
case *net.UDPAddr:
|
|
|
|
return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp")
|
|
|
|
}
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
func getIPAndPort(a net.Addr) (net.IP, int) {
|
|
|
|
switch t := a.(type) {
|
|
|
|
case *net.TCPAddr:
|
|
|
|
return t.IP, t.Port
|
|
|
|
case *net.UDPAddr:
|
|
|
|
return t.IP, t.Port
|
|
|
|
}
|
|
|
|
return nil, 0
|
|
|
|
}
|
|
|
|
|
|
|
|
func forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error {
|
|
|
|
if chain == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort)
|
|
|
|
}
|