Skip to content

Commit

Permalink
Ensure FirstPublicAddress isn't a secondary addr
Browse files Browse the repository at this point in the history
Only for linux

Signed-off-by: Juan-Luis de Sousa-Valadas Castaño <jvaladas@mirantis.com>
  • Loading branch information
juanluisvaladas committed Jan 8, 2025
1 parent 79fb6f9 commit a8ad61b
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 49 deletions.
68 changes: 19 additions & 49 deletions internal/pkg/iface/iface.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@ import (
"fmt"
"net"
"strings"

"github.com/sirupsen/logrus"
)

// AllAddresses returns a list of all network addresses on a node
Expand Down Expand Up @@ -56,53 +54,25 @@ func CollectAllIPs() (addresses []net.IP, err error) {
return addresses, nil
}

// FirstPublicAddress return the first found non-local IPv4 address that's not part of pod network
// if any interface does not have any IPv4 address then return the first found non-local IPv6 address
func FirstPublicAddress() (string, error) {
ifs, err := net.Interfaces()
if err != nil {
return "127.0.0.1", fmt.Errorf("failed to list network interfaces: %w", err)
}
ipv6addr := ""
for _, i := range ifs {
switch {
// Skip calico CNI interface
case i.Name == "vxlan.calico":
continue
// Skip kube-router CNI interface
case i.Name == "kube-bridge":
continue
// Skip k0s CPLB interface
case i.Name == "dummyvip0":
continue
// Skip kube-router pod CNI interfaces
case strings.HasPrefix(i.Name, "veth"):
continue
// Skip calico pod CNI interfaces
case strings.HasPrefix(i.Name, "cali"):
continue
}
addresses, err := i.Addrs()
if err != nil {
logrus.Warnf("failed to get addresses for interface %s: %s", i.Name, err.Error())
continue
}
for _, a := range addresses {
// check the address type and skip if loopback
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
if ipnet.IP.To16() != nil && ipv6addr == "" {
ipv6addr = ipnet.IP.String()
}
}
}
}
if ipv6addr != "" {
return ipv6addr, nil
// isPublicInterface returns true if the interface is not part of pod network.
func isPublicInterface(iface string) bool {
switch {
// Skip calico CNI interface
case iface == "vxlan.calico":
return false
// Skip kube-router CNI interface
case iface == "kube-bridge":
return false
// Skip k0s CPLB interface
case iface == "dummyvip0":
return false
// Skip kube-router pod CNI interfaces
case strings.HasPrefix(iface, "veth"):
return false
// Skip calico pod CNI interfaces
case strings.HasPrefix(iface, "cali"):
return false
}

logrus.Warn("failed to find any non-local, non podnetwork addresses on host, defaulting public address to 127.0.0.1")
return "127.0.0.1", nil
return true
}
77 changes: 77 additions & 0 deletions internal/pkg/iface/iface_linux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
Copyright 2025 k0s authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package iface

import (
"fmt"
"net"

"github.com/sirupsen/logrus"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
)

// FirstPublicAddress return the first found non-local IPv4 address that's not part of pod network
// if any interface does not have any IPv4 address then return the first found non-local IPv6 address
func FirstPublicAddress() (string, error) {
ifs, err := net.Interfaces()
if err != nil {
return "127.0.0.1", fmt.Errorf("failed to list network interfaces: %w", err)
}
ipv6addr := ""
for _, i := range ifs {
if !isPublicInterface(i.Name) {
continue
}

link, err := netlink.LinkByName(i.Name)
if err != nil {
logrus.WithError(err).Warnf("failed to get link by name %s", i.Name)
continue
}

addresses, err := netlink.AddrList(link, netlink.FAMILY_ALL)
if err != nil {
logrus.WithError(err).Warnf("failed to get addresses for interface %s", i.Name)
continue
}

for _, a := range addresses {
// skip secondary addresses. This is to avoid returning VIPs as the public address
// https://github.com/k0sproject/k0s/issues/4664
if a.Flags&unix.IFA_F_SECONDARY != 0 {
continue
}
// check the address type and skip if loopback
if a.IPNet != nil && !a.IPNet.IP.IsLoopback() {
if a.IPNet.IP.To4() != nil {
return a.IPNet.IP.String(), nil
}
if a.IPNet.IP.To16() != nil && ipv6addr == "" {
ipv6addr = a.IPNet.IP.String()
}
}
}

}
if ipv6addr != "" {
return ipv6addr, nil
}

logrus.Warn("failed to find any non-local, non podnetwork addresses on host, defaulting public address to 127.0.0.1")
return "127.0.0.1", nil
}
64 changes: 64 additions & 0 deletions internal/pkg/iface/iface_other.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
//go:build !linux

/*
Copyright 2025 k0s authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package iface

import (
"fmt"
"net"

"github.com/sirupsen/logrus"
)

// FirstPublicAddress return the first found non-local IPv4 address that's not part of pod network
// if any interface does not have any IPv4 address then return the first found non-local IPv6 address
func FirstPublicAddress() (string, error) {
ifs, err := net.Interfaces()
if err != nil {
return "127.0.0.1", fmt.Errorf("failed to list network interfaces: %w", err)
}
ipv6addr := ""
for _, i := range ifs {
if !isPublicInterface(i.Name) {
continue
}

addresses, err := i.Addrs()
if err != nil {
logrus.Warnf("failed to get addresses for interface %s: %s", i.Name, err.Error())
continue
}
for _, a := range addresses {
// check the address type and skip if loopback
if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
if ipnet.IP.To16() != nil && ipv6addr == "" {
ipv6addr = ipnet.IP.String()
}
}
}
}
if ipv6addr != "" {
return ipv6addr, nil
}

logrus.Warn("failed to find any non-local, non podnetwork addresses on host, defaulting public address to 127.0.0.1")
return "127.0.0.1", nil
}
45 changes: 45 additions & 0 deletions internal/pkg/iface/iface_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
Copyright 2025 k0s authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package iface

import (
"testing"
)

func TestIsPublicInterface(t *testing.T) {
tests := []struct {
iface string
want bool
}{
{"vxlan.calico", false},
{"kube-bridge", false},
{"dummyvip0", false},
{"veth1234", false},
{"cali1234", false},
{"eth0", true},
{"wlan0", true},
{"lo", true},
}

for _, tt := range tests {
t.Run(tt.iface, func(t *testing.T) {
if got := isPublicInterface(tt.iface); got != tt.want {
t.Errorf("isPublicInterface(%q) = %v, want %v", tt.iface, got, tt.want)
}
})
}
}

0 comments on commit a8ad61b

Please sign in to comment.