diff --git a/common/util/ip.go b/common/util/ip.go new file mode 100644 index 000000000..6f5cc3455 --- /dev/null +++ b/common/util/ip.go @@ -0,0 +1,115 @@ +package util + +import ( + "errors" + "fmt" + "io/ioutil" + "net" + "net/http" + "regexp" +) + +type ( + IPUtil struct { + } + IPUtilInterface interface { + GetPublicIP() (ip net.IP, err error) + GetPublicIPDYNDNS() (ip net.IP, err error) + IsDomain(address string) bool + IsPublicIP(ip net.IP) bool + } +) + +// GetPublicIP allowing to get own external/public ip, +// Work perfectly on server not on local machine / laptop / PC +// more accurate if getting from request header https://golangcode.com/get-the-request-ip-addr/ +func (ipu *IPUtil) GetPublicIP() (net.IP, error) { + faces, err := net.Interfaces() + if err != nil { + return nil, err + } + for _, face := range faces { + if face.Flags&net.FlagUp == 0 { + continue // interface down + } + if face.Flags&net.FlagLoopback != 0 { + continue // loopback interface + } + addrs, err := face.Addrs() + if err != nil { + return nil, err + } + for _, addr := range addrs { + var ip net.IP + switch v := addr.(type) { + case *net.IPNet: + ip = v.IP + case *net.IPAddr: + ip = v.IP + } + if ip == nil || ip.IsLoopback() { + continue + } + ip = ip.To4() + if ip == nil { + continue // not an ipv4 address + } + return ip, nil + } + } + return nil, errors.New("fail caused the internet connection") +} + +// GetPublicIPDYNDNS allowing to get own public ip via http://checkip.dyndns.org +func (ipu *IPUtil) GetPublicIPDYNDNS() (net.IP, error) { + var ( + err error + bt []byte + resp *http.Response + rgx = regexp.MustCompile(`(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}`) + ) + resp, err = http.Get("http://checkip.dyndns.org") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + bt, err = ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // unfortunately the response is tag, need to get the ip only via regexp + ipStr := rgx.FindAllString(string(bt), -1) + ip := net.ParseIP(ipStr[0]) + if ip != nil { + return ip, nil + } + return nil, fmt.Errorf("invalid ip address") +} + +// IsDomain willing to check what kinda address given +func (ipu *IPUtil) IsDomain(address string) bool { + addr := net.ParseIP(address) + return addr == nil +} + +// IsPublicIP make sure that ip is a public ip or not +func (ipu *IPUtil) IsPublicIP(ip net.IP) bool { + if ip.IsLoopback() || ip.IsLinkLocalMulticast() || ip.IsLinkLocalUnicast() { + return false + } + if ip4 := ip.To4(); ip4 != nil { + switch { + case ip4[0] == 10: + return false + case ip4[0] == 172 && ip4[1] >= 16 && ip4[1] <= 31: + return false + case ip4[0] == 192 && ip4[1] == 168: + return false + default: + return true + } + } + return false +} diff --git a/common/util/ip_test.go b/common/util/ip_test.go new file mode 100644 index 000000000..94760e24a --- /dev/null +++ b/common/util/ip_test.go @@ -0,0 +1,120 @@ +package util + +import ( + "net" + "testing" +) + +func TestGetPublicIP(t *testing.T) { + tests := []struct { + name string + wantErr bool + }{ + { + name: "WantSuccess", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ipu := &IPUtil{} + got, err := ipu.GetPublicIP() + if (err != nil) != tt.wantErr { + t.Errorf("GetPublicIP() error = %v, wantErr %v", err, tt.wantErr) + return + } + if ipu.IsPublicIP(got) { + t.Errorf("GetPublicIP() got = %v ", got) + } + }) + } +} + +func TestIsPublicIP(t *testing.T) { + type args struct { + IP net.IP + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "WantPublicIP", + args: args{ + IP: net.ParseIP("172.104.34.10"), + }, + want: true, + }, + { + name: "WantPrivateIP", + args: args{ + IP: net.ParseIP("192.168.10.1"), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ipu := &IPUtil{} + if got := ipu.IsPublicIP(tt.args.IP); got != tt.want { + t.Errorf("IsPublicIP() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestIsDomain(t *testing.T) { + type args struct { + address string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "WantDomain", + args: args{ + address: "zoobc.com", + }, + want: true, + }, + { + name: "WantIP", + args: args{ + address: "172.104.34.10", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ipu := &IPUtil{} + if got := ipu.IsDomain(tt.args.address); got != tt.want { + t.Errorf("IsDomain() = %v, want %v", got, tt.want) + } + }) + } +} + +func TestGetPublicIPDYNDNS(t *testing.T) { + tests := []struct { + name string + wantErr bool + }{ + { + name: "WantSuccess", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + ipu := &IPUtil{} + got, err := ipu.GetPublicIPDYNDNS() + if (err != nil) != tt.wantErr { + t.Errorf("GetPublicIPDYNDNS() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !ipu.IsPublicIP(got) { // perhaps is public ip + t.Errorf("GetPublicIPDYNDNS() got = %v", got) + } + }) + } +}