diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index cc37c67..5539551 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -17,8 +17,8 @@ jobs: with: submodules: true - - name: Install Docker - run: curl -fsSL https://get.docker.com | sh + # - name: Install Docker + # run: curl -fsSL https://get.docker.com | sh - name: Deploy with Docker run: docker compose up --build --force-recreate -d diff --git a/go.mod b/go.mod index 8a4860f..5937067 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.22 require ( github.com/bytedance/sonic v1.12.1 // indirect github.com/bytedance/sonic/loader v0.2.0 // indirect + github.com/c-robinson/iplib/v2 v2.0.5 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/gabriel-vasile/mimetype v1.4.5 // indirect @@ -18,6 +19,9 @@ require ( github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.8 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/likexian/gokit v0.25.15 // indirect + github.com/likexian/whois v1.15.4 // indirect + github.com/likexian/whois-parser v1.24.19 // indirect github.com/mattn/go-isatty v0.0.20 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect @@ -31,4 +35,5 @@ require ( golang.org/x/text v0.17.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + lukechampine.com/uint128 v1.3.0 // indirect ) diff --git a/go.sum b/go.sum index 0f1d275..5845eec 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,8 @@ github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKz github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/c-robinson/iplib/v2 v2.0.5 h1:puzVVzXBv9HjZqj8muiSoQwiMvkMwE9dLUaWCZ52S0g= +github.com/c-robinson/iplib/v2 v2.0.5/go.mod h1:ZfjJB+pR8Guy++ylL+OgGzN9dd4jWAE1CszvwVrjlJI= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -35,6 +37,12 @@ github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/likexian/gokit v0.25.15 h1:QjospM1eXhdMMHwZRpMKKAHY/Wig9wgcREmLtf9NslY= +github.com/likexian/gokit v0.25.15/go.mod h1:S2QisdsxLEHWeD/XI0QMVeggp+jbxYqUxMvSBil7MRg= +github.com/likexian/whois v1.15.4 h1:r5En62c+S9HKFgJtdh2WsdmRGTcxE4WUtGBdZkSBXmM= +github.com/likexian/whois v1.15.4/go.mod h1:rXFTPcQdNlPQBJCQpPWTSIDGzzmgKBftmhdOOcLpwXk= +github.com/likexian/whois-parser v1.24.19 h1:vT8lWhnV8ogkdaYLyef6IvE5VTHVCwlUDG5BUXCx06k= +github.com/likexian/whois-parser v1.24.19/go.mod h1:rAtaofg2luol09H+ogDzGIfcG8ig1NtM5R16uQADDz4= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -141,5 +149,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +lukechampine.com/uint128 v1.3.0 h1:cDdUVfRwDUDovz610ABgFD17nXD4/uDgVHl2sC3+sbo= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/pkg/protocol/requests.go b/internal/pkg/protocol/requests.go new file mode 100644 index 0000000..c885446 --- /dev/null +++ b/internal/pkg/protocol/requests.go @@ -0,0 +1,5 @@ +package protocol + +type ISPRequest struct { + Address string `json:"address"` +} diff --git a/internal/pkg/response/response.go b/internal/pkg/protocol/responses.go similarity index 57% rename from internal/pkg/response/response.go rename to internal/pkg/protocol/responses.go index fc4a459..b29f793 100644 --- a/internal/pkg/response/response.go +++ b/internal/pkg/protocol/responses.go @@ -1,6 +1,10 @@ -package response +package protocol type MyIPResponse struct { Address string `json:"address"` Version string `json:"version"` } + +type ISPResponse struct { + ISP string `json:"isp"` +} diff --git a/internal/pkg/protocol/types.go b/internal/pkg/protocol/types.go new file mode 100644 index 0000000..da1fda1 --- /dev/null +++ b/internal/pkg/protocol/types.go @@ -0,0 +1,17 @@ +package protocol + +import ( + "net" +) + +type IPVersion int32 + +const ( + IPv4 IPVersion = 4 + IPv6 IPVersion = 6 +) + +type IP struct { + Address net.IP + Version IPVersion +} diff --git a/internal/pkg/utils/network.go b/internal/pkg/utils/network.go index 73e7f15..50aa797 100644 --- a/internal/pkg/utils/network.go +++ b/internal/pkg/utils/network.go @@ -1,25 +1,87 @@ package utils import ( + "encoding/hex" "errors" + "fmt" + "ipv6-test-node/internal/pkg/protocol" "net" + "strings" ) -const ( - IPv4 = 4 - IPv6 = 6 -) - -func GetIPVersion(ip string) (int, error) { - parsedIP := net.ParseIP(ip) - if parsedIP == nil { +func GetIPVersion(ip net.IP) (protocol.IPVersion, error) { + if ip == nil { return 0, errors.New("invalid IP address") } - if parsedIP.To4() != nil { - return IPv4, nil - } else if parsedIP.To16() != nil { - return IPv6, nil + if ip.To4() != nil { + return protocol.IPv4, nil + } else if ip.To16() != nil { + return protocol.IPv6, nil } else { return 0, errors.New("invalid IP address") } } + +func GetASN(ip protocol.IP) (string, error) { + var query string + if ip.Version == 4 { + query = fmt.Sprintf("%sorigin.asn.cymru.com", reverseIPv4(ip.Address)) + } else if ip.Version == 6 { + query = fmt.Sprintf("%sorigin6.asn.cymru.com", reverseIPv6(ip.Address)) + } + + txtRecords, err := net.LookupTXT(query) + if err != nil { + return "", err + } + + if len(txtRecords) > 0 { + parts := strings.Split(txtRecords[0], " | ") + if len(parts) > 0 { + return "AS" + parts[0], nil + } + } + + return "", fmt.Errorf("ASN not found") +} + +func GetISPName(asn string) (string, error) { + query := fmt.Sprintf("%s.asn.cymru.com", asn) + txtRecords, err := net.LookupTXT(query) + if err != nil { + return "", err + } + + if len(txtRecords) > 0 { + parts := strings.Split(txtRecords[0], " | ") + if len(parts) > 0 { + return parts[4], nil + } + } + + return "", fmt.Errorf("ISP not found") +} + +func ToIP(address net.IP) protocol.IP { + version, err := GetIPVersion(address) + if err != nil { + return protocol.IP{} + } + return protocol.IP{Address: address, Version: version} +} + +func reverseIPv4(ip net.IP) string { + return fmt.Sprintf("%d.%d.%d.%d.", ip[3], ip[2], ip[1], ip[0]) +} + +func reverseIPv6(ip net.IP) string { + var dst []byte + dst = make([]byte, hex.EncodedLen(len(ip))) + hex.Encode(dst, ip) + + var reversed string + for i := len(dst) - 1; i >= 0; i-- { + reversed = reversed + string(dst[i]) + "." + } + return reversed +} diff --git a/internal/router/router.go b/internal/router/router.go index 7452272..5223038 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -12,6 +12,7 @@ func Register() *gin.Engine { ipGroup := g.Group("ip") ipGroup.GET("myip", ip.MyIP) + ipGroup.GET("isp", ip.ISP) return g } diff --git a/internal/router/routes/ip/isp.go b/internal/router/routes/ip/isp.go new file mode 100644 index 0000000..8f47c22 --- /dev/null +++ b/internal/router/routes/ip/isp.go @@ -0,0 +1,29 @@ +package ip + +import ( + "github.com/gin-gonic/gin" + "ipv6-test-node/internal/pkg/protocol" + "ipv6-test-node/internal/pkg/utils" + "net" + "net/http" +) + +func ISP(c *gin.Context) { + var req *protocol.ISPRequest + err := c.BindJSON(&req) + if err != nil { + c.AbortWithStatus(http.StatusInternalServerError) + return + } + + asn, err := utils.GetASN(utils.ToIP(net.ParseIP(req.Address))) + if err != nil { + return + } + name, err := utils.GetISPName(asn) + if err != nil { + return + } + + c.JSON(http.StatusOK, protocol.ISPResponse{ISP: name}) +} diff --git a/internal/router/routes/ip/myip.go b/internal/router/routes/ip/myip.go index 5e4b0f4..f741f14 100644 --- a/internal/router/routes/ip/myip.go +++ b/internal/router/routes/ip/myip.go @@ -2,19 +2,18 @@ package ip import ( "github.com/gin-gonic/gin" - "ipv6-test-node/internal/pkg/response" + "ipv6-test-node/internal/pkg/protocol" "ipv6-test-node/internal/pkg/utils" + "net" "net/http" "strconv" ) func MyIP(c *gin.Context) { - version, err := utils.GetIPVersion(c.ClientIP()) - if err != nil { - return - } - c.JSON(http.StatusOK, response.MyIPResponse{ + ip := utils.ToIP(net.ParseIP(c.ClientIP())) + + c.JSON(http.StatusOK, protocol.MyIPResponse{ Address: c.ClientIP(), - Version: strconv.Itoa(version), + Version: strconv.Itoa(int(ip.Version)), }) }