github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/libnetwork/portmapper/mapper.go (about) 1 package portmapper 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "net" 8 9 "github.com/Prakhar-Agarwal-byte/moby/libnetwork/portallocator" 10 "github.com/containerd/log" 11 "github.com/ishidawataru/sctp" 12 ) 13 14 type mapping struct { 15 proto string 16 userlandProxy userlandProxy 17 host net.Addr 18 container net.Addr 19 } 20 21 // newProxy is used to mock out the proxy server in tests 22 var newProxy = newProxyCommand 23 24 var ( 25 // ErrUnknownBackendAddressType refers to an unknown container or unsupported address type 26 ErrUnknownBackendAddressType = errors.New("unknown container address type not supported") 27 // ErrPortMappedForIP refers to a port already mapped to an ip address 28 ErrPortMappedForIP = errors.New("port is already mapped to ip") 29 // ErrPortNotMapped refers to an unmapped port 30 ErrPortNotMapped = errors.New("port is not mapped") 31 // ErrSCTPAddrNoIP refers to a SCTP address without IP address. 32 ErrSCTPAddrNoIP = errors.New("sctp address does not contain any IP address") 33 ) 34 35 // New returns a new instance of PortMapper 36 func New() *PortMapper { 37 return NewWithPortAllocator(portallocator.Get(), "") 38 } 39 40 // NewWithPortAllocator returns a new instance of PortMapper which will use the specified PortAllocator 41 func NewWithPortAllocator(allocator *portallocator.PortAllocator, proxyPath string) *PortMapper { 42 return &PortMapper{ 43 currentMappings: make(map[string]*mapping), 44 allocator: allocator, 45 proxyPath: proxyPath, 46 } 47 } 48 49 // Map maps the specified container transport address to the host's network address and transport port 50 func (pm *PortMapper) Map(container net.Addr, hostIP net.IP, hostPort int, useProxy bool) (host net.Addr, err error) { 51 return pm.MapRange(container, hostIP, hostPort, hostPort, useProxy) 52 } 53 54 // MapRange maps the specified container transport address to the host's network address and transport port range 55 func (pm *PortMapper) MapRange(container net.Addr, hostIP net.IP, hostPortStart, hostPortEnd int, useProxy bool) (host net.Addr, err error) { 56 pm.lock.Lock() 57 defer pm.lock.Unlock() 58 59 var ( 60 m *mapping 61 proto string 62 allocatedHostPort int 63 ) 64 65 switch t := container.(type) { 66 case *net.TCPAddr: 67 proto = "tcp" 68 if allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil { 69 return nil, err 70 } 71 72 m = &mapping{ 73 proto: proto, 74 host: &net.TCPAddr{IP: hostIP, Port: allocatedHostPort}, 75 container: container, 76 } 77 78 if useProxy { 79 m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath) 80 if err != nil { 81 return nil, err 82 } 83 } else { 84 m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort) 85 if err != nil { 86 return nil, err 87 } 88 } 89 case *net.UDPAddr: 90 proto = "udp" 91 if allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil { 92 return nil, err 93 } 94 95 m = &mapping{ 96 proto: proto, 97 host: &net.UDPAddr{IP: hostIP, Port: allocatedHostPort}, 98 container: container, 99 } 100 101 if useProxy { 102 m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, t.IP, t.Port, pm.proxyPath) 103 if err != nil { 104 return nil, err 105 } 106 } else { 107 m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort) 108 if err != nil { 109 return nil, err 110 } 111 } 112 case *sctp.SCTPAddr: 113 proto = "sctp" 114 if allocatedHostPort, err = pm.allocator.RequestPortInRange(hostIP, proto, hostPortStart, hostPortEnd); err != nil { 115 return nil, err 116 } 117 118 m = &mapping{ 119 proto: proto, 120 host: &sctp.SCTPAddr{IPAddrs: []net.IPAddr{{IP: hostIP}}, Port: allocatedHostPort}, 121 container: container, 122 } 123 124 if useProxy { 125 sctpAddr := container.(*sctp.SCTPAddr) 126 if len(sctpAddr.IPAddrs) == 0 { 127 return nil, ErrSCTPAddrNoIP 128 } 129 m.userlandProxy, err = newProxy(proto, hostIP, allocatedHostPort, sctpAddr.IPAddrs[0].IP, sctpAddr.Port, pm.proxyPath) 130 if err != nil { 131 return nil, err 132 } 133 } else { 134 m.userlandProxy, err = newDummyProxy(proto, hostIP, allocatedHostPort) 135 if err != nil { 136 return nil, err 137 } 138 } 139 default: 140 return nil, ErrUnknownBackendAddressType 141 } 142 143 // release the allocated port on any further error during return. 144 defer func() { 145 if err != nil { 146 pm.allocator.ReleasePort(hostIP, proto, allocatedHostPort) 147 } 148 }() 149 150 key := getKey(m.host) 151 if _, exists := pm.currentMappings[key]; exists { 152 return nil, ErrPortMappedForIP 153 } 154 155 containerIP, containerPort := getIPAndPort(m.container) 156 if err := pm.AppendForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort); err != nil { 157 return nil, err 158 } 159 160 cleanup := func() error { 161 // need to undo the iptables rules before we return 162 m.userlandProxy.Stop() 163 pm.DeleteForwardingTableEntry(m.proto, hostIP, allocatedHostPort, containerIP.String(), containerPort) 164 if err := pm.allocator.ReleasePort(hostIP, m.proto, allocatedHostPort); err != nil { 165 return err 166 } 167 168 return nil 169 } 170 171 if err := m.userlandProxy.Start(); err != nil { 172 if err := cleanup(); err != nil { 173 return nil, fmt.Errorf("Error during port allocation cleanup: %v", err) 174 } 175 return nil, err 176 } 177 178 pm.currentMappings[key] = m 179 return m.host, nil 180 } 181 182 // Unmap removes stored mapping for the specified host transport address 183 func (pm *PortMapper) Unmap(host net.Addr) error { 184 pm.lock.Lock() 185 defer pm.lock.Unlock() 186 187 key := getKey(host) 188 data, exists := pm.currentMappings[key] 189 if !exists { 190 return ErrPortNotMapped 191 } 192 193 if data.userlandProxy != nil { 194 data.userlandProxy.Stop() 195 } 196 197 delete(pm.currentMappings, key) 198 199 containerIP, containerPort := getIPAndPort(data.container) 200 hostIP, hostPort := getIPAndPort(data.host) 201 if err := pm.DeleteForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 202 log.G(context.TODO()).Errorf("Error on iptables delete: %s", err) 203 } 204 205 switch a := host.(type) { 206 case *net.TCPAddr: 207 return pm.allocator.ReleasePort(a.IP, "tcp", a.Port) 208 case *net.UDPAddr: 209 return pm.allocator.ReleasePort(a.IP, "udp", a.Port) 210 case *sctp.SCTPAddr: 211 if len(a.IPAddrs) == 0 { 212 return ErrSCTPAddrNoIP 213 } 214 return pm.allocator.ReleasePort(a.IPAddrs[0].IP, "sctp", a.Port) 215 } 216 return ErrUnknownBackendAddressType 217 } 218 219 // ReMapAll re-applies all port mappings 220 func (pm *PortMapper) ReMapAll() { 221 pm.lock.Lock() 222 defer pm.lock.Unlock() 223 log.G(context.TODO()).Debugln("Re-applying all port mappings.") 224 for _, data := range pm.currentMappings { 225 containerIP, containerPort := getIPAndPort(data.container) 226 hostIP, hostPort := getIPAndPort(data.host) 227 if err := pm.AppendForwardingTableEntry(data.proto, hostIP, hostPort, containerIP.String(), containerPort); err != nil { 228 log.G(context.TODO()).Errorf("Error on iptables add: %s", err) 229 } 230 } 231 } 232 233 func getKey(a net.Addr) string { 234 switch t := a.(type) { 235 case *net.TCPAddr: 236 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "tcp") 237 case *net.UDPAddr: 238 return fmt.Sprintf("%s:%d/%s", t.IP.String(), t.Port, "udp") 239 case *sctp.SCTPAddr: 240 if len(t.IPAddrs) == 0 { 241 log.G(context.TODO()).Error(ErrSCTPAddrNoIP) 242 return "" 243 } 244 return fmt.Sprintf("%s:%d/%s", t.IPAddrs[0].IP.String(), t.Port, "sctp") 245 } 246 return "" 247 } 248 249 func getIPAndPort(a net.Addr) (net.IP, int) { 250 switch t := a.(type) { 251 case *net.TCPAddr: 252 return t.IP, t.Port 253 case *net.UDPAddr: 254 return t.IP, t.Port 255 case *sctp.SCTPAddr: 256 if len(t.IPAddrs) == 0 { 257 log.G(context.TODO()).Error(ErrSCTPAddrNoIP) 258 return nil, 0 259 } 260 return t.IPAddrs[0].IP, t.Port 261 } 262 return nil, 0 263 }