377 lines
11 KiB
Go
Executable File
377 lines
11 KiB
Go
Executable File
/*
|
|
Copyright 2021 The KubeSphere 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 util
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/beclab/Olares/cli/pkg/core/logger"
|
|
"github.com/libp2p/go-netroute"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func ParseIp(ip string) []string {
|
|
var availableIPs []string
|
|
// if ip is "1.1.1.1/",trim /
|
|
ip = strings.TrimRight(ip, "/")
|
|
if strings.Contains(ip, "/") == true {
|
|
if strings.Contains(ip, "/32") == true {
|
|
aip := strings.Replace(ip, "/32", "", -1)
|
|
availableIPs = append(availableIPs, aip)
|
|
} else {
|
|
availableIPs = GetAvailableIP(ip)
|
|
}
|
|
} else if strings.Contains(ip, "-") == true {
|
|
ipRange := strings.SplitN(ip, "-", 2)
|
|
availableIPs = GetAvailableIPRange(ipRange[0], ipRange[1])
|
|
} else {
|
|
availableIPs = append(availableIPs, ip)
|
|
}
|
|
return availableIPs
|
|
}
|
|
|
|
func GetAvailableIPRange(ipStart, ipEnd string) []string {
|
|
var availableIPs []string
|
|
|
|
firstIP := net.ParseIP(ipStart)
|
|
endIP := net.ParseIP(ipEnd)
|
|
if firstIP.To4() == nil || endIP.To4() == nil {
|
|
return availableIPs
|
|
}
|
|
firstIPNum := ipToInt(firstIP.To4())
|
|
EndIPNum := ipToInt(endIP.To4())
|
|
pos := int32(1)
|
|
|
|
newNum := firstIPNum
|
|
|
|
for newNum <= EndIPNum {
|
|
availableIPs = append(availableIPs, intToIP(newNum).String())
|
|
newNum = newNum + pos
|
|
}
|
|
return availableIPs
|
|
}
|
|
|
|
func GetAvailableIP(ipAndMask string) []string {
|
|
var availableIPs []string
|
|
|
|
ipAndMask = strings.TrimSpace(ipAndMask)
|
|
ipAndMask = IPAddressToCIDR(ipAndMask)
|
|
_, ipnet, _ := net.ParseCIDR(ipAndMask)
|
|
|
|
firstIP, _ := networkRange(ipnet)
|
|
ipNum := ipToInt(firstIP)
|
|
size := networkSize(ipnet.Mask)
|
|
pos := int32(1)
|
|
max := size - 2 // -1 for the broadcast address, -1 for the gateway address
|
|
|
|
var newNum int32
|
|
for attempt := int32(0); attempt < max; attempt++ {
|
|
newNum = ipNum + pos
|
|
pos = pos%max + 1
|
|
availableIPs = append(availableIPs, intToIP(newNum).String())
|
|
}
|
|
return availableIPs
|
|
}
|
|
|
|
func IPAddressToCIDR(ipAddress string) string {
|
|
if strings.Contains(ipAddress, "/") == true {
|
|
ipAndMask := strings.Split(ipAddress, "/")
|
|
ip := ipAndMask[0]
|
|
mask := ipAndMask[1]
|
|
if strings.Contains(mask, ".") == true {
|
|
mask = IPMaskStringToCIDR(mask)
|
|
}
|
|
return ip + "/" + mask
|
|
} else {
|
|
return ipAddress
|
|
}
|
|
}
|
|
|
|
func IPMaskStringToCIDR(netmask string) string {
|
|
netmaskList := strings.Split(netmask, ".")
|
|
var mint []int
|
|
for _, v := range netmaskList {
|
|
strv, _ := strconv.Atoi(v)
|
|
mint = append(mint, strv)
|
|
}
|
|
myIPMask := net.IPv4Mask(byte(mint[0]), byte(mint[1]), byte(mint[2]), byte(mint[3]))
|
|
ones, _ := myIPMask.Size()
|
|
return strconv.Itoa(ones)
|
|
}
|
|
|
|
func networkRange(network *net.IPNet) (net.IP, net.IP) {
|
|
netIP := network.IP.To4()
|
|
firstIP := netIP.Mask(network.Mask)
|
|
lastIP := net.IPv4(0, 0, 0, 0).To4()
|
|
for i := 0; i < len(lastIP); i++ {
|
|
lastIP[i] = netIP[i] | ^network.Mask[i]
|
|
}
|
|
return firstIP, lastIP
|
|
}
|
|
|
|
func networkSize(mask net.IPMask) int32 {
|
|
m := net.IPv4Mask(0, 0, 0, 0)
|
|
for i := 0; i < net.IPv4len; i++ {
|
|
m[i] = ^mask[i]
|
|
}
|
|
return int32(binary.BigEndian.Uint32(m)) + 1
|
|
}
|
|
|
|
func ipToInt(ip net.IP) int32 {
|
|
return int32(binary.BigEndian.Uint32(ip.To4()))
|
|
}
|
|
|
|
func intToIP(n int32) net.IP {
|
|
b := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(b, uint32(n))
|
|
return net.IP(b)
|
|
}
|
|
|
|
func IsValidIPv4Addr(ip net.IP) bool {
|
|
if ip == nil {
|
|
return false
|
|
}
|
|
ip4 := ip.To4()
|
|
return ip4 != nil && ip4.IsGlobalUnicast()
|
|
}
|
|
|
|
func GetValidIPv4AddrsFromOS() ([]net.IP, error) {
|
|
var ifAddrs []net.Addr
|
|
infs, err := net.Interfaces()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get network interfaces")
|
|
}
|
|
for _, inf := range infs {
|
|
if inf.Flags&net.FlagPointToPoint != 0 {
|
|
continue
|
|
}
|
|
addrs, err := inf.Addrs()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "failed to get addresses of network interface: %s", inf.Name)
|
|
}
|
|
ifAddrs = append(ifAddrs, addrs...)
|
|
}
|
|
var validIfIPs []net.IP
|
|
for _, ifAddr := range ifAddrs {
|
|
if ipNet, ok := ifAddr.(*net.IPNet); ok && IsValidIPv4Addr(ipNet.IP) {
|
|
validIfIPs = append(validIfIPs, ipNet.IP.To4())
|
|
}
|
|
}
|
|
return validIfIPs, nil
|
|
}
|
|
|
|
// GetLocalIP gets the local ip
|
|
// either by getting the "OS_LOCALIP" environment variable
|
|
// or by resolving the local hostname to IP address if the env is not set
|
|
// in both cases, the IP address is matched against the valid addresses of the local interfaces
|
|
// to check if it is an actually bindable address
|
|
// by valid it means the address is a non-loopback IPv4 address
|
|
// the explicitly specified env var takes precedence than the hostname
|
|
// if a valid IP is not found in both cases, a default IP is selected as a fallback
|
|
func GetLocalIP() (net.IP, error) {
|
|
validIfIPs, err := GetValidIPv4AddrsFromOS()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(validIfIPs) == 0 {
|
|
return nil, errors.New("no valid IP can be found from local network interfaces")
|
|
}
|
|
|
|
if envIPStr := os.Getenv("OS_LOCALIP"); envIPStr != "" {
|
|
envIP := net.ParseIP(envIPStr)
|
|
for _, validIP := range validIfIPs {
|
|
if validIP.Equal(envIP) {
|
|
return validIP, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
localHostname, err := os.Hostname()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get local hostname")
|
|
}
|
|
hostIPs, err := net.LookupIP(localHostname)
|
|
|
|
// if the hostname can not be looked up
|
|
// it means the user has not set the /etc/hosts
|
|
// in this case we just choose a default IP
|
|
// otherwise it's an operation error we should return
|
|
// because we don't want to select a different IP than what the user specifies
|
|
if err != nil && !strings.Contains(err.Error(), "no such host") {
|
|
return nil, errors.Wrap(err, "failed to resolve local hostname")
|
|
}
|
|
|
|
// get the IP address of the interface connected to the default gateway
|
|
// by checking the route table
|
|
r, err := netroute.New()
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get the default route")
|
|
}
|
|
_, _, defaultRouteSrcIP, err := r.Route(net.IPv4(0, 0, 0, 0))
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get the default route")
|
|
}
|
|
defaultRouteSrcIP = defaultRouteSrcIP.To4()
|
|
|
|
// the IP address of the default route has the highest priority
|
|
sort.Slice(validIfIPs, func(i, j int) bool {
|
|
if defaultRouteSrcIP.Equal(validIfIPs[i]) {
|
|
return true
|
|
}
|
|
return false
|
|
})
|
|
|
|
for _, validIP := range validIfIPs {
|
|
for _, hostIP := range hostIPs {
|
|
if validIP.Equal(hostIP) {
|
|
return validIP, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return validIfIPs[0], nil
|
|
}
|
|
|
|
// GetPublicIPsFromOS gets a list of public ips by looking at the local network interfaces
|
|
// if any
|
|
func GetPublicIPsFromOS() ([]net.IP, error) {
|
|
var validIfPublicIPs []net.IP
|
|
validIfIPs, err := GetValidIPv4AddrsFromOS()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, ip := range validIfIPs {
|
|
if !ip.IsPrivate() {
|
|
validIfPublicIPs = append(validIfPublicIPs, ip)
|
|
}
|
|
}
|
|
return validIfPublicIPs, nil
|
|
}
|
|
|
|
func GetPublicIPFromAWSIMDS() (net.IP, error) {
|
|
token, err := GetTokenFROMAWSIMDS()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get AWS IMDS token: %v", err)
|
|
}
|
|
url := "http://169.254.169.254/latest/meta-data/public-ipv4"
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build http request: %v", err)
|
|
}
|
|
req.Header.Set("X-aws-ec2-metadata-token", token)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to reach AWS metadata service")
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read response from AWS metadata service")
|
|
}
|
|
logger.Debugf("retrieved public IP info from AWS metadata service: %s", string(body))
|
|
return net.ParseIP(strings.TrimSpace(string(body))), nil
|
|
}
|
|
|
|
func GetTokenFROMAWSIMDS() (string, error) {
|
|
url := "http://169.254.169.254/latest/api/token"
|
|
req, err := http.NewRequest(http.MethodPut, url, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to build http request: %v", err)
|
|
}
|
|
req.Header.Set("X-aws-ec2-metadata-token-ttl-seconds", "600")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to reach AWS metadata service")
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to read response from AWS metadata service")
|
|
}
|
|
return strings.TrimSpace(string(body)), nil
|
|
}
|
|
|
|
func GetPublicIPFromTencentIMDS() (net.IP, error) {
|
|
url := "http://metadata.tencentyun.com/latest/meta-data/public-ipv4"
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build http request: %v", err)
|
|
}
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to reach Tencent metadata service")
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read response from Tencent metadata service")
|
|
}
|
|
logger.Debugf("retrieved public IP info from Tencent metadata service: %s", string(body))
|
|
return net.ParseIP(strings.TrimSpace(string(body))), nil
|
|
}
|
|
|
|
func GetPublicIPFromAliyunIMDS() (net.IP, error) {
|
|
token, err := GetTokenFromAliyunIMDS()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get Aliyun ECS IMDS token: %v", err)
|
|
}
|
|
url := "http://100.100.100.200/latest/meta-data/public-ipv4"
|
|
req, err := http.NewRequest(http.MethodGet, url, nil)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to build http request: %v", err)
|
|
}
|
|
req.Header.Set("X-aliyun-ecs-metadata-token", token)
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to reach Aliyun metadata service")
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to read response from Aliyun metadata service")
|
|
}
|
|
logger.Debugf("retrieved public IP info from Aliyun metadata service: %s", string(body))
|
|
return net.ParseIP(strings.TrimSpace(string(body))), nil
|
|
}
|
|
|
|
func GetTokenFromAliyunIMDS() (string, error) {
|
|
url := "http://100.100.100.200/latest/api/token"
|
|
req, err := http.NewRequest(http.MethodPut, url, nil)
|
|
if err != nil {
|
|
return "", fmt.Errorf("failed to build http request: %v", err)
|
|
}
|
|
req.Header.Set("X-aliyun-ecs-metadata-token-ttl-seconds", "600")
|
|
resp, err := http.DefaultClient.Do(req)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to reach Aliyun metadata service")
|
|
}
|
|
defer resp.Body.Close()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "failed to read response from Aliyun metadata service")
|
|
}
|
|
return strings.TrimSpace(string(body)), nil
|
|
}
|