Skip to content

Commit

Permalink
updating to take context and namespace into account
Browse files Browse the repository at this point in the history
  • Loading branch information
ajones committed Sep 23, 2021
1 parent be5c3d0 commit 7e742fc
Show file tree
Hide file tree
Showing 4 changed files with 144 additions and 52 deletions.
20 changes: 15 additions & 5 deletions example.fwdconf.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
baseIP: 127.1.27.1
baseUnreservedIP: 127.1.27.1
serviceConfigurations:
- serviceName: service-name
finalOctet: 11 # 127.1.27.11
- serviceName: other-service-name
finalOctet: 99 # 127.1.27.99
- # identify a single context that will contain this
# namespace and service or wildcard to match all
context: "*"
# identify a single namespace or match on wildcard
# for each namespace containing this service
# octet 2 will be incremented
namespace: "*"
serviceName: service-name
# attaching to multiple clusters will inc octet 1
ip: 127.1.28.1
- context: "foo"
namespace: "bar"
serviceName: service-name2
ip: 127.1.28.1
148 changes: 109 additions & 39 deletions pkg/fwdIp/fwdIp.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,107 +12,165 @@ import (
"gopkg.in/yaml.v2"
)

type ForwardIPOpts struct {
ServiceName string
PodName string
Context string
ClusterN int
NamespaceN int
Namespace string
Port string
ForwardConfigurationPath string
}

// Registry is a structure to create and hold all of the
// IP address assignments
type Registry struct {
mutex *sync.Mutex
inc map[int]map[int]int
reg map[string]net.IP
mutex *sync.Mutex
inc map[int]map[int]int
reg map[string]net.IP
allocated map[string]bool
}

type ForwardConfiguration struct {
BaseIP string `yaml:"baseIP"`
BaseUnreservedIP string `yaml:"baseUnreservedIP"`
ServiceConfigurations []ServiceConfiguration `yaml:"serviceConfigurations"`
}

type ServiceConfiguration struct {
ServiceName string `yaml:"serviceName"`
FinalOctet int `yaml:"finalOctet"`
FailOnOverlap bool `yaml:"failOnOverlap"`
Context string `yaml:"context"`
Namespace string `yaml:"namespace"`
ServiceName string `yaml:"serviceName"`
IP string `yaml:"ip"`
}

var ipRegistry *Registry
var forwardConfiguration *ForwardConfiguration
var defaultConfiguration = &ForwardConfiguration{BaseIP: "127.1.27.1"}
var defaultConfiguration = &ForwardConfiguration{BaseUnreservedIP: "127.1.27.1"}

// Init
func init() {
ipRegistry = &Registry{
mutex: &sync.Mutex{},
// counter for the service cluster and namespace
inc: map[int]map[int]int{0: {0: 0}},
reg: make(map[string]net.IP),
inc: map[int]map[int]int{0: {0: 0}},
reg: make(map[string]net.IP),
allocated: make(map[string]bool),
}
}

func GetIp(svcName string, podName string, clusterN int, NamespaceN int, forwardConfigurationPath string) (net.IP, error) {
func GetIp(opts ForwardIPOpts) (net.IP, error) {
ipRegistry.mutex.Lock()
defer ipRegistry.mutex.Unlock()

regKey := fmt.Sprintf("%d-%d-%s-%s", clusterN, NamespaceN, svcName, podName)
regKey := fmt.Sprintf("%d-%d-%s-%s", opts.ClusterN, opts.NamespaceN, opts.ServiceName, opts.PodName)

if ip, ok := ipRegistry.reg[regKey]; ok {
return ip, nil
}

return determineIP(regKey, svcName, podName, clusterN, NamespaceN, forwardConfigurationPath), nil
return determineIP(regKey, opts), nil
}

func determineIP(regKey string, svcName string, podName string, clusterN int, NamespaceN int, forwardConfigurationPath string) net.IP {
baseIP := getBaseIP(forwardConfigurationPath)
func determineIP(regKey string, opts ForwardIPOpts) net.IP {
baseUnreservedIP := getBaseUnreservedIP(opts.ForwardConfigurationPath)

// if a configuration exists use it
svcConf := getConfigurationForService(svcName, forwardConfigurationPath)
svcConf := getConfigurationForService(opts)
if svcConf != nil {
ip := net.IP{baseIP[0], baseIP[1], baseIP[2], byte(svcConf.FinalOctet)}.To4()
ipRegistry.reg[regKey] = ip
return ip
if ip, err := ipFromString(svcConf.IP); err == nil {
if svcConf.HasWildcardContext() {
// for each cluster increment octet 1
// if the service could exist in multiple contexts
ip[1] = baseUnreservedIP[1] + byte(opts.ClusterN)
}

if svcConf.HasWildcardNamespace() {
// if the service could exist in multiple namespaces
ip[2] = baseUnreservedIP[2] + byte(opts.NamespaceN)
}

if err := addToRegistry(regKey, opts, ip); err != nil {
panic(fmt.Sprintf("Unable to forward service. %s", err))
}
return ip
} else {
log.Errorf("Invalid service ip format %s %s", svcConf.String(), err)
}
}

// fall back to previous implementation
if ipRegistry.inc[clusterN] == nil {
ipRegistry.inc[clusterN] = map[int]int{0: 0}
// fall back to previous implementation if svcConf not provided
if ipRegistry.inc[opts.ClusterN] == nil {
ipRegistry.inc[opts.ClusterN] = map[int]int{0: 0}
}

// @TODO check ranges
ip := net.IP{baseIP[0], baseIP[1], baseIP[2], baseIP[3]}.To4()
ip[1] += byte(clusterN)
ip[2] += byte(NamespaceN)
ip[3] += byte(ipRegistry.inc[clusterN][NamespaceN])
// bounds check
if opts.ClusterN > 255 ||
opts.NamespaceN > 255 ||
ipRegistry.inc[opts.ClusterN][opts.NamespaceN] > 255 {
panic("Ip address generation has run out of bounds.")
}

ipRegistry.inc[clusterN][NamespaceN]++
ipRegistry.reg[regKey] = ip
ip := baseUnreservedIP
ip[1] += byte(opts.ClusterN)
ip[2] += byte(opts.NamespaceN)
ip[3] += byte(ipRegistry.inc[opts.ClusterN][opts.NamespaceN])

ipRegistry.inc[opts.ClusterN][opts.NamespaceN]++
if err := addToRegistry(regKey, opts, ip); err != nil {
// failure to allocate on ip
log.Error(err)
return determineIP(regKey, opts)
}
return ip
}

func getBaseIP(forwardConfigurationPath string) []byte {
fwdCfg := getForwardConfiguration(forwardConfigurationPath)
ipParts := strings.Split(fwdCfg.BaseIP, ".")
func addToRegistry(regKey string, opts ForwardIPOpts, ip net.IP) error {
allocationKey := fmt.Sprintf("%s:%s", ip.String(), opts.Port)
if _, ok := ipRegistry.allocated[allocationKey]; ok {
// ip/port pair has allready ben allocated
return fmt.Errorf("ip/port pair %s has already been allocated", allocationKey)
}
ipRegistry.reg[regKey] = ip
return nil
}

func ipFromString(ipStr string) (net.IP, error) {
ipParts := strings.Split(ipStr, ".")

octet0, err := strconv.Atoi(ipParts[0])
if err != nil {
panic("Unable to parse BaseIP octet 0")
return nil, fmt.Errorf("Unable to parse BaseIP octet 0")
}
octet1, err := strconv.Atoi(ipParts[1])
if err != nil {
panic("Unable to parse BaseIP octet 1")
return nil, fmt.Errorf("Unable to parse BaseIP octet 1")
}
octet2, err := strconv.Atoi(ipParts[2])
if err != nil {
panic("Unable to parse BaseIP octet 2")
return nil, fmt.Errorf("Unable to parse BaseIP octet 2")
}
octet3, err := strconv.Atoi(ipParts[3])
if err != nil {
panic("Unable to parse BaseIP octet 3")
return nil, fmt.Errorf("Unable to parse BaseIP octet 3")
}
return []byte{byte(octet0), byte(octet1), byte(octet2), byte(octet3)}
return net.IP{byte(octet0), byte(octet1), byte(octet2), byte(octet3)}.To4(), nil
}

func getConfigurationForService(serviceName string, forwardConfigurationPath string) *ServiceConfiguration {
func getBaseUnreservedIP(forwardConfigurationPath string) []byte {
fwdCfg := getForwardConfiguration(forwardConfigurationPath)
ip, err := ipFromString(fwdCfg.BaseUnreservedIP)
if err != nil {
panic(err)
}
return ip
}

func getConfigurationForService(opts ForwardIPOpts) *ServiceConfiguration {
fwdCfg := getForwardConfiguration(opts.ForwardConfigurationPath)
for _, c := range fwdCfg.ServiceConfigurations {
if c.ServiceName == serviceName {
if c.ServiceName == opts.ServiceName &&
(c.Namespace == "*" || c.Namespace == opts.Namespace) {
return &c
}
}
Expand Down Expand Up @@ -149,3 +207,15 @@ func getForwardConfiguration(forwardConfigurationPath string) *ForwardConfigurat
forwardConfiguration = conf
return forwardConfiguration
}

func (c ServiceConfiguration) HasWildcardContext() bool {
return c.Context == "*"
}

func (c ServiceConfiguration) HasWildcardNamespace() bool {
return c.Namespace == "*"
}

func (c ServiceConfiguration) String() string {
return fmt.Sprintf("Ctx: %s Ns:%s Svc:%s IP:%s", c.Context, c.Namespace, c.ServiceName, c.IP)
}
10 changes: 5 additions & 5 deletions pkg/fwdnet/fwdnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ import (

// ReadyInterface prepares a local IP address on
// the loopback interface.
func ReadyInterface(svcName string, podName string, clusterN int, namespaceN int, port string, forwardConfigurationPath string) (net.IP, error) {
func ReadyInterface(opts fwdIp.ForwardIPOpts) (net.IP, error) {

ip, _ := fwdIp.GetIp(svcName, podName, clusterN, namespaceN, forwardConfigurationPath)
ip, _ := fwdIp.GetIp(opts)

// lo means we are probably on linux and not mac
_, err := net.InterfaceByName("lo")
if err == nil || runtime.GOOS == "windows" {
// if no error then check to see if the ip:port are in use
_, err := net.Dial("tcp", ip.String()+":"+port)
_, err := net.Dial("tcp", ip.String()+":"+opts.Port)
if err != nil {
return ip, nil
}
Expand All @@ -45,7 +45,7 @@ func ReadyInterface(svcName string, podName string, clusterN int, namespaceN int
// found a match
if addr.String() == ip.String()+"/8" {
// found ip, now check for unused port
conn, err := net.Dial("tcp", ip.String()+":"+port)
conn, err := net.Dial("tcp", ip.String()+":"+opts.Port)
if err != nil {
return ip, nil
}
Expand All @@ -62,7 +62,7 @@ func ReadyInterface(svcName string, podName string, clusterN int, namespaceN int
os.Exit(1)
}

conn, err := net.Dial("tcp", ip.String()+":"+port)
conn, err := net.Dial("tcp", ip.String()+":"+opts.Port)
if err != nil {
return ip, nil
}
Expand Down
18 changes: 15 additions & 3 deletions pkg/fwdservice/fwdservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"time"

log "github.com/sirupsen/logrus"
"github.com/txn2/kubefwd/pkg/fwdIp"
"github.com/txn2/kubefwd/pkg/fwdnet"
"github.com/txn2/kubefwd/pkg/fwdport"
"github.com/txn2/kubefwd/pkg/fwdpub"
Expand Down Expand Up @@ -234,7 +235,17 @@ func (svcFwd *ServiceFWD) LoopPodsToForward(pods []v1.Pod, includePodNameInHost
svcName = pod.Name + "." + svcFwd.Svc.Name
}

localIp, err := fwdnet.ReadyInterface(svcName, pod.Name, svcFwd.ClusterN, svcFwd.NamespaceN, podPort, svcFwd.ServiceConfigPath)
opts := fwdIp.ForwardIPOpts{
ServiceName: svcName,
PodName: pod.Name,
Context: svcFwd.Context,
ClusterN: svcFwd.ClusterN,
NamespaceN: svcFwd.NamespaceN,
Namespace: svcFwd.Namespace,
Port: podPort,
ForwardConfigurationPath: svcFwd.ServiceConfigPath,
}
localIp, err := fwdnet.ReadyInterface(opts)
if err != nil {
log.Warnf("WARNING: error readying interface: %s\n", err)
}
Expand Down Expand Up @@ -285,9 +296,10 @@ func (svcFwd *ServiceFWD) LoopPodsToForward(pods []v1.Pod, includePodNameInHost
)

// 30 chars is a pretty long service name
log.Printf("Port-Forward: %-30s %s to pod %s:%s\n",
fmt.Sprintf("%s:%d", serviceHostName, port.Port),
log.Printf("Port-Forward: %16s %s:%d to pod %s:%s\n",
localIp.String(),
serviceHostName,
port.Port,
pod.Name,
podPort,
)
Expand Down

0 comments on commit 7e742fc

Please sign in to comment.