mirror of
https://github.com/nxtrace/NTrace-core.git
synced 2025-08-12 06:26:39 +00:00
Compare commits
45 Commits
v0.0.1
...
v0.1.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
433c8656a1 | ||
|
|
1542cb4b07 | ||
|
|
06ee8f7373 | ||
|
|
6792bafb02 | ||
|
|
70305caa1c | ||
|
|
671ad82780 | ||
|
|
e62575beba | ||
|
|
d92a1e10d3 | ||
|
|
971d68f93f | ||
|
|
f765dbafae | ||
|
|
ea7feab2f9 | ||
|
|
e941eaa167 | ||
|
|
46e32d697d | ||
|
|
97578e40f7 | ||
|
|
89ed828e6e | ||
|
|
afa61d6e8d | ||
|
|
a28ff7c034 | ||
|
|
97557159e7 | ||
|
|
c63fee7d06 | ||
|
|
9e1df30bbd | ||
|
|
8a6ebdfae1 | ||
|
|
be2e197b7c | ||
|
|
89504876bc | ||
|
|
f4f1cd7f33 | ||
|
|
82493dda43 | ||
|
|
42a141d360 | ||
|
|
2e681b48c5 | ||
|
|
13af96ae15 | ||
|
|
d65ac66cde | ||
|
|
24e1b1f497 | ||
|
|
0b9e30e21c | ||
|
|
ce736fd1c2 | ||
|
|
4b2a34aeaa | ||
|
|
b6f226c400 | ||
|
|
ebdc2157da | ||
|
|
53d16b74aa | ||
|
|
6898e5d727 | ||
|
|
a8874dd809 | ||
|
|
3d5734b7b3 | ||
|
|
584be93c8d | ||
|
|
262a97d7a5 | ||
|
|
8d82ad10bb | ||
|
|
7e43140052 | ||
|
|
81d5da467c | ||
|
|
b695234015 |
@@ -2,7 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
DIST_PREFIX="BetterTrace"
|
||||
DIST_PREFIX="nexttrace"
|
||||
DEBUG_MODE=${2}
|
||||
TARGET_DIR="dist"
|
||||
PLATFORMS="darwin/amd64 darwin/arm64 linux/amd64 linux/arm64"
|
||||
|
||||
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -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 代码
|
||||
@@ -17,10 +18,11 @@ jobs:
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with: # 将下述可执行文件 release 上去
|
||||
draft: false # Release草稿
|
||||
files: |
|
||||
dist/BetterTrace_darwin_amd64
|
||||
dist/BetterTrace_darwin_arm64
|
||||
dist/BetterTrace_linux_amd64
|
||||
dist/BetterTrace_linux_arm64
|
||||
dist/nexttrace_darwin_amd64
|
||||
dist/nexttrace_darwin_arm64
|
||||
dist/nexttrace_linux_amd64
|
||||
dist/nexttrace_linux_arm64
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
|
||||
163
.gitignore
vendored
Normal file
163
.gitignore
vendored
Normal file
@@ -0,0 +1,163 @@
|
||||
### VisualStudioCode template
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
!.vscode/tasks.json
|
||||
!.vscode/launch.json
|
||||
!.vscode/extensions.json
|
||||
*.code-workspace
|
||||
|
||||
# Local History for Visual Studio Code
|
||||
.history/
|
||||
|
||||
### JetBrains template
|
||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||
|
||||
# User-specific stuff
|
||||
.idea/**/workspace.xml
|
||||
.idea/**/tasks.xml
|
||||
.idea/**/usage.statistics.xml
|
||||
.idea/**/dictionaries
|
||||
.idea/**/shelf
|
||||
|
||||
# Generated files
|
||||
.idea/**/contentModel.xml
|
||||
|
||||
# Sensitive or high-churn files
|
||||
.idea/**/dataSources/
|
||||
.idea/**/dataSources.ids
|
||||
.idea/**/dataSources.local.xml
|
||||
.idea/**/sqlDataSources.xml
|
||||
.idea/**/dynamic.xml
|
||||
.idea/**/uiDesigner.xml
|
||||
.idea/**/dbnavigator.xml
|
||||
|
||||
# Gradle
|
||||
.idea/**/gradle.xml
|
||||
.idea/**/libraries
|
||||
|
||||
# Gradle and Maven with auto-import
|
||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
||||
# since they will be recreated, and may cause churn. Uncomment if using
|
||||
# auto-import.
|
||||
# .idea/artifacts
|
||||
# .idea/compiler.xml
|
||||
# .idea/jarRepositories.xml
|
||||
# .idea/modules.xml
|
||||
# .idea/*.iml
|
||||
# .idea/modules
|
||||
# *.iml
|
||||
# *.ipr
|
||||
|
||||
# CMake
|
||||
cmake-build-*/
|
||||
|
||||
# Mongo Explorer plugin
|
||||
.idea/**/mongoSettings.xml
|
||||
|
||||
# File-based project format
|
||||
*.iws
|
||||
|
||||
# IntelliJ
|
||||
out/
|
||||
|
||||
# mpeltonen/sbt-idea plugin
|
||||
.idea_modules/
|
||||
|
||||
# JIRA plugin
|
||||
atlassian-ide-plugin.xml
|
||||
|
||||
# Cursive Clojure plugin
|
||||
.idea/replstate.xml
|
||||
|
||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
||||
com_crashlytics_export_strings.xml
|
||||
crashlytics.properties
|
||||
crashlytics-build.properties
|
||||
fabric.properties
|
||||
|
||||
# Editor-based Rest Client
|
||||
.idea/httpRequests
|
||||
|
||||
# Android studio 3.1+ serialized cache file
|
||||
.idea/caches/build_file_checksums.ser
|
||||
|
||||
### Example user template template
|
||||
### Example user template
|
||||
|
||||
# IntelliJ project files
|
||||
.idea
|
||||
*.iml
|
||||
out
|
||||
gen
|
||||
### Go template
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
### Windows template
|
||||
# Windows thumbnail cache files
|
||||
Thumbs.db
|
||||
Thumbs.db:encryptable
|
||||
ehthumbs.db
|
||||
ehthumbs_vista.db
|
||||
|
||||
# Dump file
|
||||
*.stackdump
|
||||
|
||||
# Folder config file
|
||||
[Dd]esktop.ini
|
||||
|
||||
# Recycle Bin used on file shares
|
||||
$RECYCLE.BIN/
|
||||
|
||||
# Windows Installer files
|
||||
*.cab
|
||||
*.msi
|
||||
*.msix
|
||||
*.msm
|
||||
*.msp
|
||||
|
||||
# Windows shortcuts
|
||||
*.lnk
|
||||
|
||||
### macOS template
|
||||
# General
|
||||
.DS_Store
|
||||
.AppleDouble
|
||||
.LSOverride
|
||||
|
||||
# Icon must end with two \r
|
||||
Icon
|
||||
|
||||
# Thumbnails
|
||||
._*
|
||||
|
||||
# Files that might appear in the root of a volume
|
||||
.DocumentRevisions-V100
|
||||
.fseventsd
|
||||
.Spotlight-V100
|
||||
.TemporaryItems
|
||||
.Trashes
|
||||
.VolumeIcon.icns
|
||||
.com.apple.timemachine.donotpresent
|
||||
|
||||
# Directories potentially created on remote AFP share
|
||||
.AppleDB
|
||||
.AppleDesktop
|
||||
Network Trash Folder
|
||||
Temporary Items
|
||||
.apdisk
|
||||
|
||||
22
go.mod
22
go.mod
@@ -1,10 +1,26 @@
|
||||
module traceroute
|
||||
module github.com/xgadget-lab/nexttrace
|
||||
|
||||
go 1.18
|
||||
|
||||
require (
|
||||
github.com/google/gopacket v1.1.19
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4
|
||||
)
|
||||
|
||||
require golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 // indirect
|
||||
require (
|
||||
github.com/mattn/go-colorable v0.1.9 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
|
||||
)
|
||||
|
||||
44
go.sum
44
go.sum
@@ -1,22 +1,50 @@
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
|
||||
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
|
||||
github.com/mattn/go-colorable v0.1.9 h1:sqDoxXbdeALODt0DAeJCVp38ps9ZogZEAXjus69YV3U=
|
||||
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
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=
|
||||
github.com/rodaine/table v1.0.1 h1:U/VwCnUxlVYxw8+NJiLIuCxA/xa6jL38MY3FYysVWWQ=
|
||||
github.com/rodaine/table v1.0.1/go.mod h1:UVEtfBsflpeEcD56nF4F5AocNFta0ZuolpSVdPtlmP4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo=
|
||||
github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
|
||||
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
|
||||
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
|
||||
github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
|
||||
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
|
||||
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA=
|
||||
golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
|
||||
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
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-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/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
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=
|
||||
|
||||
13
ipgeo/ipgeo.go
Normal file
13
ipgeo/ipgeo.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ipgeo
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner string
|
||||
Isp string
|
||||
}
|
||||
|
||||
type Source = func(ip string) (*IPGeoData, error)
|
||||
42
ipgeo/ipgeo_test.go
Normal file
42
ipgeo/ipgeo_test.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLeoIP(t *testing.T) {
|
||||
res, err := LeoIP("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Asnumber)
|
||||
assert.NotEmpty(t, res.Isp)
|
||||
}
|
||||
|
||||
func TestIPSB(t *testing.T) {
|
||||
res, err := IPSB("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Asnumber)
|
||||
assert.NotEmpty(t, res.Isp)
|
||||
}
|
||||
|
||||
func TestIPInfo(t *testing.T) {
|
||||
res, err := IPInfo("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.City)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
}
|
||||
|
||||
func TestIPInSight(t *testing.T) {
|
||||
res, err := IPInSight("1.1.1.1")
|
||||
assert.Nil(t, err)
|
||||
assert.NotNil(t, res)
|
||||
assert.NotEmpty(t, res.Country)
|
||||
assert.NotEmpty(t, res.Prov)
|
||||
// 这个库有时候不提供城市信息,返回值为""
|
||||
//assert.NotEmpty(t, res.City)
|
||||
}
|
||||
29
ipgeo/ipinfo.go
Normal file
29
ipgeo/ipinfo.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPInfo(ip string) (*IPGeoData, error) {
|
||||
|
||||
resp, err := http.Get("https://ipinfo.io/" + ip + "?token=" + token.ipinfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
return &IPGeoData{
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
}, nil
|
||||
}
|
||||
27
ipgeo/ipinsight.go
Normal file
27
ipgeo/ipinsight.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPInSight(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://ipinsight.io/query?ip=" + ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
return &IPGeoData{
|
||||
Country: res.Get("country_name").String(),
|
||||
City: res.Get("city_name").String(),
|
||||
Prov: res.Get("region_name").String(),
|
||||
}, nil
|
||||
}
|
||||
30
ipgeo/ipsb.go
Normal file
30
ipgeo/ipsb.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func IPSB(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://api.ip.sb/geoip/" + ip)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asn").String(),
|
||||
Country: res.Get("country").String(),
|
||||
City: res.Get("city").String(),
|
||||
Prov: res.Get("region").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
31
ipgeo/leo.go
Normal file
31
ipgeo/leo.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package ipgeo
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
func LeoIP(ip string) (*IPGeoData, error) {
|
||||
resp, err := http.Get("https://api.leo.moe/ip/?ip=" + ip + "&token=" + token.ipleo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res := gjson.ParseBytes(body)
|
||||
return &IPGeoData{
|
||||
Asnumber: res.Get("asnumber").String(),
|
||||
Country: res.Get("country").String(),
|
||||
Prov: res.Get("prov").String(),
|
||||
City: res.Get("city").String(),
|
||||
District: res.Get("district").String(),
|
||||
Owner: res.Get("owner").String(),
|
||||
Isp: res.Get("isp").String(),
|
||||
}, nil
|
||||
}
|
||||
13
ipgeo/tokens.go
Normal file
13
ipgeo/tokens.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package ipgeo
|
||||
|
||||
type tokenData struct {
|
||||
ipinsight string
|
||||
ipinfo string
|
||||
ipleo string
|
||||
}
|
||||
|
||||
var token = tokenData{
|
||||
ipinsight: "",
|
||||
ipinfo: "42764a944dabd0",
|
||||
ipleo: "NextTraceDemo",
|
||||
}
|
||||
275
main.go
275
main.go
@@ -1,211 +1,100 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"traceroute/methods"
|
||||
"traceroute/methods/tcp"
|
||||
"traceroute/methods/udp"
|
||||
"os"
|
||||
"net"
|
||||
"time"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"io/ioutil"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"strings"
|
||||
)
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
type IPGeoData struct {
|
||||
Asnumber string `json:"asnumber"`
|
||||
Country string `json:"country"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
Owner string `json:"owner"`
|
||||
Isp string `json:"isp"`
|
||||
}
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
"github.com/xgadget-lab/nexttrace/methods/tcp"
|
||||
"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)")
|
||||
var port = flag.Int("p", 80, "Set SYN Traceroute Port")
|
||||
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, 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() {
|
||||
fmt.Println("ManGoTrace v0.0.1 Alpha \nOwO Organiztion Leo (leo.moe) & Vincent (vincent.moe)")
|
||||
ip := domainLookUp(flagApply())
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ip.String())
|
||||
printer.PrintCopyRight()
|
||||
domain := flagApply()
|
||||
ip := util.DomainLookUp(domain)
|
||||
printer.PrintTraceRouteNav(ip, domain, *dataOrigin)
|
||||
|
||||
if (*tcpSYNFlag) {
|
||||
tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: time.Second / 2,
|
||||
})
|
||||
res, _ := tcpTraceroute.Start()
|
||||
if *tcpSYNFlag {
|
||||
tcpTraceroute := tcp.New(ip, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: time.Second / 2,
|
||||
})
|
||||
res, err := tcpTraceroute.Start()
|
||||
|
||||
traceroutePrinter(ip, res)
|
||||
} else {
|
||||
if (*port == 80) {
|
||||
*port = 53
|
||||
}
|
||||
udpTraceroute := udp.New(ip, true, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: 2 * time.Second,
|
||||
})
|
||||
res, _ := udpTraceroute.Start()
|
||||
if err != nil {
|
||||
fmt.Println("请赋予 sudo (root) 权限运行本程序")
|
||||
} else {
|
||||
if *routeReport {
|
||||
r := reporter.New(*res, ip.String())
|
||||
r.Print()
|
||||
} else {
|
||||
util.Printer(&util.PrinterConfig{
|
||||
IP: ip,
|
||||
DisplayMode: *displayMode,
|
||||
DataOrigin: *dataOrigin,
|
||||
Rdnsenable: *rdnsenable,
|
||||
Results: *res,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
traceroutePrinter(ip, res)
|
||||
}
|
||||
} else {
|
||||
if *port == 80 {
|
||||
*port = 53
|
||||
}
|
||||
udpTraceroute := udp.New(ip, true, methods.TracerouteConfig{
|
||||
MaxHops: uint16(*maxHops),
|
||||
NumMeasurements: uint16(*numMeasurements),
|
||||
ParallelRequests: uint16(*parallelRequests),
|
||||
Port: *port,
|
||||
Timeout: 2 * time.Second,
|
||||
})
|
||||
res, err := udpTraceroute.Start()
|
||||
|
||||
if err != nil {
|
||||
fmt.Println("请赋予 sudo (root) 权限运行本程序")
|
||||
} else {
|
||||
if *routeReport {
|
||||
r := reporter.New(*res, ip.String())
|
||||
r.Print()
|
||||
} else {
|
||||
util.Printer(&util.PrinterConfig{
|
||||
IP: ip,
|
||||
DisplayMode: *displayMode,
|
||||
DataOrigin: *dataOrigin,
|
||||
Rdnsenable: *rdnsenable,
|
||||
Results: *res,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func traceroutePrinter(ip net.IP, res *map[uint16][]methods.TracerouteHop) {
|
||||
hopIndex := uint16(1)
|
||||
for ; hopIndex <= 29 ; {
|
||||
for k,v := range *res {
|
||||
if (k == hopIndex) {
|
||||
fmt.Print(k)
|
||||
for _,v2 := range v {
|
||||
ch := make(chan uint16)
|
||||
go hopPrinter(hopIndex, ip, v2, ch)
|
||||
hopIndex = <- ch
|
||||
}
|
||||
hopIndex = hopIndex + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func flagApply() string{
|
||||
flag.Parse()
|
||||
ipArg := flag.Args()
|
||||
if (flag.NArg() != 1) {
|
||||
fmt.Println("Args Error")
|
||||
os.Exit(2)
|
||||
}
|
||||
return ipArg[0]
|
||||
}
|
||||
|
||||
func getIPGeo(ip string, c chan IPGeoData) {
|
||||
resp, err := http.Get("https://leo.moe/api.php?ip=" + ip)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := ioutil.ReadAll(resp.Body)
|
||||
|
||||
ipGeoData := IPGeoData{}
|
||||
err = json.Unmarshal(body,&ipGeoData)
|
||||
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
}
|
||||
c <- ipGeoData
|
||||
}
|
||||
|
||||
func domainLookUp(host string) net.IP {
|
||||
ips, err := net.LookupIP(host)
|
||||
if (err != nil) {
|
||||
fmt.Println("Domain Lookup Fail.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var ipSlice = []net.IP{}
|
||||
|
||||
for _, ip := range ips {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
}
|
||||
if (len(ipSlice) == 1) {
|
||||
return ipSlice[0]
|
||||
} else {
|
||||
fmt.Println("Please Choose the IP You Want To TraceRoute")
|
||||
for i, ip := range ipSlice {
|
||||
fmt.Printf("%d. %s\n",i, ip)
|
||||
}
|
||||
var index int
|
||||
fmt.Printf("Your Option: ")
|
||||
fmt.Scanln(&index)
|
||||
if (index >= len(ipSlice) || index < 0) {
|
||||
fmt.Println("Your Option is invalid")
|
||||
os.Exit(3)
|
||||
}
|
||||
return ipSlice[index]
|
||||
}
|
||||
}
|
||||
|
||||
func hopPrinter(hopIndex uint16, ip net.IP, v2 methods.TracerouteHop, c chan uint16) {
|
||||
if (v2.Address == nil) {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
ip_str := fmt.Sprintf("%s", v2.Address)
|
||||
|
||||
ptr, err := net.LookupAddr(ip_str)
|
||||
|
||||
ch_b := make(chan IPGeoData)
|
||||
go getIPGeo(ip_str, ch_b)
|
||||
iPGeoData := <-ch_b
|
||||
|
||||
if (ip.String() == ip_str) {
|
||||
hopIndex = 30
|
||||
iPGeoData.Owner = iPGeoData.Isp
|
||||
}
|
||||
|
||||
if (strings.Index(ip_str, "9.31.") == 0 || strings.Index(ip_str, "11.72.") == 0) {
|
||||
fmt.Printf("\t%-15s %.2fms * 局域网, 腾讯云\n", v2.Address, v2.RTT.Seconds()*1000)
|
||||
c <- hopIndex
|
||||
return
|
||||
}
|
||||
|
||||
if (strings.Index(ip_str, "11.13.") == 0) {
|
||||
fmt.Printf("\t%-15s %.2fms * 局域网, 阿里云\n", v2.Address, v2.RTT.Seconds()*1000)
|
||||
c <- hopIndex
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (iPGeoData.Owner == "") {
|
||||
iPGeoData.Owner = iPGeoData.Isp
|
||||
}
|
||||
|
||||
if (iPGeoData.Asnumber == "") {
|
||||
iPGeoData.Asnumber = "*"
|
||||
} else {
|
||||
iPGeoData.Asnumber = "AS" + iPGeoData.Asnumber
|
||||
}
|
||||
|
||||
if (iPGeoData.District != "") {
|
||||
iPGeoData.City = iPGeoData.City + ", " + iPGeoData.District
|
||||
}
|
||||
|
||||
if (iPGeoData.Country == "") {
|
||||
fmt.Printf("\t%-15s %.2fms * 局域网\n", v2.Address, v2.RTT.Seconds()*1000)
|
||||
c <- hopIndex
|
||||
return
|
||||
}
|
||||
|
||||
if (iPGeoData.Prov == "" && iPGeoData.City == "") {
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("\t%-15s %.2fms %s %s, %s, %s 骨干网\n",v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Owner, iPGeoData.Owner)
|
||||
} else {
|
||||
fmt.Printf("\t%-15s (%s) %.2fms %s %s, %s, %s 骨干网\n",ptr[0], v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Owner, iPGeoData.Owner)
|
||||
}
|
||||
} else {
|
||||
|
||||
if err != nil {
|
||||
fmt.Printf("\t%-15s %.2fms %s %s, %s, %s, %s\n",v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Prov, iPGeoData.City, iPGeoData.Owner)
|
||||
} else {
|
||||
fmt.Printf("\t%-15s (%s) %.2fms %s %s, %s, %s, %s\n",ptr[0], v2.Address, v2.RTT.Seconds()*1000, iPGeoData.Asnumber, iPGeoData.Country, iPGeoData.Prov, iPGeoData.City, iPGeoData.Owner)
|
||||
}
|
||||
}
|
||||
}
|
||||
c <- hopIndex
|
||||
func flagApply() string {
|
||||
flag.Parse()
|
||||
ipArg := flag.Args()
|
||||
if flag.NArg() != 1 {
|
||||
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]
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
package tcp
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"traceroute/listener_channel"
|
||||
"traceroute/methods"
|
||||
"traceroute/parallel_limiter"
|
||||
"traceroute/signal"
|
||||
"traceroute/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"log"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
"github.com/xgadget-lab/nexttrace/parallel_limiter"
|
||||
"github.com/xgadget-lab/nexttrace/signal"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type inflightData struct {
|
||||
|
||||
@@ -1,23 +1,24 @@
|
||||
package udp
|
||||
|
||||
import (
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"traceroute/listener_channel"
|
||||
"traceroute/methods"
|
||||
"traceroute/methods/quic"
|
||||
"traceroute/parallel_limiter"
|
||||
"traceroute/signal"
|
||||
"traceroute/taskgroup"
|
||||
"traceroute/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
time "time"
|
||||
"time"
|
||||
|
||||
"github.com/google/gopacket"
|
||||
"github.com/google/gopacket/layers"
|
||||
"github.com/xgadget-lab/nexttrace/listener_channel"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
"github.com/xgadget-lab/nexttrace/methods/quic"
|
||||
"github.com/xgadget-lab/nexttrace/parallel_limiter"
|
||||
"github.com/xgadget-lab/nexttrace/signal"
|
||||
"github.com/xgadget-lab/nexttrace/taskgroup"
|
||||
"github.com/xgadget-lab/nexttrace/util"
|
||||
"golang.org/x/net/context"
|
||||
"golang.org/x/net/icmp"
|
||||
"golang.org/x/net/ipv4"
|
||||
)
|
||||
|
||||
type inflightData struct {
|
||||
|
||||
39
util/methods.go
Normal file
39
util/methods.go
Normal file
@@ -0,0 +1,39 @@
|
||||
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"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
District string `json:"district"`
|
||||
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)
|
||||
}
|
||||
}
|
||||
20
util/printer/basic.go
Normal file
20
util/printer/basic.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
func PrintCopyRight() {
|
||||
fmt.Println("NextTrace v0.1.0 Alpha \nxgadget-lab zhshch (xzhsh.ch) & leo (leo.moe)")
|
||||
}
|
||||
|
||||
func PrintTraceRouteNav(ip net.IP, domain string, dataOrigin string) {
|
||||
fmt.Println("IP Geo Data Provider: " + dataOrigin)
|
||||
|
||||
if ip.String() == domain {
|
||||
fmt.Printf("traceroute to %s, 30 hops max, 32 byte packets\n", ip.String())
|
||||
} else {
|
||||
fmt.Printf("traceroute to %s (%s), 30 hops max, 32 byte packets\n", ip.String(), domain)
|
||||
}
|
||||
}
|
||||
118
util/printer/printer.go
Normal file
118
util/printer/printer.go
Normal file
@@ -0,0 +1,118 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
)
|
||||
|
||||
var 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, rdnsenable)
|
||||
if v.Address != nil && ip.String() == v.Address.String() {
|
||||
hi = 31
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hopPrinter(v2 methods.TracerouteHop, rdnsenable bool) {
|
||||
if v2.Address == nil {
|
||||
fmt.Println("\t*")
|
||||
} else {
|
||||
var iPGeoData *ipgeo.IPGeoData
|
||||
var err error
|
||||
|
||||
ipStr := v2.Address.String()
|
||||
|
||||
// 判断 err 返回,并且在CLI终端提示错误
|
||||
switch dataOrigin {
|
||||
case "LeoMoeAPI":
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
case "IP.SB":
|
||||
iPGeoData, err = ipgeo.IPSB(ipStr)
|
||||
case "IPInfo":
|
||||
iPGeoData, err = ipgeo.IPInfo(ipStr)
|
||||
case "IPInsight":
|
||||
iPGeoData, err = ipgeo.IPInSight(ipStr)
|
||||
default:
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
}
|
||||
|
||||
geo := ""
|
||||
if err != nil {
|
||||
geo = fmt.Sprint("Error: ", err)
|
||||
} else {
|
||||
geo = formatIpGeoData(ipStr, iPGeoData)
|
||||
}
|
||||
|
||||
txt := "\t"
|
||||
|
||||
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(ipStr, " ", fmt.Sprintf("%.2f", v2.RTT.Seconds()*1000), "ms ", geo)
|
||||
}
|
||||
|
||||
fmt.Println(txt)
|
||||
}
|
||||
}
|
||||
|
||||
func formatIpGeoData(ip string, data *ipgeo.IPGeoData) string {
|
||||
var res = make([]string, 0, 10)
|
||||
|
||||
if data.Asnumber == "" {
|
||||
res = append(res, "*")
|
||||
} else {
|
||||
res = append(res, "AS"+data.Asnumber)
|
||||
}
|
||||
|
||||
// TODO: 判断阿里云和腾讯云内网,数据不足,有待进一步完善
|
||||
if strings.HasPrefix(ip, "9.") {
|
||||
res = append(res, "局域网", "腾讯云")
|
||||
} 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
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(res, ", ")
|
||||
}
|
||||
137
util/printer/tableprinter.go
Normal file
137
util/printer/tableprinter.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package printer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"github.com/rodaine/table"
|
||||
"github.com/xgadget-lab/nexttrace/ipgeo"
|
||||
"github.com/xgadget-lab/nexttrace/methods"
|
||||
)
|
||||
|
||||
type rowData struct {
|
||||
Hop int64
|
||||
IP string
|
||||
Latency string
|
||||
Asnumber string
|
||||
Country string
|
||||
Prov string
|
||||
City string
|
||||
District string
|
||||
Owner 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, 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
|
||||
}
|
||||
}
|
||||
}
|
||||
// 打印表格
|
||||
tbl.Print()
|
||||
}
|
||||
|
||||
func New() table.Table {
|
||||
// 初始化表格
|
||||
headerFmt := color.New(color.FgGreen, color.Underline).SprintfFunc()
|
||||
columnFmt := color.New(color.FgYellow).SprintfFunc()
|
||||
|
||||
tbl := table.New("Hop", "IP", "Lantency", "ASN", "Country", "Province", "City", "Owner")
|
||||
tbl.WithHeaderFormatter(headerFmt).WithFirstColumnFormatter(columnFmt)
|
||||
return tbl
|
||||
}
|
||||
|
||||
func tableDataGenerator(v2 methods.TracerouteHop, rdnsenable bool) *rowData {
|
||||
if v2.Address == nil {
|
||||
return &rowData{
|
||||
Hop: int64(v2.TTL),
|
||||
IP: "*",
|
||||
}
|
||||
} else {
|
||||
// 初始化变量
|
||||
var iPGeoData *ipgeo.IPGeoData
|
||||
var err error
|
||||
var lantency, IP string
|
||||
|
||||
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终端提示错误
|
||||
switch dataOrigin {
|
||||
case "LeoMoeAPI":
|
||||
iPGeoData, err = ipgeo.LeoIP(ipStr)
|
||||
case "IP.SB":
|
||||
iPGeoData, err = ipgeo.IPSB(ipStr)
|
||||
case "IPInfo":
|
||||
iPGeoData, err = ipgeo.IPInfo(ipStr)
|
||||
case "IPInsight":
|
||||
iPGeoData, err = ipgeo.IPInSight(ipStr)
|
||||
default:
|
||||
iPGeoData, err = ipgeo.LeoIP(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 iPGeoData.Owner == "" {
|
||||
iPGeoData.Owner = iPGeoData.Isp
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
fmt.Print("Error: ", err)
|
||||
return &rowData{
|
||||
Hop: int64(v2.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
}
|
||||
} else {
|
||||
|
||||
return &rowData{
|
||||
Hop: int64(v2.TTL),
|
||||
IP: IP,
|
||||
Latency: lantency,
|
||||
Asnumber: iPGeoData.Asnumber,
|
||||
Country: iPGeoData.Country,
|
||||
Prov: iPGeoData.Prov,
|
||||
City: iPGeoData.City,
|
||||
District: iPGeoData.District,
|
||||
Owner: iPGeoData.Owner,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
127
util/reporter/reporter.go
Normal file
127
util/reporter/reporter.go
Normal 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("』」")
|
||||
}
|
||||
31
util/reporter/reporter_test.go
Normal file
31
util/reporter/reporter_test.go
Normal 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()
|
||||
}
|
||||
46
util/util.go
46
util/util.go
@@ -1,8 +1,10 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
)
|
||||
|
||||
// get the local ip and port based on our destination ip
|
||||
@@ -22,3 +24,47 @@ func LocalIPPort(dstip net.IP) (net.IP, int) {
|
||||
}
|
||||
return nil, -1
|
||||
}
|
||||
|
||||
func DomainLookUp(host string) net.IP {
|
||||
ips, err := net.LookupIP(host)
|
||||
if err != nil {
|
||||
fmt.Println("Domain " + host + " Lookup Fail.")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
var ipSlice = []net.IP{}
|
||||
var ipv6Flag = false
|
||||
|
||||
for _, ip := range ips {
|
||||
// 仅返回ipv4的ip
|
||||
if ip.To4() != nil {
|
||||
ipSlice = append(ipSlice, ip)
|
||||
} else {
|
||||
ipv6Flag = true
|
||||
}
|
||||
}
|
||||
|
||||
if ipv6Flag {
|
||||
fmt.Println("[Info] IPv6 Traceroute is not supported right now.")
|
||||
if len(ipSlice) == 0 {
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
if len(ipSlice) == 1 {
|
||||
return ipSlice[0]
|
||||
} else {
|
||||
fmt.Println("Please Choose the IP You Want To TraceRoute")
|
||||
for i, ip := range ipSlice {
|
||||
fmt.Printf("%d. %s\n", i, ip)
|
||||
}
|
||||
var index int
|
||||
fmt.Printf("Your Option: ")
|
||||
fmt.Scanln(&index)
|
||||
if index >= len(ipSlice) || index < 0 {
|
||||
fmt.Println("Your Option is invalid")
|
||||
os.Exit(3)
|
||||
}
|
||||
return ipSlice[index]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user