kubekey/pkg/const/ip.go
liujian 620b7f56a3
feat: enhance connector interface and implementations (#2675)
- Updated the Connector interface to return both stdout and stderr for command execution.
- Modified implementations in local, kubernetes, and ssh connectors to support the new return values.
- Improved documentation for the Connector interface methods for clarity.
- Added error handling for stderr in command execution across connectors.
- Introduced new utility functions for IP parsing and checking localhost IPs.

Signed-off-by: joyceliu <joyceliu@yunify.com>
2025-07-30 08:02:34 +00:00

262 lines
7.3 KiB
Go

package _const
import (
"encoding/binary"
"math"
"math/big"
"net"
"strconv"
"strings"
)
// ===========================================================================
// ============================= ParseIP =================================
// ===========================================================================
// ParseIP parses a CIDR, an IP range string (e.g., "xxx-xxx"), or a single IP into a slice of actual IPs.
// Supports both IPv4 and IPv6.
func ParseIP(ip string) []string {
var availableIPs []string
ip = strings.TrimRight(ip, "/")
if strings.Contains(ip, "/") {
// Handle single IP case in CIDR format
if strings.HasSuffix(ip, "/32") || strings.HasSuffix(ip, "/128") {
availableIPs = append(availableIPs, strings.Split(ip, "/")[0])
} else {
availableIPs = getAvailableIP(ip)
}
} else if strings.Contains(ip, "-") {
ipRange := strings.SplitN(ip, "-", 2)
if len(ipRange) == 2 {
availableIPs = getAvailableIPRange(ipRange[0], ipRange[1])
}
} else {
availableIPs = append(availableIPs, ip)
}
return availableIPs
}
// getAvailableIPRange generates all IPs between the start and end IP addresses (inclusive).
// Supports both IPv4 and IPv6.
func getAvailableIPRange(ipStart, ipEnd string) []string {
var availableIPs []string
startIP := net.ParseIP(ipStart)
endIP := net.ParseIP(ipEnd)
if startIP == nil || endIP == nil {
return availableIPs
}
// Determine if IPv4 or IPv6
if startIP.To4() != nil && endIP.To4() != nil {
startIP = startIP.To4()
endIP = endIP.To4()
startIPNum := ip4ToInt(startIP)
endIPNum := ip4ToInt(endIP)
for ipNum := new(big.Int).Set(startIPNum); ipNum.Cmp(endIPNum) <= 0; ipNum.Add(ipNum, big.NewInt(1)) {
availableIPs = append(availableIPs, intToIP4(ipNum).String())
}
} else if startIP.To16() != nil && endIP.To16() != nil {
startIP = startIP.To16()
endIP = endIP.To16()
startIPNum := ip6ToInt(startIP)
endIPNum := ip6ToInt(endIP)
for ipNum := new(big.Int).Set(startIPNum); ipNum.Cmp(endIPNum) <= 0; ipNum.Add(ipNum, big.NewInt(1)) {
availableIPs = append(availableIPs, intToIP6(ipNum).String())
}
}
return availableIPs
}
// getAvailableIP calculates all available IPs in a given CIDR.
// Supports both IPv4 and IPv6.
func getAvailableIP(ipAndMask string) []string {
var availableIPs []string
ipAndMask = strings.TrimSpace(ipAndMask)
ipAndMask = iPAddressToCIDR(ipAndMask)
_, ipnet, err := net.ParseCIDR(ipAndMask)
if err != nil || ipnet == nil {
return availableIPs
}
firstIP, lastIP := networkRange(ipnet)
if firstIP == nil || lastIP == nil {
return availableIPs
}
// IPv4
if firstIP.To4() != nil {
startIPNum := ip4ToInt(firstIP)
endIPNum := ip4ToInt(lastIP)
// Exclude network and broadcast addresses if possible
for ipNum := new(big.Int).Add(startIPNum, big.NewInt(1)); ipNum.Cmp(endIPNum) < 0; ipNum.Add(ipNum, big.NewInt(1)) {
availableIPs = append(availableIPs, intToIP4(ipNum).String())
}
} else if firstIP.To16() != nil {
// IPv6: no broadcast, so include all except network address
startIPNum := ip6ToInt(firstIP)
endIPNum := ip6ToInt(lastIP)
for ipNum := new(big.Int).Set(startIPNum); ipNum.Cmp(endIPNum) <= 0; ipNum.Add(ipNum, big.NewInt(1)) {
availableIPs = append(availableIPs, intToIP6(ipNum).String())
}
}
return availableIPs
}
// ip4ToInt converts an IPv4 address to a big.Int.
func ip4ToInt(ip net.IP) *big.Int {
ip = ip.To4()
if ip == nil {
return big.NewInt(0)
}
return big.NewInt(0).SetUint64(uint64(binary.BigEndian.Uint32(ip)))
}
// intToIP4 converts a big.Int to an IPv4 address.
func intToIP4(n *big.Int) net.IP {
maxIPv4 := big.NewInt(int64(math.MaxUint32))
if n.Cmp(maxIPv4) > 0 {
n = maxIPv4
}
// Get 4-byte representation
b := n.Bytes()
if len(b) < 4 {
// Pad with leading zeros if necessary
padded := make([]byte, 4)
copy(padded[4-len(b):], b)
b = padded
} else if len(b) > 4 {
// Trim to last 4 bytes if it's longer (shouldn't happen due to clamp)
b = b[len(b)-4:]
}
return net.IP(b)
}
// ip6ToInt converts an IPv6 address to a big.Int.
func ip6ToInt(ip net.IP) *big.Int {
ip = ip.To16()
if ip == nil {
return big.NewInt(0)
}
return big.NewInt(0).SetBytes(ip)
}
// intToIP6 converts a big.Int to an IPv6 address.
func intToIP6(n *big.Int) net.IP {
b := n.Bytes()
if len(b) < 16 {
pad := make([]byte, 0, 16-len(b))
b = append(pad, b...)
}
return net.IP(b)
}
// iPAddressToCIDR converts an IP address with a subnet mask to CIDR format.
// Only supports IPv4 mask notation (e.g., "192.168.1.1/255.255.255.0").
func iPAddressToCIDR(ipAddress string) string {
if strings.Contains(ipAddress, "/") {
parts := strings.Split(ipAddress, "/")
ip := parts[0]
mask := parts[1]
if strings.Contains(mask, ".") {
mask = iPMaskStringToCIDR(mask)
}
return ip + "/" + mask
}
return ipAddress
}
// iPMaskStringToCIDR converts a subnet mask string (e.g., "255.255.255.0") to a CIDR prefix length.
func iPMaskStringToCIDR(netmask string) string {
parts := strings.Split(netmask, ".")
if len(parts) != 4 {
return "0"
}
maskBytes := make([]byte, 4)
for i, part := range parts {
val, _ := strconv.Atoi(part)
maskBytes[i] = byte(val)
}
mask := net.IPv4Mask(maskBytes[0], maskBytes[1], maskBytes[2], maskBytes[3])
ones, _ := mask.Size()
return strconv.Itoa(ones)
}
// networkRange calculates the first and last IP in a given network.
// Supports both IPv4 and IPv6.
func networkRange(network *net.IPNet) (net.IP, net.IP) {
netIP := network.IP
mask := network.Mask
if netIP == nil || mask == nil {
return nil, nil
}
ipLen := len(netIP)
if ipLen == net.IPv4len {
netIP = netIP.To4()
} else if ipLen == net.IPv6len {
netIP = netIP.To16()
}
if netIP == nil {
return nil, nil
}
startIP := make(net.IP, len(netIP))
copy(startIP, netIP.Mask(mask))
endIP := make(net.IP, len(startIP))
for i := range startIP {
endIP[i] = startIP[i] | ^mask[i]
}
return startIP, endIP
}
// ===========================================================================
// ============================= IsLocalhostIP ===========================
// ===========================================================================
// IsLocalhostIP checks if the given IP address string (ipStr) is bound to any local network interface.
// It returns true if the IP is found on any interface, false otherwise.
// This function parses the input string as an IP address, iterates over all network interfaces on the host,
// and checks if any of the interface addresses match the target IP.
func IsLocalhostIP(ipStr string) bool {
targetIP := net.ParseIP(ipStr)
if targetIP == nil {
// The input string is not a valid IP address.
return false
}
ifaces, err := net.Interfaces()
if err != nil {
// Failed to retrieve network interfaces.
return false
}
for _, iface := range ifaces {
addrs, err := iface.Addrs()
if err != nil {
// Skip this interface if its addresses cannot be retrieved.
continue
}
for _, addr := range addrs {
switch v := addr.(type) {
case *net.IPNet:
// Check if the IP address of this interface matches the target IP.
if v.IP.Equal(targetIP) {
return true
}
case *net.IPAddr:
// Check if the IP address of this interface matches the target IP.
if v.IP.Equal(targetIP) {
return true
}
}
}
}
// The target IP was not found on any local network interface.
return false
}