forked from kubernetes-sigs/cloud-provider-kind
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
allow to reach ports from host on mac
This is a pretty simple solution with some limitations, it uses the existing port forwarding capabilities of docker to expose the loadbalancer ports on the host, and then adds the same IP of the container to the loopback address and start proxying from user space the service ports on the service address to the portmapped ports. Limitations: - Service port changes are not updated as docker does not dynamically update forwarded ports - Kind binary needs permissions to add IP address to interfaces and to listen on privileged ports
- Loading branch information
Showing
6 changed files
with
255 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
package loadbalancer | ||
|
||
import ( | ||
"fmt" | ||
"io" | ||
"net" | ||
"os/exec" | ||
"sync" | ||
|
||
"k8s.io/klog/v2" | ||
|
||
"sigs.k8s.io/cloud-provider-kind/pkg/container" | ||
) | ||
|
||
const ( | ||
ifaceName = "lo0" | ||
) | ||
|
||
type tunnelManager struct { | ||
mu sync.Mutex | ||
tunnels map[string]map[string]*tunnel // first key is the service namespace/name second key is the servicePort | ||
} | ||
|
||
func NewTunnelManager() *tunnelManager { | ||
t := &tunnelManager{ | ||
tunnels: map[string]map[string]*tunnel{}, | ||
} | ||
return t | ||
} | ||
|
||
func (t *tunnelManager) setupTunnels(containerName string) error { | ||
// get the portmapping from the container and its internal IPs and forward them | ||
// 1. Create the fake IP on the tunnel interface | ||
// 2. Capture the traffic directed to that IP port and forward to the exposed port in the host | ||
portmaps, err := container.PortMaps(containerName) | ||
if err != nil { | ||
return err | ||
} | ||
klog.V(0).Infof("found port maps %v associated to container %s", portmaps, containerName) | ||
|
||
ipv4, _, err := container.IPs(containerName) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
// TODO: IPv6 | ||
klog.V(0).Infof("setting IPv4 address %s associated to container %s", ipv4, containerName) | ||
if err := exec.Command("ifconfig", ifaceName, "alias", ipv4, "netmask", "255.255.255.255", "-arp", "up").Run(); err != nil { | ||
return err | ||
} | ||
|
||
// create tunnel from the ip:svcport to the localhost:portmap | ||
t.mu.Lock() | ||
defer t.mu.Unlock() | ||
// There is one IP per Service and a tunnel per Service Port | ||
for containerPort, hostPort := range portmaps { | ||
tun := NewTunnel(ipv4, containerPort, "localhost", hostPort) | ||
// TODO check if we can leak tunnels | ||
err = tun.Start() | ||
if err != nil { | ||
return err | ||
} | ||
t.tunnels[containerName][containerPort] = tun | ||
} | ||
return nil | ||
} | ||
|
||
func (t *tunnelManager) removeTunnels(containerName string) error { | ||
t.mu.Lock() | ||
defer t.mu.Unlock() | ||
tunnels, ok := t.tunnels[containerName] | ||
if !ok { | ||
return nil | ||
} | ||
|
||
// all tunnels in the same container share the same local IP on the host | ||
var tunnelIP string | ||
for _, tunnel := range tunnels { | ||
if tunnelIP == "" { | ||
tunnelIP = tunnel.localIP | ||
} | ||
tunnel.Stop() | ||
} | ||
|
||
// delete the IP address | ||
if err := exec.Command("ifconfig", ifaceName, "-alias", tunnelIP, "netmask", "255.255.255.255").Run(); err != nil { | ||
return err | ||
} | ||
return nil | ||
} | ||
|
||
// tunnel listens on localIP:localPort and proxies the connection to remoteIP:remotePort | ||
type tunnel struct { | ||
listener net.Listener | ||
localIP string | ||
localPort string | ||
remoteIP string // address:Port | ||
remotePort string | ||
} | ||
|
||
func NewTunnel(localIP, localPort, remoteIP, remotePort string) *tunnel { | ||
return &tunnel{ | ||
localIP: localIP, | ||
localPort: localPort, | ||
remoteIP: remoteIP, | ||
remotePort: remotePort, | ||
} | ||
} | ||
|
||
func (t *tunnel) Start() error { | ||
klog.Infof("Starting tunnel on %s", net.JoinHostPort(t.localIP, t.localPort)) | ||
ln, err := net.Listen("tcp", net.JoinHostPort(t.localIP, t.localPort)) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for { | ||
conn, err := ln.Accept() | ||
if err != nil { | ||
klog.Infof("unexpected error listening: %v", err) | ||
} else { | ||
go func() { | ||
err := t.handleConnection(conn) | ||
if err != nil { | ||
klog.Infof("unexpected error on connection: %v", err) | ||
} | ||
}() | ||
} | ||
} | ||
} | ||
|
||
func (t *tunnel) Stop() error { | ||
return t.listener.Close() | ||
} | ||
|
||
func (t *tunnel) handleConnection(local net.Conn) error { | ||
remote, err := net.Dial("tcp", net.JoinHostPort(t.remoteIP, t.remotePort)) | ||
if err != nil { | ||
return fmt.Errorf("can't connect to server %q: %v", net.JoinHostPort(t.remoteIP, t.remotePort), err) | ||
} | ||
defer remote.Close() | ||
|
||
wg := &sync.WaitGroup{} | ||
wg.Add(2) | ||
go func() { | ||
defer wg.Done() | ||
io.Copy(local, remote) | ||
}() | ||
go func() { | ||
defer wg.Done() | ||
io.Copy(remote, local) | ||
}() | ||
wg.Wait() | ||
return nil | ||
} |