github.com/squaremo/docker@v1.3.2-0.20150516120342-42cfc9554972/daemon/networkdriver/portmapper/mapper.go (about) 1 package portmapper 2 3 import ( 4 "errors" 5 "fmt" 6 "net" 7 "sync" 8 9 "github.com/Sirupsen/logrus" 10 "github.com/docker/docker/daemon/networkdriver/portallocator" 11 "github.com/docker/docker/pkg/iptables" 12 ) 13 14 type mapping struct { 15 proto string 16 userlandProxy UserlandProxy 17 host net.Addr 18 container net.Addr 19 } 20 21 var NewProxy = NewProxyCommand 22 23 var ( 24 ErrUnknownBackendAddressType = errors.New("unknown container address type not supported") 25 ErrPortMappedForIP = errors.New("port is already mapped to ip") 26 ErrPortNotMapped = errors.New("port is not mapped") 27 ) 28 29 type PortMapper struct { 30 chain *iptables.Chain 31 32 // udp:ip:port 33 currentMappings map[string]*mapping 34 lock sync.Mutex 35 36 Allocator *portallocator.PortAllocator 37 } 38 39 func New() *PortMapper { 40 return NewWithPortAllocator(portallocator.New()) 41 } 42 43 func NewWithPortAllocator(allocator *portallocator.PortAllocator) *PortMapper { 44 return &PortMapper{ 45 currentMappings: make(map[string]*mapping), 46 Allocator: allocator, 47 } 48 } 49 50 func (pm *PortMapper) SetIptablesChain(c *iptables.Chain) { 51 pm.chain = c 52 } 53 54 func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) { 55 pm.lock.Lock() 56 defer pm.lock.Unlock() 57 58 var ( 59 m *mapping 60 proto string 61 allocatedHostPort int 62 ) 63 64 switch container.(type) { 65 case *net.TCPAddr: 66 proto = "tcp" 67 if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { 68 return nil, err 69 } 70 71 m = &mapping{ 72 proto: proto, 73 host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort}, 74 container: container, 75 } 76 77 if useProxy { 78 m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.TCPAddr).IP, container.(*net.TCPAddr).Port) 79 } 80 case *net.UDPAddr: 81 proto = "udp" 82 if allocatedHostPort, err = pm.Allocator.RequestPort(hostIP, proto, hostPort); err != nil { 83 return nil, err 84 } 85 86 m = &mapping{ 87 proto: proto, 88 host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort}, 89 container: container, 90 } 91 92 if useProxy { 93 m.userlandProxy = NewProxy(proto, hostIP, allocatedHostPort, container.(*net.UDPAddr).IP, container.(*net.UDPAddr).Port) 94 } 95 default: 96 return nil, ErrUnknownBackendAddressType 97 } 98 99 // release the allocated port on any further error during return. 100 defer func() { 101 if err != nil { 102 pm.Allocator.ReleasePort(hostIP, proto, allocatedHostPort) 103 } 104 }() 105 106 key := getKey(m.host) 107 if _, exists := pm.currentMappings[key]; exists { 108 return nil, ErrPortMappedForIP 109 } 110 111 containerIP, containerPort := getIPAndPort(m.container) 112 if err := pm.forward(iptables.Append, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { 113 return nil, err 114 } 115 116 cleanup := func() error { 117 // need to undo the iptables rules before we return 118 if m.userlandProxy != nil { 119 m.userlandProxy.Stop() 120 } 121 pm.forward(iptables.Delete, m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) 122 if err := pm.Allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { 123 return err 124 } 125 126 return nil 127 } 128 129 if m.userlandProxy != nil { 130 if err := m.userlandProxy.Start(); err != nil { 131 if err := cleanup(); err != nil { 132 return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) 133 } 134 return nil, err 135 } 136 } 137 138 pm.currentMappings[key] = m 139 return m.host, nil 140 } 141 142 // re-apply all port mappings 143 func (pm *PortMapper) ReMapAll() { 144 logrus.Debugln("Re-applying all port mappings.") 145 for _, data := range pm.currentMappings { 146 containerIP, containerPort := getIPAndPort(data.container) 147 hostIP, hostPort := getIPAndPort(data.host) 148 if err := pm.forward(iptables.Append, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 149 logrus.Errorf("Error on iptables add: %s", err) 150 } 151 } 152 } 153 154 func (pm *PortMapper) Unmap(host net.Addr) error { 155 pm.lock.Lock() 156 defer pm.lock.Unlock() 157 158 key := getKey(host) 159 data, exists := pm.currentMappings[key] 160 if !exists { 161 return ErrPortNotMapped 162 } 163 164 if data.userlandProxy != nil { 165 data.userlandProxy.Stop() 166 } 167 168 delete(pm.currentMappings, key) 169 170 containerIP, containerPort := getIPAndPort(data.container) 171 hostIP, hostPort := getIPAndPort(data.host) 172 if err := pm.forward(iptables.Delete, data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 173 logrus.Errorf("Error on iptables delete: %s", err) 174 } 175 176 switch a := host.(type) { 177 case *net.TCPAddr: 178 return pm.Allocator.ReleasePort(a.IP, "tcp", a.Port) 179 case *net.UDPAddr: 180 return pm.Allocator.ReleasePort(a.IP, "udp", a.Port) 181 } 182 return nil 183 } 184 185 func getKey(a net.Addr) string { 186 switch t := a.(type) { 187 case *net.TCPAddr: 188 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp") 189 case *net.UDPAddr: 190 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp") 191 } 192 return "" 193 } 194 195 func getIPAndPort(a net.Addr) (net.IP, int) { 196 switch t := a.(type) { 197 case *net.TCPAddr: 198 return t.IP, t.Port 199 case *net.UDPAddr: 200 return t.IP, t.Port 201 } 202 return nil, 0 203 } 204 205 func (pm *PortMapper) forward(action iptables.Action, proto string, sourceIP net.IP, sourcePort int, containerIP string, containerPort int) error { 206 if pm.chain == nil { 207 return nil 208 } 209 return pm.chain.Forward(action, sourceIP, sourcePort, proto, containerIP, containerPort) 210 }