-
Notifications
You must be signed in to change notification settings - Fork 28
feat: fetch built DN42 data online by default #122
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the 概览本次更改将配置默认值从本地文件路径迁移至远程 URL,为地理位置反馈和 PTR 数据加载引入 HTTP 支持,并在缺失配置时改变错误处理行为,不再自动写入默认配置文件。 变更详情
时序图sequenceDiagram
participant User
participant Config as config/viper.go
participant GeoFeed as dn42/geofeed.go
participant PTR as dn42/ptr.go
participant Remote as Remote URL
participant Local as Local File
User->>Config: 初始化配置
alt 配置文件存在
Config->>Config: 读取 nt_config.yaml
else 配置文件缺失
Config->>Config: 使用远程 URL 默认值
Note over Config: 仅打印提示,不写入文件
end
User->>GeoFeed: 读取地理数据
alt 首次调用 getGeoFeed
GeoFeed->>GeoFeed: 检查 geoFeedPath
alt URL 路径
GeoFeed->>Remote: HTTP GET (10s 超时)
Remote-->>GeoFeed: CSV 响应
else 本地路径
GeoFeed->>Local: 读取本地文件
Local-->>GeoFeed: CSV 数据
end
GeoFeed->>GeoFeed: 缓存结果 (sync.OnceValues)
else 后续调用
GeoFeed->>GeoFeed: 返回缓存数据
end
GeoFeed->>GeoFeed: 解析 CIDR,排序掩码
GeoFeed-->>User: GeoFeedRow 切片
User->>PTR: 查询 PTR 记录
alt 首次调用 getPtr
PTR->>PTR: 检查 ptrPath
alt URL 路径
PTR->>Remote: HTTP GET (10s 超时)
Remote-->>PTR: CSV 响应
else 本地路径
PTR->>Local: 读取本地文件
Local-->>PTR: CSV 数据
end
PTR->>PTR: 缓存结果 (sync.OnceValues)
else 后续调用
PTR->>PTR: 返回缓存数据
end
PTR->>PTR: 匹配城市名称或 IATA 码
PTR-->>User: PTR 记录
代码审查工作量估计🎯 4 (复杂) | ⏱️ ~45 分钟 本次变更涉及多个文件的异构逻辑修改,包括:HTTP 网络请求处理、超时机制、sync.OnceValues 缓存模式的引入、CIDR 解析和排序逻辑、多层次的错误处理以及控制流的调整。虽然各个修改点相对独立,但每个模块都需要单独的逻辑推导和测试路径分析。 诗
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (4)
dn42/ptr.go (2)
63-105: 避免 CSV 越界与保持返回字段原貌;PTR 匹配需更稳健
- 多处直接访问
row[3]/row[1]等,遇到短行会越界 panic。- IATA 为匹配而转小写,但返回值应保留数据源原貌。
- 结合远端数据源,不可信输入需更稳健处理。
func FindPtrRecord(ptr string) (PtrRow, error) { rows, err := getPtr() if err != nil { return PtrRow{}, err } // 转小写 ptr = strings.ToLower(ptr) // 先查城市名 for _, row := range rows { - city := row[3] + if len(row) < 4 { + continue + } + 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], + LtdCode: row[1], + Region: row[2], + City: row[3], }, nil } } // 查 IATA Code for _, row := range rows { - iata := row[0] + if len(row) < 4 { + continue + } + iata := row[0] if iata == "" { continue } - iata = strings.ToLower(iata) - if matchesPattern(iata, ptr) { + if matchesPattern(strings.ToLower(iata), ptr) { return PtrRow{ - IATACode: iata, - LtdCode: row[1], - Region: row[2], - City: row[3], + IATACode: iata, // 保留原始大小写 + LtdCode: row[1], + Region: row[2], + City: row[3], }, nil } } return PtrRow{}, errors.New("ptr not found") }
25-35: 正则拼接未转义,城市名等含特殊字符时匹配不可靠建议使用
regexp.QuoteMeta转义prefix,避免正则注入/误匹配。func matchesPattern(prefix string, s string) bool { - pattern := fmt.Sprintf(`^(.*[-.\d]|^)%s[-.\d].*$`, prefix) + safe := regexp.QuoteMeta(prefix) + pattern := fmt.Sprintf(`^(.*[-.\d]|^)%s[-.\d].*$`, safe) r, err := regexp.Compile(pattern) if err != nil { fmt.Println("Invalid regular expression:", err) return false } return r.MatchString(s) }dn42/geofeed.go (2)
66-108: CSV 行访问未做长度校验,存在越界风险;读取失败不应 panic
- 直接访问
row[1..5],短行会 panic。GetGeoFeed读取失败时panic,不利于作为库函数复用。func GetGeoFeed(ip string) (GeoFeedRow, bool) { rows, err := ReadGeoFeed() if err != nil { - // 处理错误 - panic(err) + log.Printf("读取 GeoFeed 失败:%v", err) + return GeoFeedRow{}, false } @@ func ReadGeoFeed() ([]GeoFeedRow, error) { rows, err := getGeoFeed() if err != nil { return nil, err } // 将 CSV 中的每一行转换为 GeoFeedRow 类型,并保存到 rowsSlice 中 var rowsSlice []GeoFeedRow for _, row := range rows { - cidr := row[0] // 假设第一列是 CIDR 字段 + if len(row) < 1 { + continue + } + cidr := row[0] // 第一列是 CIDR _, ipnet, err := net.ParseCIDR(cidr) if err != nil { // 如果解析 CIDR 失败,跳过这一行 continue } - if len(row) == 4 { + switch { + case len(row) >= 6: + rowsSlice = append(rowsSlice, GeoFeedRow{ + IPNet: ipnet, + CIDR: cidr, + LtdCode: row[1], + ISO3166: row[2], + City: row[3], + ASN: row[4], + IPWhois: row[5], + }) + case 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], - }) + default: + continue } }
103-105: 排序依据不正确,可能导致未按最长前缀匹配(LPM)返回使用
Mask.String()的字典序无法保证前缀长度从大到小;应按掩码比特位数排序,优先匹配更具体的网段。- // 根据 CIDR 范围从小到大排序,方便后面查找 - sort.Slice(rowsSlice, func(i, j int) bool { - return rowsSlice[i].IPNet.Mask.String() > rowsSlice[j].IPNet.Mask.String() - }) + // 按前缀长度从大到小排序(LPM 优先) + sort.Slice(rowsSlice, func(i, j int) bool { + oi, _ := rowsSlice[i].IPNet.Mask.Size() + oj, _ := rowsSlice[j].IPNet.Mask.Size() + if oi != oj { + return oi > oj + } + // 次级稳定排序 + return rowsSlice[i].CIDR < rowsSlice[j].CIDR + })
🧹 Nitpick comments (1)
config/viper.go (1)
31-33: 统一日志体系并明确动作建议使用
fmt.Println可能与全局日志风格不一致;建议使用统一 logger,并在找不到配置时同时输出配置查找路径与下一步建议(例如如何创建/覆盖)。
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
config/viper.go(1 hunks)dn42/geofeed.go(2 hunks)dn42/ptr.go(2 hunks)nt_config.yaml(0 hunks)
💤 Files with no reviewable changes (1)
- nt_config.yaml
| viper.SetDefault("ptrPath", "https://cryolitia.github.io/Next-Trace-DN42-Feeder/ptr.csv") | ||
| viper.SetDefault("geoFeedPath", "https://cryolitia.github.io/Next-Trace-DN42-Feeder/geofeed.csv") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
默认数据源指向个人 GitHub Pages,不宜合并;请改为组织托管并提供回退
- PR 说明已提及“不要使用作者的 GitHub Pages 地址合并”。当前默认值与此相冲突。
- 运行时强依赖外网,离线/限网环境将直接失败;建议提供本地回退路径或内置数据。
建议:
- 迁移到组织域(如 org 的 GitHub Pages 或 Release 资产/CDN),并进行版本化与校验(ETag/If-None-Match 或校验和)。
- 支持环境变量/配置覆盖基础 URL(如 DN42_FEED_BASE_URL),默认使用组织源。
- 实现“远端失败→本地路径回退”的双路策略,提升可用性与隐私。
- 为后续灰度与回滚,建议在日志中标记所用数据源与版本。
| var getGeoFeed = sync.OnceValues(func() ([][]string, error) { | ||
| path := viper.Get("geoFeedPath").(string) | ||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| return nil, err | ||
| var r *csv.Reader | ||
| log.Println(path) | ||
| if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { | ||
| client := &http.Client{ | ||
| // 10 秒超时 | ||
| Timeout: time.Duration(10) * time.Second, | ||
| } | ||
| req, _ := http.NewRequest("GET", path, nil) | ||
| content, err := client.Do(req) | ||
| if err != nil { | ||
| log.Println("DN42数据请求超时,请更换其他数据源或使用本地数据") | ||
| return nil, err | ||
| } | ||
| r = csv.NewReader(content.Body) | ||
| } else { | ||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() | ||
| r = csv.NewReader(f) | ||
| } | ||
| defer f.Close() | ||
| return r.ReadAll() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
HTTP 获取存在与 PTR 相同的问题;请修复错误处理、关闭 Body、校验状态码并限制读取
- 忽略
NewRequest错误、未Close()、未检查StatusCode、无读取上限、viper.Get(...).(string)有 panic 风险。 log.Println(path)可能噪声过大;建议降级为 debug 或移除。
@@
-import (
+import (
"encoding/csv"
- "log"
+ "io"
+ "log"
"net"
"net/http"
"os"
"sort"
"strings"
"sync"
"time"
@@
-var getGeoFeed = sync.OnceValues(func() ([][]string, error) {
- path := viper.Get("geoFeedPath").(string)
+var getGeoFeed = sync.OnceValues(func() ([][]string, error) {
+ path := viper.GetString("geoFeedPath")
var r *csv.Reader
- log.Println(path)
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
client := &http.Client{
// 10 秒超时
Timeout: time.Duration(10) * time.Second,
}
- req, _ := http.NewRequest("GET", path, nil)
- content, err := client.Do(req)
- if err != nil {
- log.Println("DN42数据请求超时,请更换其他数据源或使用本地数据")
- return nil, err
- }
- r = csv.NewReader(content.Body)
+ req, err := http.NewRequest("GET", path, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Printf("DN42数据请求失败:%v", err)
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("DN42数据请求失败:%s", resp.Status)
+ }
+ r = csv.NewReader(io.LimitReader(resp.Body, 1<<20))
} else {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
r = csv.NewReader(f)
}
return r.ReadAll()
})同 PTR:sync.OnceValues 缓存首次错误,建议后续引入 TTL/重试策略以提升健壮性。
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In dn42/geofeed.go around lines 39 to 64, the HTTP path handling ignores
NewRequest errors, doesn't close the response body, fails to check HTTP status
codes, reads without size limits, and unsafely type-asserts viper.Get to string
(and prints the path noisily); fix by validating viper.Get("geoFeedPath") is a
non-empty string before use, handle and return any error from http.NewRequest,
use http.Client.Do and immediately defer resp.Body.Close() after a nil error,
check resp.StatusCode and treat non-2xx as an error, wrap the body reader with a
size limiter (e.g., io.LimitReader or io.ReadAll with an imposed max) before
creating csv.NewReader, and remove or lower log.Println(path) to debug level;
also consider not caching first error forever by adding TTL/retry logic to the
sync.OnceValues usage.
| var getPtr = sync.OnceValues(func() ([][]string, error) { | ||
| path := viper.Get("ptrPath").(string) | ||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| return PtrRow{}, err | ||
| var r *csv.Reader | ||
| if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") { | ||
| client := &http.Client{ | ||
| // 10 秒超时 | ||
| Timeout: time.Duration(10) * time.Second, | ||
| } | ||
| req, _ := http.NewRequest("GET", path, nil) | ||
| content, err := client.Do(req) | ||
| if err != nil { | ||
| log.Println("DN42数据请求超时,请更换其他数据源或使用本地数据") | ||
| return nil, err | ||
| } | ||
| r = csv.NewReader(content.Body) | ||
| } else { | ||
| f, err := os.Open(path) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| defer f.Close() | ||
| r = csv.NewReader(f) | ||
| } | ||
| defer f.Close() | ||
| return r.ReadAll() | ||
| }) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
HTTP 获取存在资源泄漏与潜在 panic;建议完善错误处理与健壮性
- 忽略
http.NewRequest的错误,req可能为 nil → 触发 panic。 - 未检查
StatusCode。 - 未
Close()响应体 → 资源泄漏。 - 仅打印“超时”,掩盖其他错误类型。
- 直接断言
viper.Get(...).(string)有 panic 风险;建议用viper.GetString. - 建议对远端响应加大小上限,避免异常大响应耗内存。
可按此最小变更修复:
@@
-import (
+import (
"encoding/csv"
"errors"
"fmt"
- "log"
+ "io"
+ "log"
"net/http"
"os"
"regexp"
"strings"
"sync"
"time"
@@
-var getPtr = sync.OnceValues(func() ([][]string, error) {
- path := viper.Get("ptrPath").(string)
+var getPtr = sync.OnceValues(func() ([][]string, error) {
+ path := viper.GetString("ptrPath")
var r *csv.Reader
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
client := &http.Client{
// 10 秒超时
Timeout: time.Duration(10) * time.Second,
}
- req, _ := http.NewRequest("GET", path, nil)
- content, err := client.Do(req)
- if err != nil {
- log.Println("DN42数据请求超时,请更换其他数据源或使用本地数据")
- return nil, err
- }
- r = csv.NewReader(content.Body)
+ req, err := http.NewRequest("GET", path, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := client.Do(req)
+ if err != nil {
+ log.Printf("DN42数据请求失败:%v", err)
+ return nil, err
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("DN42数据请求失败:%s", resp.Status)
+ }
+ // 限制读取大小(1 MiB)
+ r = csv.NewReader(io.LimitReader(resp.Body, 1<<20))
} else {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
r = csv.NewReader(f)
}
return r.ReadAll()
})另外:sync.OnceValues 会把“失败结果”也永久缓存,首次网络抖动后将一直返回错误直至进程重启。建议改为带重试/TTL 的可刷新缓存或 singleflight+TTL(可后续提交实现)。
🏁 Script executed:
#!/bin/bash
# 核查 Go 版本是否兼容 sync.OnceValues(Go 1.22+)
rg -n '^(go|toolchain)\s+go1\.(\d+)' go.mod
echo '查找 OnceValues 用法:'
rg -n 'sync\.OnceValues' -g '!**/vendor/**'
# 粗查是否存在未 Close 的 Body 用法
echo '可能未关闭 Body 的位置:'
rg -n -C1 'csv\.NewReader\(.+\.Body\)' -g '!**/vendor/**'Length of output: 388
🏁 Script executed:
#!/bin/bash
cat go.mod | head -5
echo "---"
echo "dn42/ptr.go lines 30-70:"
sed -n '30,70p' dn42/ptr.go
echo "---"
echo "dn42/geofeed.go lines 30-70:"
sed -n '30,70p' dn42/geofeed.goLength of output: 1849
🏁 Script executed:
#!/bin/bash
echo "dn42/ptr.go lines 61-100 (FindPtrRecord):"
sed -n '61,100p' dn42/ptr.go
echo "---"
echo "dn42/ptr.go lines 100-130 (rest):"
sed -n '100,130p' dn42/ptr.goLength of output: 881
HTTP 获取存在资源泄漏与多个潜在 panic;必须完善错误处理与数据校验
验证结果确认以下问题均存在:
- 忽略
http.NewRequest的错误,req可能为 nil →client.Do(req)触发 panic - 未检查
StatusCode - 未
Close()响应体 → 资源泄漏 - 直接断言
viper.Get(...).(string)有 panic 风险;建议用viper.GetString - 访问
row[0]、row[1]、row[2]、row[3]无边界检查 → 若 CSV 行不足 4 列则 index out of range panic - 返回结果中 IATA 被转小写(第 95 行),改变了原始数据
sync.OnceValues会把失败结果也永久缓存,首次网络抖动后将一直返回错误直至进程重启
建议修改(同时适用于 dn42/geofeed.go 中的相同模式):
@@
+import (
"encoding/csv"
"errors"
"fmt"
+ "io"
"log"
"net/http"
"os"
"regexp"
"strings"
"sync"
"time"
@@
var getPtr = sync.OnceValues(func() ([][]string, error) {
- path := viper.Get("ptrPath").(string)
+ path := viper.GetString("ptrPath")
var r *csv.Reader
if strings.HasPrefix(path, "http://") || strings.HasPrefix(path, "https://") {
client := &http.Client{
Timeout: time.Duration(10) * time.Second,
}
- req, _ := http.NewRequest("GET", path, nil)
- content, err := client.Do(req)
+ req, err := http.NewRequest("GET", path, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := client.Do(req)
if err != nil {
- log.Println("DN42数据请求超时,请更换其他数据源或使用本地数据")
+ log.Printf("DN42数据请求失败:%v", err)
return nil, err
}
- r = csv.NewReader(content.Body)
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("DN42数据请求失败:%s", resp.Status)
+ }
+ r = csv.NewReader(io.LimitReader(resp.Body, 1<<20))
} else {
f, err := os.Open(path)
if err != nil {
return nil, err
}
defer f.Close()
r = csv.NewReader(f)
}
return r.ReadAll()
})
@@
- // 查 IATA Code
for _, row := range rows {
+ if len(row) < 4 {
+ continue
+ }
iata := row[0]
if iata == "" {
continue
}
- iata = strings.ToLower(iata)
if matchesPattern(iata, ptr) {
return PtrRow{
- IATACode: iata,
+ IATACode: row[0],
LtdCode: row[1],
Region: row[2],
City: row[3],
}, nil
}
}另外在 city 循环处也应加边界检查(第 73 行之后):
for _, row := range rows {
+ if len(row) < 4 {
+ continue
+ }
city := row[3]Committable suggestion skipped: line range outside the PR's diff.
|
https://cryolitia.github.io/Next-Trace-DN42-Feeder/ptr.csv 我不懂DN42,这两个文件的出处在哪里,适用于所有人吗? |
这两个文件根据 https://github.com/Cryolitia/Next-Trace-DN42-Feeder 中的规则每日构建 其中 geofeed.csv 来自DN42 registry,而DN42 registry又导入了NeoNetwork。用这个构建出来的数据对这两个网络的玩家都是开箱即用的。ptr.csv 使用了三个不同来源的IATA数据库,构建了世界上绝大部分机场(现实意义上的机场)和他们所在的城市国家的结果。 对于极个别不适用这些预构建数据的人,他们只需要自己写个配置文件指向自己的数据库就可以了,这只是增加了默认选项,并没有削弱原本的能力。 |
对于IATA数据库,如果你们拉黑了ip2location的话还有其他两个公开数据集,这几个都只是我在网上搜索得到的,都有与NextTrace兼容的开源协议,而且在积极维护。 对于DN42注册表,官方注册表无法匿名访问,我找的是蓝天的镜像,蓝天是DN42社区比较有知名度的成员和比较大的网络节点,应当是可以信任的。 不确定您指的保证安全是什么意思,csv怎么看也不像是能注入木马病毒的东西。 |
|
还在沟通专业人士来review这份PR,因为我实在不懂DN42,可能需要再等等了 |
|
为什么关闭了这个PR |
|
暂无合适的人员review,当有的时候会重启merge进程 |
内核工程师没有使用任何AI一晚上速成go的故事将 DN42 模式的默认行为更改为联网下载预构建的数据库,两个库都只有200kb左右所以直接把整个下载下来应该是比较合适的。
暂时用了我的GitHub Pages,或许可以把这个预构建仓库transfer到组织里然后用你们的GitHub Pages或可能存在的什么分发网络,建议不要直接用我的地址合并
Summary by CodeRabbit
发布说明
新功能
改进
配置变更