123
ipx/ip.go
Normal file
123
ipx/ip.go
Normal file
@@ -0,0 +1,123 @@
|
||||
package ipx
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func RemoteAddr(r *http.Request, mustPublic bool) string {
|
||||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
for _, key := range []string{"X-Forwarded-For", "X-Real-IP", "X-Appengine-Remote-Addr"} {
|
||||
value := r.Header.Get(key)
|
||||
if value != "" {
|
||||
for _, item := range strings.Split(value, ",") {
|
||||
var ip string
|
||||
if strings.ContainsRune(item, ':') {
|
||||
if host, _, err := net.SplitHostPort(strings.TrimSpace(item)); err != nil {
|
||||
continue
|
||||
} else {
|
||||
ip = host
|
||||
}
|
||||
} else {
|
||||
ip = strings.TrimSpace(item)
|
||||
}
|
||||
|
||||
if mustPublic {
|
||||
if v, e := IsPublic(ip); e != nil && v {
|
||||
return ip
|
||||
}
|
||||
} else {
|
||||
return ip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return r.RemoteAddr
|
||||
}
|
||||
|
||||
func LocalAddr() string {
|
||||
addresses, err := net.InterfaceAddrs()
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
addr := ""
|
||||
for _, address := range addresses {
|
||||
if ipNet, ok := address.(*net.IPNet); ok &&
|
||||
!ipNet.IP.IsLoopback() &&
|
||||
!ipNet.IP.IsPrivate() &&
|
||||
!ipNet.IP.IsLinkLocalUnicast() {
|
||||
if ipNet.IP.To4() != nil {
|
||||
addr = ipNet.IP.String()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if addr == "" {
|
||||
for _, address := range []string{"114.114.114.114:53", "8.8.8.8:53"} {
|
||||
var conn net.Conn
|
||||
conn, err = net.Dial("udp", address)
|
||||
if err == nil {
|
||||
conn.Close()
|
||||
localAddr := conn.LocalAddr().(*net.UDPAddr)
|
||||
addr = strings.Split(localAddr.String(), ":")[0]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return addr
|
||||
}
|
||||
|
||||
func IsPrivate(ip string) (v bool, err error) {
|
||||
addr := net.ParseIP(ip)
|
||||
if addr == nil {
|
||||
err = fmt.Errorf("ipx: %s address is invalid", ip)
|
||||
} else {
|
||||
v = addr.IsLoopback() || addr.IsPrivate() || addr.IsLinkLocalUnicast()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func IsPublic(ip string) (v bool, err error) {
|
||||
v, err = IsPrivate(ip)
|
||||
v = err == nil && !v
|
||||
return
|
||||
}
|
||||
|
||||
func Number(ip string) (uint, error) {
|
||||
addr := net.ParseIP(ip)
|
||||
if addr == nil {
|
||||
return 0, fmt.Errorf("ipx: %s is invalid ip", ip)
|
||||
}
|
||||
return uint(addr[3]) | uint(addr[2])<<8 | uint(addr[1])<<16 | uint(addr[0])<<24, nil
|
||||
}
|
||||
|
||||
func Random() string {
|
||||
size := 4
|
||||
ip := make([]byte, size)
|
||||
for i := 0; i < size; i++ {
|
||||
ip[i] = byte(rand.Intn(256))
|
||||
}
|
||||
return net.IP(ip).To4().String()
|
||||
}
|
||||
|
||||
func String(ip uint) (string, error) {
|
||||
if ip > math.MaxUint32 {
|
||||
return "", fmt.Errorf("ipx: %d is not valid ipv4", ip)
|
||||
}
|
||||
|
||||
addr := make(net.IP, net.IPv4len)
|
||||
addr[0] = byte(ip >> 24)
|
||||
addr[1] = byte(ip >> 16)
|
||||
addr[2] = byte(ip >> 8)
|
||||
addr[3] = byte(ip)
|
||||
return addr.String(), nil
|
||||
}
|
||||
99
ipx/ip_test.go
Normal file
99
ipx/ip_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package ipx
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/assert"
|
||||
"net/http"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRemoteAddr(t *testing.T) {
|
||||
request := &http.Request{
|
||||
Header: map[string][]string{},
|
||||
}
|
||||
testCases := []struct {
|
||||
tag string
|
||||
headers map[string][]string
|
||||
mustPublic bool
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"t1", map[string][]string{
|
||||
"X-Real-IP": {"127.0.0.1"},
|
||||
"X-Forwarded-For": {"127.0.0.1"},
|
||||
}, false, "127.0.0.1",
|
||||
},
|
||||
{
|
||||
"t2", map[string][]string{
|
||||
"X-Real-IP": {"127.0.0.1:8080"},
|
||||
"X-Forwarded-For": {"127.0.0.1:8080"},
|
||||
}, false, "127.0.0.1",
|
||||
},
|
||||
{
|
||||
"t3", map[string][]string{
|
||||
"X-Real-IP": {"127.0.0.1"},
|
||||
"X-Forwarded-For": {"127.0.0.1"},
|
||||
}, true, "",
|
||||
},
|
||||
{
|
||||
"t4", map[string][]string{
|
||||
"X-Real-IP": {"127.0.0.1:8080"},
|
||||
"X-Forwarded-For": {"127.0.0.1:8080"},
|
||||
}, true, "",
|
||||
},
|
||||
{
|
||||
"t5", map[string][]string{
|
||||
"X-Real-IP": {"::1"},
|
||||
"X-Forwarded-For": {"::1"},
|
||||
}, true, "",
|
||||
},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
request.Header = testCase.headers
|
||||
addr := RemoteAddr(request, testCase.mustPublic)
|
||||
assert.Equal(t, testCase.expected, addr, testCase.tag)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocalAddr(t *testing.T) {
|
||||
ip := LocalAddr()
|
||||
if ip == "" {
|
||||
t.Error("LocalAddr() return empty value")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPrivate(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tag string
|
||||
ip string
|
||||
expected bool
|
||||
hasError bool
|
||||
}{
|
||||
{"t1", "127.0.0.1", true, false},
|
||||
{"t2", "::1", true, false},
|
||||
{"t3", "xxx", false, true},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
v, err := IsPrivate(testCase.ip)
|
||||
assert.Equal(t, testCase.expected, v, testCase.tag)
|
||||
assert.Equal(t, testCase.hasError, err != nil, testCase.tag+" error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsPublic(t *testing.T) {
|
||||
testCases := []struct {
|
||||
tag string
|
||||
ip string
|
||||
expected bool
|
||||
hasError bool
|
||||
}{
|
||||
{"t1", "127.0.0.1", false, false},
|
||||
{"t2", "::1", false, false},
|
||||
{"t3", "xxx", false, true},
|
||||
{"t4", "120.228.142.126", true, false},
|
||||
}
|
||||
for _, testCase := range testCases {
|
||||
v, err := IsPublic(testCase.ip)
|
||||
assert.Equal(t, testCase.expected, v, testCase.tag)
|
||||
assert.Equal(t, testCase.hasError, err != nil, testCase.tag+" error")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user