From ba133e3c73dbccd666604e58841e7e95777d0598 Mon Sep 17 00:00:00 2001 From: Leo Date: Thu, 16 Feb 2023 21:16:47 +0800 Subject: [PATCH] feat: add DN42 Mode --- cmd/cmd.go | 10 +++++ config/viper.go | 34 +++++++++++++++ dn42/dn42.go | 7 +++ dn42/geofeed.go | 101 +++++++++++++++++++++++++++++++++++++++++++ dn42/geofeed_test.go | 17 ++++++++ dn42/ptr.go | 83 +++++++++++++++++++++++++++++++++++ dn42/ptr_test.go | 50 +++++++++++++++++++++ geofeed.example.csv | 3 ++ ipgeo/dn42.go | 62 ++++++++++++++++++++++++++ ipgeo/dn42_test.go | 5 +++ ipgeo/ipgeo.go | 2 + nt_config.yaml | 2 + ptr.example.csv | 4 ++ trace/trace.go | 14 ++++++ 14 files changed, 394 insertions(+) create mode 100644 config/viper.go create mode 100644 dn42/dn42.go create mode 100644 dn42/geofeed.go create mode 100644 dn42/geofeed_test.go create mode 100644 dn42/ptr.go create mode 100644 dn42/ptr_test.go create mode 100644 geofeed.example.csv create mode 100644 ipgeo/dn42.go create mode 100644 ipgeo/dn42_test.go create mode 100644 nt_config.yaml create mode 100644 ptr.example.csv diff --git a/cmd/cmd.go b/cmd/cmd.go index 568cc359..db0e27a0 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -13,6 +13,7 @@ import ( "github.com/akamensky/argparse" "github.com/syndtr/gocapability/capability" + "github.com/xgadget-lab/nexttrace/config" fastTrace "github.com/xgadget-lab/nexttrace/fast_trace" "github.com/xgadget-lab/nexttrace/ipgeo" "github.com/xgadget-lab/nexttrace/printer" @@ -42,6 +43,7 @@ func Excute() { alwaysRdns := parser.Flag("a", "always-rdns", &argparse.Options{Help: "Always resolve IP addresses to their domain names"}) routePath := parser.Flag("P", "route-path", &argparse.Options{Help: "Print traceroute hop path by ASN and location"}) report := parser.Flag("r", "report", &argparse.Options{Help: "output using report mode"}) + dn42 := parser.Flag("", "dn42", &argparse.Options{Help: "DN42 Mode"}) output := parser.Flag("o", "output", &argparse.Options{Help: "Write trace result to file (RealTimePrinter ONLY)"}) tablePrint := parser.Flag("t", "table", &argparse.Options{Help: "Output trace results as table"}) classicPrint := parser.Flag("c", "classic", &argparse.Options{Help: "Classic Output trace results like BestTrace"}) @@ -129,6 +131,13 @@ func Excute() { } } + if *dn42 { + // 初始化配置 + config.InitConfig() + *dataOrigin = "DN42" + *maptrace = true + } + if strings.ToUpper(*dataOrigin) == "LEOMOEAPI" { w := wshandle.New() w.Interrupt = make(chan os.Signal, 1) @@ -156,6 +165,7 @@ func Excute() { } var conf = trace.Config{ + DN42: *dn42, SrcAddr: *src_addr, BeginHop: *beginHop, DestIP: ip, diff --git a/config/viper.go b/config/viper.go new file mode 100644 index 00000000..8f31cce8 --- /dev/null +++ b/config/viper.go @@ -0,0 +1,34 @@ +package config + +import ( + "fmt" + + "github.com/spf13/viper" +) + +func InitConfig() { + + // 配置文件名, 不加扩展 + viper.SetConfigName("nt_config") // name of config file (without extension) + // 设置文件的扩展名 + viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name + // 查找配置文件所在路径 + viper.AddConfigPath("/etc/bin/nexttrace/") + viper.AddConfigPath("/usr/local/bin/nexttrace/") + // 在当前路径进行查找 + viper.AddConfigPath(".") + // viper.AddConfigPath("./config/") + + // 配置默认值 + viper.SetDefault("ptrPath", "./ptr.csv") + viper.SetDefault("geoFeedPath", "./geofeed.csv") + + // 开始查找并读取配置文件 + err := viper.ReadInConfig() // Find and read the config file + if err != nil { // Handle errors reading the config file + fmt.Println("未能找到配置文件,我们将在您的运行目录为您创建 nt_config.yaml 默认配置") + viper.SafeWriteConfigAs("./nt_config.yaml") + } + + viper.ReadInConfig() +} diff --git a/dn42/dn42.go b/dn42/dn42.go new file mode 100644 index 00000000..7d17a4de --- /dev/null +++ b/dn42/dn42.go @@ -0,0 +1,7 @@ +package dn42 + +/*** + [DN42 Package] + 谨献给 DN42 所有的小伙伴们,祝你们终有一天能有自己的公网 ASN ~ + By Leo +***/ diff --git a/dn42/geofeed.go b/dn42/geofeed.go new file mode 100644 index 00000000..7b183f6d --- /dev/null +++ b/dn42/geofeed.go @@ -0,0 +1,101 @@ +package dn42 + +import ( + "encoding/csv" + "net" + "os" + "sort" + + "github.com/spf13/viper" +) + +type GeoFeedRow struct { + IPNet *net.IPNet + CIDR string + LtdCode string + ISO3166 string + City string + ASN string + IPWhois string +} + +func GetGeoFeed(ip string) (GeoFeedRow, bool) { + rows, err := ReadGeoFeed() + if err != nil { + // 处理错误 + panic(err) + } + + row, find := FindGeoFeedRow(ip, rows) + return row, find + +} + +func ReadGeoFeed() ([]GeoFeedRow, error) { + path := viper.Get("geoFeedPath").(string) + f, err := os.Open(path) + if err != nil { + return nil, err + } + defer f.Close() + + r := csv.NewReader(f) + rows, err := r.ReadAll() + if err != nil { + return nil, err + } + + // 将 CSV 中的每一行转换为 GeoFeedRow 类型,并保存到 rowsSlice 中 + var rowsSlice []GeoFeedRow + for _, row := range rows { + cidr := row[0] // 假设第一列是 CIDR 字段 + _, ipnet, err := net.ParseCIDR(cidr) + if err != nil { + // 如果解析 CIDR 失败,跳过这一行 + continue + } + if len(row) == 4 { + rowsSlice = append(rowsSlice, GeoFeedRow{ + IPNet: ipnet, + CIDR: cidr, + LtdCode: row[1], + ISO3166: row[2], + City: row[3], + }) + } else { + rowsSlice = append(rowsSlice, GeoFeedRow{ + IPNet: ipnet, + CIDR: cidr, + LtdCode: row[1], + ISO3166: row[2], + City: row[3], + ASN: row[4], + IPWhois: row[5], + }) + } + + } + // 根据 CIDR 范围从小到大排序,方便后面查找 + sort.Slice(rowsSlice, func(i, j int) bool { + return rowsSlice[i].IPNet.Mask.String() > rowsSlice[j].IPNet.Mask.String() + }) + + return rowsSlice, nil +} + +func FindGeoFeedRow(ipStr string, rows []GeoFeedRow) (GeoFeedRow, bool) { + ip := net.ParseIP(ipStr) + if ip == nil { + // 如果传入的 IP 无效,直接返回 + return GeoFeedRow{}, false + } + + // 遍历每个 CIDR 范围,找到第一个包含传入的 IP 的 CIDR + for _, row := range rows { + if row.IPNet.Contains(ip) { + return row, true + } + } + + return GeoFeedRow{}, false +} diff --git a/dn42/geofeed_test.go b/dn42/geofeed_test.go new file mode 100644 index 00000000..38daab4c --- /dev/null +++ b/dn42/geofeed_test.go @@ -0,0 +1,17 @@ +package dn42 + +// func TestGeoFeed(t *testing.T) { +// rows, err := ReadGeoFeed() +// if err != nil { +// // 处理错误 +// } + +// row, found := FindGeoFeedRow("2001:0418:1403:8080::6fff", rows) +// if found { +// // 处理符合条件的 row +// log.Println(row) +// } else { +// // 处理未找到的情况 +// } + +// } diff --git a/dn42/ptr.go b/dn42/ptr.go new file mode 100644 index 00000000..c470ff92 --- /dev/null +++ b/dn42/ptr.go @@ -0,0 +1,83 @@ +package dn42 + +import ( + "encoding/csv" + "errors" + "fmt" + "os" + "regexp" + "strings" + + "github.com/spf13/viper" +) + +type PtrRow struct { + IATACode string + LtdCode string + Region string + City string +} + +func matchesPattern(prefix string, s string) bool { + pattern := fmt.Sprintf(`^(.*[-.\d]|^)%s[-.\d].*$`, prefix) + + r, err := regexp.Compile(pattern) + if err != nil { + fmt.Println("Invalid regular expression:", err) + return false + } + + return r.MatchString(s) +} + +func FindPtrRecord(ptr string) (PtrRow, error) { + path := viper.Get("ptrPath").(string) + f, err := os.Open(path) + if err != nil { + return PtrRow{}, err + } + defer f.Close() + + r := csv.NewReader(f) + rows, err := r.ReadAll() + if err != nil { + return PtrRow{}, err + } + // 转小写 + ptr = strings.ToLower(ptr) + // 先查城市名 + for _, row := range rows { + city := row[3] + if city == "" { + continue + } + city = strings.ReplaceAll(city, " ", "") + city = strings.ToLower(city) + + if matchesPattern(city, ptr) { + return PtrRow{ + LtdCode: row[1], + Region: row[2], + City: row[3], + }, nil + } + } + // 查 IATA Code + for _, row := range rows { + iata := row[0] + if iata == "" { + continue + } + iata = strings.ToLower(iata) + if matchesPattern(iata, ptr) { + return PtrRow{ + IATACode: iata, + LtdCode: row[1], + Region: row[2], + City: row[3], + }, nil + } + } + + return PtrRow{}, errors.New("ptr not found") +} diff --git a/dn42/ptr_test.go b/dn42/ptr_test.go new file mode 100644 index 00000000..e1a1faa9 --- /dev/null +++ b/dn42/ptr_test.go @@ -0,0 +1,50 @@ +package dn42 + +import ( + "testing" +) + +func TestPTR(t *testing.T) { + + // example_mis := []string{ + // "sloutravel.com", + // "memeslou.org", + // "followsloucity.net", + // "slouslou.slou", + // "slouslou8.slou", + // } + + // examples := []string{ + + // "1ge.slou.as1299.net", + // "1ge.slou2.as1299.net", + // "1ge-slou.as1299.net", + // "slou-1.as1299.net", + // "slou.as1299.com", + // "1ge-snge-6.as1299.net", + // "c-1.sin.sg.atlas.moeqing.com", + // "core.hkg1.hk.atlas.moeqing.com", + // "core.losangles.us.atlas.moeqing.com", + // } + + // fmt.Println("容易误匹配的 PTR") + + // for _, s := range example_mis { + // if r, err := FindPtrRecord("ptr.csv"); err == nil { + // fmt.Println(s, r) + // } else { + // fmt.Println(s, err) + // } + + // } + // fmt.Println("\n应该正常匹配的 PTR") + // for _, s := range examples { + // if r, err := FindPtrRecord("ptr.csv"); err == nil { + // fmt.Println(s, r) + // } else { + // fmt.Println(s, err) + // } + + // } + +} diff --git a/geofeed.example.csv b/geofeed.example.csv new file mode 100644 index 00000000..92183386 --- /dev/null +++ b/geofeed.example.csv @@ -0,0 +1,3 @@ +154.48.0.0/12,,,,174,COGENT-NET +200.15.12.0/22,BR,BR-SP,Sao Paulo,2914,NTT-BACKBONE +2001:0418:1403::/48,US,US-VA,Ashburn,2914,NTT-BACKBONE \ No newline at end of file diff --git a/ipgeo/dn42.go b/ipgeo/dn42.go new file mode 100644 index 00000000..d6eebea1 --- /dev/null +++ b/ipgeo/dn42.go @@ -0,0 +1,62 @@ +package ipgeo + +import ( + "strings" + + "github.com/xgadget-lab/nexttrace/dn42" +) + +func LtdCodeToCountryOrAreaName(Code string) string { + countryName := []string{"United States", "Afghanistan", "Åland Islands", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "Argentina", "Armenia", "Aruba", "Australia", "Austria", "Azerbaijan", "Bahamas", "Bahrain", " Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "Cambodia", "Cameroon", "Canada ", "Cape Verde", "Cayman Islands", "Central Africa", "Chad", "Chile", "China", "Christmas Island", "Cocos (Keeling) Islands", "Colombia", "Comoros", "Congo (Brazzaville)", "DRC", "Cook Islands", "Costa Rica", "Cote d'Ivoire", "Croatia", "Cuba", "Cyprus", "Czech Republic", " Denmark", "Djibouti", "Dominica", "Dominica", "Ecuador", "Egypt", "El Salvador", "Equatorial Guinea", "Eritrea", "Estonia", "Ethiopia", "Falkland Islands (Malvinas)", "Faroe Islands", "Fiji", "Finland", "France", "French Guiana", "French Polynesia", " French Southern Territories", "Gabon", "Gambia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guinea", "Guinea-Bissau", "Guyana", "Haiti", "Heard and McDonald Islands", "Vatican", "Honduras", "Hong Kong", "Hungary", "Iceland", "India", "Indonesia", "Iran", "Iraq", "Ireland", "British Isles of Man", "Israel", "Italy", "Jamaica", "Japan", "Jersey", "Jordan", "Kazakhstan", "Kenya", "Kiribati", "North Korea", "South Korea", " Kuwait", "Kyrgyzstan", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Luxembourg", "Macao", "FYROM", "Madagascar", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", " Marshall Islands", "Martinique", "Mauritania", "Mauritius", "Mayotte", "Mexico", "Micronesia (Federated States of)", "Moldova", "Monaco", "Mongolia", "Montenegro", "Montserrat", "Morocco", "Mozambique", "Myanmar", "Namibia", "Nauru", "Nepal", "Netherlands", "Netherlands Antilles", "New Caledonia", "New Zealand", "Nicaragua", "Niger", "Nigeria", "Niue", "Norfolk Island", "Northern Mariana", "Norway", "Oman", "Pakistan", "Palau", "Palestine", "Panama", "Papua New Guinea", "Paraguay", "Peru", "Philippines", "Pitcairn", "Poland", "Portugal", "Puerto Rico", "Qatar", "Reunion", "Romania", "Russian Federation", "Rwanda", "St. Helena", "St. Kitts and Nevis", "St. Lucia", "St. Pierre and Miquelon", "St. Vincent and the Grenadines", "Samoa", "San Marino", "Sao Tome and Principe", "Saudi Arabia", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Georgia and South Sandwich Islands", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard and Jan Mayen Islands", "Swaziland", "Sweden ", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Thailand", "Timor-Leste", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "Uganda", "Ukraine", "United Arab Emirates", " United Kingdom", "U.S. Minor Outlying Islands", "Uruguay", "Uzbekistan", "Vanuatu", "Venezuela", "Vietnam", "British Virgin Islands", "U.S. Virgin Islands", "Wallis and Futuna", "Western Sahara", "Yemen", "Zambia", "Zimbabwe"} + countryCode := []string{"us", "af", "ax", "al", "dz", "as", "ad", "ao", "ai", "aq", "ag", "ar", "am", "aw", "au", "at", "az", "bs", "bh", "bd", "bb", "by", "be", "bz", "bj", "bm", "bt", "bo", "ba", "bw", "bv", "br", "io", "bn", "bg", "bf", "bi", "kh", "cm", "ca", "cv", "ky", "cf", "td", "cl", "cn", "cx", "cc", "co", "km", "cg", "cd", "ck", "cr", "ci", "hr", "cu", "cy", "cz", "dk", "dj", "dm", "do", "ec", "eg", "sv", "gq", "er", "ee", "et", "fk", "fo", "fj", "fi", "fr", "gf", "pf", "tf", "ga", "gm", "ge", "de", "gh", "gi", "gr", "gl", "gd", "gp", "gu", "gt", "gg", "gn", "gw", "gy", "ht", "hm", "va", "hn", "hk", "hu", "is", "in", "id", "ir", "iq", "ie", "im", "il", "it", "jm", "jp", "je", "jo", "kz", "ke", "ki", "kp", "kr", "kw", "kg", "la", "lv", "lb", "ls", "lr", "ly", "li", "lt", "lu", "mo", "mk", "mg", "mw", "my", "mv", "ml", "mt", "mh", "mq", "mr", "mu", "yt", "mx", "fm", "md", "mc", "mn", "me", "ms", "ma", "mz", "mm", "na", "nr", "np", "nl", "an", "nc", "nz", "ni", "ne", "ng", "nu", "nf", "mp", "no", "om", "pk", "pw", "ps", "pa", "pg", "py", "pe", "ph", "pn", "pl", "pt", "pr", "qa", "re", "ro", "ru", "rw", "sh", "kn", "lc", "pm", "vc", "ws", "sm", "st", "sa", "sn", "rs", "sc", "sl", "sg", "sk", "si", "sb", "so", "za", "gs", "es", "lk", "sd", "sr", "sj", "sz", "se", "ch", "sy", "tw", "tj", "tz", "th", "tl", "tg", "tk", "to", "tt", "tn", "tr", "tm", "tc", "tv", "ug", "ua", "ae", "gb", "um", "uy", "uz", "vu", "ve", "vn", "vg", "vi", "wf", "eh", "ye", "zm", "zw"} + Code = strings.ToLower(Code) + for i, v := range countryCode { + if strings.Contains(Code, v) { + return countryName[i] + } + } + return Code +} + +func DN42(ip string) (*IPGeoData, error) { + data := &IPGeoData{} + // 先解析传入过来的数据 + ipTmp := strings.Split(ip, ",") + if len(ipTmp) > 1 { + ip = ipTmp[0] + } + // 先查找 GeoFeed + if geo, find := dn42.GetGeoFeed(ip); find { + data.Country = geo.LtdCode + data.City = geo.City + data.Asnumber = geo.ASN + data.Whois = geo.IPWhois + } + // 如果没找到,查找 PTR + if len(ipTmp) > 1 { + // 存在 PTR 记录 + if res, err := dn42.FindPtrRecord(ipTmp[1]); err == nil && res.LtdCode != "" { + data.Country = res.LtdCode + data.Prov = res.Region + data.City = res.City + } + } + + data.Country = LtdCodeToCountryOrAreaName(data.Country) + + switch data.Country { + case "Hong Kong": + data.Country = "China" + data.Prov = "Hong Kong" + case "Taiwan": + data.Country = "China" + data.Prov = "Taiwan" + case "Macao": + data.Country = "China" + data.Prov = "Macao" + case "": + data.Country = "Unknown" + } + + return data, nil +} diff --git a/ipgeo/dn42_test.go b/ipgeo/dn42_test.go new file mode 100644 index 00000000..0ebc61de --- /dev/null +++ b/ipgeo/dn42_test.go @@ -0,0 +1,5 @@ +package ipgeo + +// func TestDN42(t *testing.T) { +// DN42("") +// } diff --git a/ipgeo/ipgeo.go b/ipgeo/ipgeo.go index 3f373b2a..0b5e1dc3 100644 --- a/ipgeo/ipgeo.go +++ b/ipgeo/ipgeo.go @@ -29,6 +29,8 @@ type Source = func(ip string) (*IPGeoData, error) func GetSource(s string) Source { switch strings.ToUpper(s) { + case "DN42": + return DN42 case "LEOMOEAPI": return LeoIP case "IP.SB": diff --git a/nt_config.yaml b/nt_config.yaml new file mode 100644 index 00000000..e884f408 --- /dev/null +++ b/nt_config.yaml @@ -0,0 +1,2 @@ +geofeedpath: ./geofeed.csv +ptrpath: ./ptr.csv diff --git a/ptr.example.csv b/ptr.example.csv new file mode 100644 index 00000000..aac4b644 --- /dev/null +++ b/ptr.example.csv @@ -0,0 +1,4 @@ +snge,SG,,Singapore +CXS,CN,Hunan,Changsha +LAX,US,California,Los Angeles +SJC,US,California,San Jose \ No newline at end of file diff --git a/trace/trace.go b/trace/trace.go index 75127b5c..b4bd3447 100644 --- a/trace/trace.go +++ b/trace/trace.go @@ -31,6 +31,7 @@ type Config struct { PacketInterval int TTLInterval int Lang string + DN42 bool RealtimePrinter func(res *Result, ttl int) AsyncPrinter func(res *Result) } @@ -121,6 +122,19 @@ type Hop struct { } func (h *Hop) fetchIPData(c Config) (err error) { + // DN42 + if c.DN42 { + var ip string + // 首先查找 PTR 记录 + r, err := net.LookupAddr(h.Address.String()) + if err == nil && len(r) > 0 { + h.Hostname = r[0][:len(r[0])-1] + ip = h.Address.String() + "," + h.Hostname + } + h.Geo, err = c.IPGeoSource(ip) + return nil + } + // Debug Area // c.AlwaysWaitRDNS = true