-
Notifications
You must be signed in to change notification settings - Fork 68
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
base: main
Are you sure you want to change the base?
Add netutil package #100
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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 | ||||||
default: | ||||||
return net.IPv4zero, false | ||||||
} | ||||||
} | ||||||
|
||||||
func PrivateNetworkInterfaces() []string { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [nit] Can you comment the function, please? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we test it? You could have |
||||||
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() { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this seems to check for |
||||||
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) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
} | ||||||
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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't be |
||||||
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 { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? |
||||||
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 | ||||||
} |
There was a problem hiding this comment.
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.