Compare commits

...

14 Commits

Author SHA1 Message Date
sjlleo
433c8656a1 update: 新增 route-path 模块 2022-05-15 11:12:13 +08:00
sjlleo
1542cb4b07 update: 骨干网判断优化 2022-05-14 16:55:50 +08:00
sjlleo
06ee8f7373 add: 新增路由报告模块接口 2022-05-14 16:55:28 +08:00
sjlleo
6792bafb02 update: 整理 2022-05-14 08:48:49 +08:00
sjlleo
70305caa1c update: 改Switch判断 (Git还我头像qwq) 2022-05-14 08:38:02 +08:00
sjlleo
671ad82780 update: 完善阿里云、腾讯云的内网识别范围 2022-05-14 08:28:30 +08:00
sjlleo
e62575beba update: 完善table显示,对阿里云、腾讯云内网的识别 2022-05-14 08:28:00 +08:00
sjlleo
d92a1e10d3 update: 完善参数提醒 2022-05-14 08:27:13 +08:00
sjlleo
971d68f93f update: 架构整理 2022-05-13 21:44:43 +08:00
sjlleo
f765dbafae update: go mod 2022-05-13 20:44:04 +08:00
zhshch2002
ea7feab2f9 update: build.yml trigger condition 2022-05-13 16:55:36 +08:00
zhshch2002
e941eaa167 update: build.yml Token 2022-05-13 16:43:25 +08:00
zhshch2002
46e32d697d fix: "lab 实验室"语意重复 2022-05-13 16:41:57 +08:00
sjlleo
97578e40f7 update: 修复一个作者名称的异常 2022-05-13 16:37:31 +08:00
10 changed files with 291 additions and 53 deletions

View File

@@ -1,10 +1,11 @@
on:
push: # 每次 push 的时候触发
push: # 每次带有 tag 的 push 候触发
tags:
- 'v*'
name: Build Release
jobs:
release:
if: startsWith(github.ref, 'refs/tags/') # 只有这次 Commit 是 创建 Tag 时,才进行后续发布操作
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master # checkout 代码
@@ -24,4 +25,4 @@ jobs:
dist/nexttrace_linux_amd64
dist/nexttrace_linux_arm64
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}

6
go.mod
View File

@@ -16,9 +16,9 @@ require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/fatih/color v1.13.0
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rodaine/table v1.0.1 // indirect
github.com/stretchr/testify v1.7.1 // indirect
github.com/tidwall/gjson v1.14.1 // indirect
github.com/rodaine/table v1.0.1
github.com/stretchr/testify v1.7.1
github.com/tidwall/gjson v1.14.1
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.0 // indirect
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 // indirect

4
go.sum
View File

@@ -9,6 +9,7 @@ github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@@ -38,13 +39,12 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e h1:fLOSk5Q00efkSvAm+4xcoXD+RRmLmmulPn5I3Y9F2EM=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6 h1:nonptSpoQ4vQjyraW20DXPAglgQfVnM9ZC6MmNLMR60=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

37
main.go
View File

@@ -11,6 +11,7 @@ import (
"github.com/xgadget-lab/nexttrace/methods/udp"
"github.com/xgadget-lab/nexttrace/util"
"github.com/xgadget-lab/nexttrace/util/printer"
"github.com/xgadget-lab/nexttrace/util/reporter"
)
var tcpSYNFlag = flag.Bool("T", false, "Use TCP SYN for tracerouting (default port is 80 in TCP, 53 in UDP)")
@@ -19,7 +20,9 @@ var numMeasurements = flag.Int("q", 3, "Set the number of probes per each hop.")
var parallelRequests = flag.Int("r", 18, "Set ParallelRequests number. It should be 1 when there is a multi-routing.")
var maxHops = flag.Int("m", 30, "Set the max number of hops (max TTL to be reached).")
var dataOrigin = flag.String("d", "LeoMoeAPI", "Choose IP Geograph Data Provider [LeoMoeAPI, IP.SB, IPInfo, IPInsight]")
var displayMode = flag.String("displayMode", "table", "Choose The Display Mode [table, Besttrace]")
var displayMode = flag.String("displayMode", "table", "Choose The Display Mode [table, classic]")
var rdnsenable = flag.Bool("rdns", false, "Set whether rDNS will be display")
var routeReport = flag.Bool("report", false, "Auto-Generate a Route-Path Report by TCPTraceroute")
func main() {
printer.PrintCopyRight()
@@ -40,12 +43,17 @@ func main() {
if err != nil {
fmt.Println("请赋予 sudo (root) 权限运行本程序")
} else {
if *displayMode == "Besttrace" {
printer.TraceroutePrinter(ip, *res, *dataOrigin)
} else if *displayMode == "table" {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
if *routeReport {
r := reporter.New(*res, ip.String())
r.Print()
} else {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
util.Printer(&util.PrinterConfig{
IP: ip,
DisplayMode: *displayMode,
DataOrigin: *dataOrigin,
Rdnsenable: *rdnsenable,
Results: *res,
})
}
}
@@ -65,12 +73,17 @@ func main() {
if err != nil {
fmt.Println("请赋予 sudo (root) 权限运行本程序")
} else {
if *displayMode == "Besttrace" {
printer.TraceroutePrinter(ip, *res, *dataOrigin)
} else if *displayMode == "table" {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
if *routeReport {
r := reporter.New(*res, ip.String())
r.Print()
} else {
printer.TracerouteTablePrinter(ip, *res, *dataOrigin)
util.Printer(&util.PrinterConfig{
IP: ip,
DisplayMode: *displayMode,
DataOrigin: *dataOrigin,
Rdnsenable: *rdnsenable,
Results: *res,
})
}
}
}
@@ -80,7 +93,7 @@ func flagApply() string {
flag.Parse()
ipArg := flag.Args()
if flag.NArg() != 1 {
fmt.Println("Args Error\nUsage : ./bettertrace [-T] [-d <dataOrigin> ] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
fmt.Println("Args Error\nUsage : ./nexttrace [-T] [-rdns] [-displayMode <displayMode>] [-d <dataOrigin> ] [ -m <hops> ] [ -p <port> ] [ -q <probes> ] [ -r <parallelrequests> ] <hostname>")
os.Exit(2)
}
return ipArg[0]

View File

@@ -1,5 +1,12 @@
package util
import (
"net"
"github.com/xgadget-lab/nexttrace/methods"
"github.com/xgadget-lab/nexttrace/util/printer"
)
type IPGeoData struct {
Asnumber string `json:"asnumber"`
Country string `json:"country"`
@@ -9,3 +16,24 @@ type IPGeoData struct {
Owner string `json:"owner"`
Isp string `json:"isp"`
}
type PrinterConfig struct {
IP net.IP
DataOrigin string
DisplayMode string
Rdnsenable bool
Results map[uint16][]methods.TracerouteHop
}
func Printer(config *PrinterConfig) {
switch config.DisplayMode {
case "table":
printer.TracerouteTablePrinter(config.IP, config.Results, config.DataOrigin, config.Rdnsenable)
case "classic":
printer.TraceroutePrinter(config.IP, config.Results, config.DataOrigin, config.Rdnsenable)
case "json":
//TracerouteJSONPrinter(config.Results, config.DataOrigin)
default:
printer.TraceroutePrinter(config.IP, config.Results, config.DataOrigin, config.Rdnsenable)
}
}

View File

@@ -6,7 +6,7 @@ import (
)
func PrintCopyRight() {
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab 实验室 zhsh (xzhsh.ch) & leo (leo.moe)")
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab zhshch (xzhsh.ch) & leo (leo.moe)")
}
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {

View File

@@ -11,11 +11,11 @@ import (
var dataOrigin string
func TraceroutePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string) {
func TraceroutePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string, rdnsenable bool) {
for hi := uint16(1); hi < 30; hi++ {
fmt.Print(hi)
for _, v := range res[hi] {
hopPrinter(v)
hopPrinter(v, rdnsenable)
if v.Address != nil && ip.String() == v.Address.String() {
hi = 31
}
@@ -23,7 +23,7 @@ func TraceroutePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOr
}
}
func hopPrinter(v2 methods.TracerouteHop) {
func hopPrinter(v2 methods.TracerouteHop, rdnsenable bool) {
if v2.Address == nil {
fmt.Println("\t*")
} else {
@@ -32,16 +32,17 @@ func hopPrinter(v2 methods.TracerouteHop) {
ipStr := v2.Address.String()
// TODO: 判断 err 返回并且在CLI终端提示错误
if dataOrigin == "LeoMoeAPI" {
// 判断 err 返回并且在CLI终端提示错误
switch dataOrigin {
case "LeoMoeAPI":
iPGeoData, err = ipgeo.LeoIP(ipStr)
} else if dataOrigin == "IP.SB" {
case "IP.SB":
iPGeoData, err = ipgeo.IPSB(ipStr)
} else if dataOrigin == "IPInfo" {
case "IPInfo":
iPGeoData, err = ipgeo.IPInfo(ipStr)
} else if dataOrigin == "IPInsight" {
case "IPInsight":
iPGeoData, err = ipgeo.IPInSight(ipStr)
} else {
default:
iPGeoData, err = ipgeo.LeoIP(ipStr)
}
@@ -52,14 +53,19 @@ func hopPrinter(v2 methods.TracerouteHop) {
geo = formatIpGeoData(ipStr, iPGeoData)
}
ptr, err := net.LookupAddr(ipStr)
txt := "\t"
if err != nil {
txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
if rdnsenable {
ptr, err := net.LookupAddr(ipStr)
if err != nil {
txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
} else {
txt += fmt.Sprint(ptr[0], " (", ipStr, ") ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
}
} else {
txt += fmt.Sprint(ptr[0], " (", ipStr, ") ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
txt += fmt.Sprint(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
}
fmt.Println(txt)
}
}
@@ -74,26 +80,35 @@ func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
}
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
if strings.HasPrefix(ip, "9.31.") || strings.HasPrefix(ip, "11.72.") {
if strings.HasPrefix(ip, "9.") {
res = append(res, "局域网", "腾讯云")
} else if strings.HasPrefix(ip, "11.13.") {
} else if strings.HasPrefix(ip, "11.") {
res = append(res, "局域网", "阿里云")
} else if data.Country == "" {
res = append(res, "局域网")
} else {
// 有些IP的归属信息为空这个时候将ISP的信息填入
if data.Owner == "" {
data.Owner = data.Isp
}
if data.District != "" {
data.City = data.City + ", " + data.District
}
res = append(res, data.Country)
if data.Prov == "" && data.City == "" {
// anyCast或是骨干网数据不应该有国家信息
data.Owner = data.Owner + ", " + data.Owner
} else {
// 非骨干网正常填入IP的国家信息数据
res = append(res, data.Country)
}
if data.Prov != "" {
res = append(res, data.Prov)
}
if data.City != "" {
res = append(res, data.City)
}
if data.Owner != "" {
res = append(res, data.Owner)
}

View File

@@ -3,6 +3,7 @@ package printer
import (
"fmt"
"net"
"strings"
"github.com/fatih/color"
"github.com/rodaine/table"
@@ -22,12 +23,12 @@ type rowData struct {
Owner string
}
func TracerouteTablePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string) {
func TracerouteTablePrinter(ip net.IP, res map[uint16][]methods.TracerouteHop, dataOrigin string, rdnsenable bool) {
// 初始化表格
tbl := New()
for hi := uint16(1); hi < 30; hi++ {
for _, v := range res[hi] {
data := tableDataGenerator(v)
data := tableDataGenerator(v, rdnsenable)
tbl.AddRow(data.Hop, data.IP, data.Latency, data.Asnumber, data.Country, data.Prov, data.City, data.Owner)
if v.Address != nil && ip.String() == v.Address.String() {
hi = 31
@@ -48,7 +49,7 @@ func New() table.Table {
return tbl
}
func tableDataGenerator(v2 methods.TracerouteHop) *rowData {
func tableDataGenerator(v2 methods.TracerouteHop, rdnsenable bool) *rowData {
if v2.Address == nil {
return &rowData{
Hop: int64(v2.TTL),
@@ -62,29 +63,51 @@ func tableDataGenerator(v2 methods.TracerouteHop) *rowData {
ipStr := v2.Address.String()
if strings.HasPrefix(ipStr, "9.") {
return &rowData{
Hop: int64(v2.TTL),
IP: ipStr,
Latency: lantency,
Country: "局域网",
Owner: "腾讯云",
}
} else if strings.HasPrefix(ipStr, "11.") {
return &rowData{
Hop: int64(v2.TTL),
IP: ipStr,
Latency: lantency,
Country: "局域网",
Owner: "阿里云",
}
}
// TODO: 判断 err 返回并且在CLI终端提示错误
if dataOrigin == "LeoMoeAPI" {
switch dataOrigin {
case "LeoMoeAPI":
iPGeoData, err = ipgeo.LeoIP(ipStr)
} else if dataOrigin == "IP.SB" {
case "IP.SB":
iPGeoData, err = ipgeo.IPSB(ipStr)
} else if dataOrigin == "IPInfo" {
case "IPInfo":
iPGeoData, err = ipgeo.IPInfo(ipStr)
} else if dataOrigin == "IPInsight" {
case "IPInsight":
iPGeoData, err = ipgeo.IPInSight(ipStr)
} else {
default:
iPGeoData, err = ipgeo.LeoIP(ipStr)
}
ptr, err_LookupAddr := net.LookupAddr(ipStr)
if rdnsenable {
ptr, err_LookupAddr := net.LookupAddr(ipStr)
if err_LookupAddr != nil {
IP = fmt.Sprint(ipStr)
} else {
IP = fmt.Sprint(ptr[0], " (", ipStr, ") ")
}
} else {
IP = fmt.Sprint(ipStr)
}
lantency = fmt.Sprintf("%.2fms", v2.RTT.Seconds()*1000)
if err_LookupAddr != nil {
IP = fmt.Sprint(ipStr)
} else {
IP = fmt.Sprint(ptr[0], " (", ipStr, ") ")
}
if iPGeoData.Owner == "" {
iPGeoData.Owner = iPGeoData.Isp
}

127
util/reporter/reporter.go Normal file
View File

@@ -0,0 +1,127 @@
package reporter
import (
"errors"
"fmt"
"net"
"strings"
"github.com/xgadget-lab/nexttrace/ipgeo"
"github.com/xgadget-lab/nexttrace/methods"
)
type Reporter interface {
Print()
}
func New(rs map[uint16][]methods.TracerouteHop, ip string) Reporter {
experimentTag()
r := reporter{
routeResult: rs,
targetIP: ip,
}
return &r
}
type reporter struct {
targetIP string
routeReport map[uint16][]routeReportNode
routeResult map[uint16][]methods.TracerouteHop
}
type routeReportNode struct {
asn string
isp string
geo []string
ix bool
}
func experimentTag() {
fmt.Println("Route-Path是一个实验性功能我们的IP库不能很好的支持我们提供骨干网的地理位置信息所以IP位置有时候会异常")
}
func reduceRouteReportNode(ip string, ipGeoData ipgeo.IPGeoData) (routeReportNode, error) {
rpn := routeReportNode{}
ptr, err := net.LookupAddr(ip)
if err == nil {
if strings.Contains(strings.ToLower(ptr[0]), "ix") {
rpn.ix = true
} else {
rpn.ix = false
}
}
if strings.Contains(strings.ToLower(ipGeoData.Isp), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Isp), "ix") || strings.Contains(strings.ToLower(ipGeoData.Owner), "exchange") || strings.Contains(strings.ToLower(ipGeoData.Owner), "ix") {
rpn.ix = true
}
if strings.HasPrefix(ip, "59.43") {
rpn.asn = "4809"
} else {
rpn.asn = ipGeoData.Asnumber
}
if ipGeoData.Country == "" || ipGeoData.City == "" {
return rpn, errors.New("GeoData Search Failed")
} else {
rpn.geo = []string{ipGeoData.Country, ipGeoData.City}
}
if ipGeoData.Isp == "" {
rpn.isp = ipGeoData.Owner
} else {
rpn.isp = ipGeoData.Isp
}
return rpn, nil
}
func (r *reporter) InitialBaseData() Reporter {
var nodeIndex uint16 = 1
reportNodes := map[uint16][]routeReportNode{}
for i := uint16(1); int(i) < len(r.routeResult)+1; i++ {
traceHop := r.routeResult[i][0]
if traceHop.Success {
currentIP := traceHop.Address.String()
ipGeoData, _ := ipgeo.LeoIP(currentIP)
rpn, err := reduceRouteReportNode(currentIP, *ipGeoData)
if err == nil {
reportNodes[nodeIndex] = append(reportNodes[nodeIndex], rpn)
nodeIndex += 1
}
}
}
r.routeReport = reportNodes
return r
}
func (r *reporter) Print() {
r.InitialBaseData()
//fmt.Println(r.routeReport)
for i := uint16(1); int(i) < len(r.routeReport)+1; i++ {
nodeReport := r.routeReport[i][0]
if i == 1 {
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
} else {
nodeReportBefore := r.routeReport[i-1][0]
if nodeReportBefore.asn == nodeReport.asn {
// Same ASN but Coutry or City Changed
if nodeReportBefore.geo[0] != nodeReport.geo[0] {
fmt.Printf("』→ %s『%s", nodeReport.geo[0], nodeReport.geo[1])
} else {
if nodeReportBefore.geo[1] != nodeReport.geo[1] {
fmt.Printf(" → %s", nodeReport.geo[1])
}
}
} else {
fmt.Printf("』」")
if int(i) != len(r.routeReport)+1 {
fmt.Printf("\n ╭╯\n ╰")
}
if nodeReport.ix {
fmt.Printf("AS%s \033[42;37mIXP\033[0m %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
} else {
fmt.Printf("AS%s %s「%s『%s", nodeReport.asn, nodeReport.isp, nodeReport.geo[0], nodeReport.geo[1])
}
}
}
}
fmt.Println("』」")
}

View File

@@ -0,0 +1,31 @@
package reporter
import (
"testing"
"time"
"github.com/xgadget-lab/nexttrace/methods"
"github.com/xgadget-lab/nexttrace/methods/tcp"
"github.com/xgadget-lab/nexttrace/util"
)
func TestPrint(t *testing.T) {
ip := util.DomainLookUp("213.226.68.73")
tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{
MaxHops: uint16(30),
NumMeasurements: uint16(1),
ParallelRequests: uint16(12),
Port: 80,
Timeout: time.Second / 2,
})
res, _ := tcpTraceroute.Start()
util.Printer(&util.PrinterConfig{
IP: ip,
DisplayMode: "classic",
DataOrigin: "LeoMoeAPI",
Rdnsenable: true,
Results: *res,
})
r := New(*res, ip.String())
r.Print()
}