Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add netutil package #100

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions netutil/netutil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package netutil

import (
"fmt"
"net"

"github.com/go-kit/log"
"github.com/go-kit/log/level"
)

func ipForAddr(addr net.Addr) (net.IP, bool) {
switch a := addr.(type) {
case *net.IPAddr:
return a.IP, true
case *net.IPNet:
return a.IP, true
Copy link
Contributor

@replay replay Dec 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If addr is a network address I'm not sure it's a good idea to just return the address of the network without any indication that this isn't a host address. Couldn't that lead to users wrongly assuming that they will get a host address back? I think there should at least be a comment stating that this might also return a network address.

default:
return net.IPv4zero, false
}
}

func PrivateNetworkInterfaces() []string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] Can you comment the function, please?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we test it? You could have privateNetworkInterfaces(all []net.Interface), test it and have PrivateNetworkInterfaces() just calling it.

ifaces := []string{}

all, err := net.Interfaces()
if err != nil {
return ifaces
}

IFACES:
for _, iface := range all {
if iface.Flags&net.FlagLoopback == 0 && iface.Flags&net.FlagUp != 0 {
addrs, err := iface.Addrs()
if err != nil {
continue
}
for _, addr := range addrs {
ip, ok := ipForAddr(addr)
if !ok {
continue
}
if !IsPrivate(ip) {
continue IFACES
}
}
ifaces = append(ifaces, iface.Name)
}
}
return ifaces
}

// FirstAddressOf returns the first IPv4 address of the supplied interface
// names, omitting any 169.254.x.x automatic private IPs if possible.
func FirstAddressOf(names []string, logger log.Logger) (string, error) {
var ipAddr net.IP
for _, name := range names {
inf, err := net.InterfaceByName(name)
if err != nil {
level.Warn(logger).Log("msg", "error getting interface", "inf", name, "err", err)
continue
}
addrs, err := inf.Addrs()
if err != nil {
level.Warn(logger).Log("msg", "error getting addresses for interface", "inf", name, "err", err)
continue
}
if len(addrs) <= 0 {
level.Warn(logger).Log("msg", "no addresses found for interface", "inf", name, "err", err)
continue
}
if ip := filterIPs(addrs); !ip.IsUnspecified() {
ipAddr = ip
}
if isAPIPA(ipAddr) || ipAddr.IsUnspecified() {
Copy link
Contributor

@replay replay Dec 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this seems to check for .IsUnspecified() twice in some cases

continue
}
return ipAddr.String(), nil
}
if ipAddr.IsUnspecified() {
return "", fmt.Errorf("no address found for %s", names)
}
if isAPIPA(ipAddr) {
level.Warn(logger).Log("msg", "using automatic private ip", "address", ipAddr)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd also mention why we fall back to using an automatic private ip

Suggested change
level.Warn(logger).Log("msg", "using automatic private ip", "address", ipAddr)
level.Warn(logger).Log("msg", "using automatic private ip because no other one was found", "address", ipAddr)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are multiple automatic private addresses and no other addresses then this will return the last one of them, which I think is confusing given the name of the function being FirstAddressOf

}
return ipAddr.String(), nil
}

func isAPIPA(ip4 net.IP) bool {
return ip4[0] == 169 && ip4[1] == 254
}

// filterIPs attempts to return the first non automatic private IP (APIPA /
// 169.254.x.x) if possible, only returning APIPA if available and no other
// valid IP is found.
func filterIPs(addrs []net.Addr) net.IP {
ipAddr := net.IPv4zero
for _, addr := range addrs {
if ip, ok := ipForAddr(addr); ok {
if ip4 := ip.To4(); ip4 != nil {
ipAddr = ip4
if isAPIPA(ip4) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't be if !isAPIPA()? Can we have a test on this regression?

return ipAddr
}
}
}
}
return ipAddr
}

// IsPrivate reports whether ip is a private address, according to
// RFC 1918 (IPv4 addresses) and RFC 4193 (IPv6 addresses).
// Copied form net package of the Go 1.17 stdlib.
// So, it can be removed once dskit is updated to Go 1.17
func IsPrivate(ip net.IP) bool {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have this same exact code in Cortex too. Can we import its tests?
https://github.com/cortexproject/cortex/blob/master/pkg/util/net/firewall_dialer_test.go#L102

if ip4 := ip.To4(); ip4 != nil {
// Following RFC 1918, Section 3. Private Address Space which says:
// The Internet Assigned Numbers Authority (IANA) has reserved the
// following three blocks of the IP address space for private internets:
// 10.0.0.0 - 10.255.255.255 (10/8 prefix)
// 172.16.0.0 - 172.31.255.255 (172.16/12 prefix)
// 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)
return ip4[0] == 10 ||
(ip4[0] == 172 && ip4[1]&0xf0 == 16) ||
(ip4[0] == 192 && ip4[1] == 168)
}
// Following RFC 4193, Section 8. IANA Considerations which says:
// The IANA has assigned the FC00::/7 prefix to "Unique Local Unicast".
return len(ip) == net.IPv6len && ip[0]&0xfe == 0xfc
}
58 changes: 2 additions & 56 deletions ring/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ package ring

import (
"context"
"fmt"
"math/rand"
"net"
"sort"
"strings"
"time"

"github.com/go-kit/log"
"github.com/go-kit/log/level"

"github.com/grafana/dskit/backoff"
"github.com/grafana/dskit/netutil"
)

// GenerateTokens make numTokens unique random tokens, none of which clash
Expand Down Expand Up @@ -55,7 +52,7 @@ func GetInstanceAddr(configAddr string, netInterfaces []string, logger log.Logge
return configAddr, nil
}

addr, err := getFirstAddressOf(netInterfaces, logger)
addr, err := netutil.FirstAddressOf(netInterfaces, logger)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -171,54 +168,3 @@ func searchToken(tokens []uint32, key uint32) int {
}
return i
}

// GetFirstAddressOf returns the first IPv4 address of the supplied interface names, omitting any 169.254.x.x automatic private IPs if possible.
func getFirstAddressOf(names []string, logger log.Logger) (string, error) {
var ipAddr string
for _, name := range names {
inf, err := net.InterfaceByName(name)
if err != nil {
level.Warn(logger).Log("msg", "error getting interface", "inf", name, "err", err)
continue
}
addrs, err := inf.Addrs()
if err != nil {
level.Warn(logger).Log("msg", "error getting addresses for interface", "inf", name, "err", err)
continue
}
if len(addrs) <= 0 {
level.Warn(logger).Log("msg", "no addresses found for interface", "inf", name, "err", err)
continue
}
if ip := filterIPs(addrs); ip != "" {
ipAddr = ip
}
if strings.HasPrefix(ipAddr, `169.254.`) || ipAddr == "" {
continue
}
return ipAddr, nil
}
if ipAddr == "" {
return "", fmt.Errorf("No address found for %s", names)
}
if strings.HasPrefix(ipAddr, `169.254.`) {
level.Warn(logger).Log("msg", "using automatic private ip", "address", ipAddr)
}
return ipAddr, nil
}

// filterIPs attempts to return the first non automatic private IP (APIPA / 169.254.x.x) if possible, only returning APIPA if available and no other valid IP is found.
func filterIPs(addrs []net.Addr) string {
var ipAddr string
for _, addr := range addrs {
if v, ok := addr.(*net.IPNet); ok {
if ip := v.IP.To4(); ip != nil {
ipAddr = v.IP.String()
if !strings.HasPrefix(ipAddr, `169.254.`) {
return ipAddr
}
}
}
}
return ipAddr
}