k3s/pkg/util/net.go

433 lines
11 KiB
Go

package util
import (
"context"
"errors"
"fmt"
"net"
"os"
"strings"
"time"
"github.com/rancher/wrangler/v3/pkg/merr"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
apinet "k8s.io/apimachinery/pkg/util/net"
netutils "k8s.io/utils/net"
)
// JoinIPs stringifies and joins a list of IP addresses with commas.
func JoinIPs(elems []net.IP) string {
var strs []string
for _, elem := range elems {
strs = append(strs, elem.String())
}
return strings.Join(strs, ",")
}
// JoinIPNets stringifies and joins a list of IP networks with commas.
func JoinIPNets(elems []*net.IPNet) string {
var strs []string
for _, elem := range elems {
strs = append(strs, elem.String())
}
return strings.Join(strs, ",")
}
// getFirst4Net returns the first IPv4 network from the list of IP networks.
// If no IPv4 addresses are found, an error is raised.
func getFirst4Net(elems []*net.IPNet) (*net.IPNet, error) {
for _, elem := range elems {
if elem == nil || elem.IP.To4() == nil {
continue
}
return elem, nil
}
return nil, errors.New("no IPv4 CIDRs found")
}
// getFirst4 returns the first IPv4 address from the list of IP addresses.
// If no IPv4 addresses are found, an error is raised.
func getFirst4(elems []net.IP) (net.IP, error) {
for _, elem := range elems {
if elem == nil || elem.To4() == nil {
continue
}
return elem, nil
}
return nil, errors.New("no IPv4 address found")
}
// GetFirst4String returns the first IPv4 address from a list of IP address strings.
// If no IPv4 addresses are found, an error is raised.
func GetFirst4String(elems []string) (string, error) {
ips := []net.IP{}
for _, elem := range elems {
for _, v := range strings.Split(elem, ",") {
ips = append(ips, net.ParseIP(v))
}
}
ip, err := getFirst4(ips)
if err != nil {
return "", err
}
return ip.String(), nil
}
// JoinIP4Nets stringifies and joins a list of IPv4 networks with commas.
func JoinIP4Nets(elems []*net.IPNet) string {
var strs []string
for _, elem := range elems {
if elem != nil && elem.IP.To4() != nil {
strs = append(strs, elem.String())
}
}
return strings.Join(strs, ",")
}
// getFirst6 returns the first IPv6 address from the list of IP addresses.
// If no IPv6 addresses are found, an error is raised.
func getFirst6(elems []net.IP) (net.IP, error) {
for _, elem := range elems {
if elem != nil && netutils.IsIPv6(elem) {
return elem, nil
}
}
return nil, errors.New("no IPv6 address found")
}
// getFirst6Net returns the first IPv4 network from the list of IP networks.
// If no IPv6 addresses are found, an error is raised.
func getFirst6Net(elems []*net.IPNet) (*net.IPNet, error) {
for _, elem := range elems {
if elem != nil && netutils.IsIPv6(elem.IP) {
return elem, nil
}
}
return nil, errors.New("no IPv6 CIDRs found")
}
// GetFirst6String returns the first IPv6 address from a list of IP address strings.
// If no IPv6 addresses are found, an error is raised.
func GetFirst6String(elems []string) (string, error) {
ips := []net.IP{}
for _, elem := range elems {
for _, v := range strings.Split(elem, ",") {
ips = append(ips, net.ParseIP(v))
}
}
ip, err := getFirst6(ips)
if err != nil {
return "", err
}
return ip.String(), nil
}
// JoinIP6Nets stringifies and joins a list of IPv6 networks with commas.
func JoinIP6Nets(elems []*net.IPNet) string {
var strs []string
for _, elem := range elems {
if elem != nil && netutils.IsIPv6(elem.IP) {
strs = append(strs, elem.String())
}
}
return strings.Join(strs, ",")
}
// GetHostnameAndIPs takes a node name and list of IPs, usually from CLI args.
// If set, these are used to return the node's name and addresses. If not set,
// the system hostname and primary interface addresses are returned instead.
func GetHostnameAndIPs(name string, nodeIPs cli.StringSlice) (string, []net.IP, error) {
ips := []net.IP{}
if len(nodeIPs) == 0 {
hostIP, err := apinet.ChooseHostInterface()
if err != nil {
return "", nil, err
}
ips = append(ips, hostIP)
// If IPv6 it's an IPv6 only node
if hostIP.To4() != nil {
hostIPv6, err := apinet.ResolveBindAddress(net.IPv6loopback)
if err == nil && !hostIPv6.Equal(hostIP) {
ips = append(ips, hostIPv6)
}
}
} else {
var err error
ips, err = ParseStringSliceToIPs(nodeIPs)
if err != nil {
return "", nil, fmt.Errorf("invalid node-ip: %w", err)
}
}
if name == "" {
hostname, err := os.Hostname()
if err != nil {
return "", nil, err
}
name = hostname
}
// Use lower case hostname to comply with kubernetes constraint:
// https://github.com/kubernetes/kubernetes/issues/71140
name = strings.ToLower(name)
return name, ips, nil
}
// ParseStringSliceToIPs converts slice of strings that in turn can be lists of comma separated unparsed IP addresses
// into a single slice of net.IP, it returns error if at any point parsing failed
func ParseStringSliceToIPs(s cli.StringSlice) ([]net.IP, error) {
var ips []net.IP
for _, unparsedIP := range s {
for _, v := range strings.Split(unparsedIP, ",") {
ip := net.ParseIP(v)
if ip == nil {
return nil, fmt.Errorf("invalid ip format '%s'", v)
}
ips = append(ips, ip)
}
}
return ips, nil
}
// GetFirstValidIPString returns the first valid address from a list of IP address strings,
// without preference for IP family. If no address are found, an empty string is returned.
func GetFirstValidIPString(s cli.StringSlice) string {
for _, unparsedIP := range s {
for _, v := range strings.Split(unparsedIP, ",") {
if ip := net.ParseIP(v); ip != nil {
return v
}
}
}
return ""
}
// GetFirstIP checks what is the IPFamily of the first item. Based on that, returns a set of values
func GetDefaultAddresses(nodeIP net.IP) (string, string, string, error) {
if netutils.IsIPv4(nodeIP) {
ListenAddress := "0.0.0.0"
clusterCIDR := "10.42.0.0/16"
serviceCIDR := "10.43.0.0/16"
return ListenAddress, clusterCIDR, serviceCIDR, nil
}
if netutils.IsIPv6(nodeIP) {
ListenAddress := "::"
clusterCIDR := "fd00:42::/56"
serviceCIDR := "fd00:43::/112"
return ListenAddress, clusterCIDR, serviceCIDR, nil
}
return "", "", "", fmt.Errorf("ip: %v is not ipv4 or ipv6", nodeIP)
}
// GetFirstString returns the first IP4 address from a list of IP address strings.
// If no IPv4 addresses are found, returns the first IPv6 address
// if neither of IPv4 or IPv6 are found an error is raised.
func GetFirstString(elems []string) (string, bool, error) {
ip, err := GetFirst4String(elems)
IPv6only := false
if err != nil {
ip, err = GetFirst6String(elems)
if err != nil {
return "", false, err
}
IPv6only = true
}
return ip, IPv6only, nil
}
// IPToIPNet converts an IP to an IPNet, using a fully filled mask appropriate for the address family.
func IPToIPNet(ip net.IP) (*net.IPNet, error) {
address := ip.String()
if strings.Contains(address, ":") {
address += "/128"
} else {
address += "/32"
}
_, cidr, err := net.ParseCIDR(address)
return cidr, err
}
// IPStringToIPNet converts an IP string to an IPNet, using a fully filled mask appropriate for the address family.
func IPStringToIPNet(address string) (*net.IPNet, error) {
if strings.Contains(address, ":") {
address += "/128"
} else {
address += "/32"
}
_, cidr, err := net.ParseCIDR(address)
return cidr, err
}
// GetIPFromInterface is the public function that returns the IP of an interface
func GetIPFromInterface(ifaceName string) (string, error) {
ip, err := getIPFromInterface(ifaceName)
if err != nil {
return "", fmt.Errorf("interface %s does not have a correct global unicast ip: %w", ifaceName, err)
}
logrus.Infof("Found ip %s from iface %s", ip, ifaceName)
return ip, nil
}
// getIPFromInterface is the private function that returns de IP of an interface
func getIPFromInterface(ifaceName string) (string, error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
return "", err
}
addrs, err := iface.Addrs()
if err != nil {
return "", err
}
if iface.Flags&net.FlagUp == 0 {
return "", fmt.Errorf("the interface %s is not up", ifaceName)
}
globalUnicasts := []string{}
globalUnicastsIPv6 := []string{}
for _, addr := range addrs {
ip, _, err := net.ParseCIDR(addr.String())
if err != nil {
return "", fmt.Errorf("unable to parse CIDR for interface %s: %w", iface.Name, err)
}
// if not IPv4 adding it on IPv6 list
if ip.To4() == nil {
if ip.IsGlobalUnicast() {
globalUnicastsIPv6 = append(globalUnicastsIPv6, ip.String())
}
continue
}
if ip.IsGlobalUnicast() {
globalUnicasts = append(globalUnicasts, ip.String())
}
}
if len(globalUnicasts) > 1 {
return "", fmt.Errorf("multiple global unicast addresses defined for %s, please set ip from one of %v", ifaceName, globalUnicasts)
}
if len(globalUnicasts) == 1 && len(globalUnicastsIPv6) == 0 {
return globalUnicasts[0], nil
} else if len(globalUnicastsIPv6) > 0 && len(globalUnicasts) == 1 {
return globalUnicasts[0] + "," + globalUnicastsIPv6[0], nil
} else if len(globalUnicastsIPv6) > 0 {
return globalUnicastsIPv6[0], nil
}
return "", fmt.Errorf("can't find ip for interface %s", ifaceName)
}
type multiListener struct {
listeners []net.Listener
closing chan struct{}
conns chan acceptRes
}
type acceptRes struct {
conn net.Conn
err error
}
// explicit interface check
var _ net.Listener = &multiListener{}
var loopbacks = []string{"127.0.0.1", "::1"}
// ListenWithLoopback listens on the given address, as well as on IPv4 and IPv6 loopback addresses.
// If the address is a wildcard, the listener is return unwrapped.
func ListenWithLoopback(ctx context.Context, addr string, port string) (net.Listener, error) {
lc := &net.ListenConfig{
KeepAlive: 3 * time.Minute,
Control: permitReuse,
}
l, err := lc.Listen(ctx, "tcp", net.JoinHostPort(addr, port))
if err != nil {
return nil, err
}
// If we're listening on a wildcard address, we don't need to wrap with the other loopback addresses
switch addr {
case "", "::", "0.0.0.0":
return l, nil
}
ml := &multiListener{
listeners: []net.Listener{l},
closing: make(chan struct{}),
conns: make(chan acceptRes),
}
for _, laddr := range loopbacks {
if laddr == addr {
continue
}
if l, err := lc.Listen(ctx, "tcp", net.JoinHostPort(laddr, port)); err == nil {
ml.listeners = append(ml.listeners, l)
} else {
logrus.Debugf("Failed to listen on %s: %v", net.JoinHostPort(laddr, port), err)
}
}
for i := range ml.listeners {
go ml.accept(ml.listeners[i])
}
return ml, nil
}
// Addr returns the address of the non-loopback address that this multiListener is listening on
func (ml *multiListener) Addr() net.Addr {
return ml.listeners[0].Addr()
}
// Close closes all the listeners
func (ml *multiListener) Close() error {
close(ml.closing)
var errs merr.Errors
for i := range ml.listeners {
err := ml.listeners[i].Close()
if err != nil {
errs = append(errs, err)
}
}
return merr.NewErrors(errs)
}
// Accept returns a Conn/err pair from one of the waiting listeners
func (ml *multiListener) Accept() (net.Conn, error) {
select {
case res, ok := <-ml.conns:
if ok {
return res.conn, res.err
}
return nil, fmt.Errorf("connection channel closed")
case <-ml.closing:
return nil, fmt.Errorf("listener closed")
}
}
// accept runs a loop, accepting connections and trying to send on the result channel
func (ml *multiListener) accept(listener net.Listener) {
for {
conn, err := listener.Accept()
r := acceptRes{
conn: conn,
err: err,
}
select {
case ml.conns <- r:
case <-ml.closing:
if r.err == nil {
r.conn.Close()
}
return
}
}
}