Skip to content

Commit d7ca2f7

Browse files
Merge pull request #3 from githubflyideas/feat/drop-list-files
targets: database only — remove ping.list/tcp.list and the import inbox
2 parents 698a750 + 809bf91 commit d7ca2f7

10 files changed

Lines changed: 122 additions & 347 deletions

File tree

README.md

Lines changed: 47 additions & 72 deletions
Large diffs are not rendered by default.

config.go

Lines changed: 4 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,17 @@ package main
33
import (
44
"fmt"
55
"net"
6-
"os"
7-
"path/filepath"
86
"strconv"
97
"strings"
108
"unicode"
119
"unicode/utf8"
1210
)
1311

14-
// Program parameters are constants. Targets live in SQLite (the targets table);
15-
// targets/*.list files are only an import inbox — see ingest.go.
12+
// Program parameters are constants. Targets live in SQLite (the targets table) and
13+
// are changed only through the web UI, when started with --edit.
1614
type Config struct {
1715
Listen string
1816
DataDir string
19-
TargetsDir string
2017
Probe ProbeCfg
2118
HotDays int // full samples kept this long
2219
RetentionDays int // downsampled data kept this long
@@ -27,7 +24,6 @@ func defaultConfig() *Config {
2724
return &Config{
2825
Listen: "0.0.0.0:8518",
2926
DataDir: "./data",
30-
TargetsDir: "./targets",
3127
Probe: ProbeCfg{IntervalSec: 60, Packets: 20, GapMs: 50, TimeoutMs: 1000},
3228
HotDays: 2, // raw samples: enough for the sub-day windows
3329
RetentionDays: 40, // hourly rollups; --days overrides
@@ -51,8 +47,8 @@ type ProbeCfg struct {
5147
TimeoutMs int
5248
}
5349

54-
// normalizeTarget is the single validation gate: web CRUD and list import both
55-
// pass through it, so the DB never holds a target the prober can't run.
50+
// normalizeTarget is the single validation gate for the web API, so the DB never
51+
// holds a target the prober can't run.
5652
func normalizeTarget(t *TargetCfg) error {
5753
t.Type = strings.TrimSpace(t.Type)
5854
t.Host = strings.TrimSpace(t.Host)
@@ -99,80 +95,3 @@ func targetAddr(t TargetCfg) string {
9995
}
10096
return t.Host
10197
}
102-
103-
// parseListFile reads one list file into validated targets. Any bad line rejects
104-
// the whole file: a half-imported list is harder to reason about than none.
105-
func parseListFile(path, typ string) ([]TargetCfg, error) {
106-
raw, err := os.ReadFile(path)
107-
if err != nil {
108-
return nil, err
109-
}
110-
var out []TargetCfg
111-
seen := map[string]bool{}
112-
for ln, line := range strings.Split(string(raw), "\n") {
113-
line = strings.TrimSpace(line)
114-
if line == "" || strings.HasPrefix(line, "#") {
115-
continue
116-
}
117-
t, err := parseListLine(line, typ)
118-
if err == nil {
119-
err = normalizeTarget(&t)
120-
}
121-
if err == nil && seen[t.Name] {
122-
err = fmt.Errorf("duplicate name %q", t.Name)
123-
}
124-
if err != nil {
125-
return nil, fmt.Errorf("%s line %d: %w", filepath.Base(path), ln+1, err)
126-
}
127-
seen[t.Name] = true
128-
out = append(out, t)
129-
}
130-
return out, nil
131-
}
132-
133-
// line format: host[:port] [name...] [pace=fast|slow] [interval=sec]
134-
func parseListLine(line, typ string) (TargetCfg, error) {
135-
t := TargetCfg{Type: typ}
136-
fields := strings.Fields(line)
137-
if len(fields) == 0 { // fuzz 发现:纯空白行会越界 —— 上游会挡,但函数自身必须皮实
138-
return t, fmt.Errorf("empty line")
139-
}
140-
addr := fields[0]
141-
if typ == "tcp" {
142-
host, portStr, err := net.SplitHostPort(addr)
143-
if err != nil {
144-
return t, fmt.Errorf("tcp target %q must be host:port", addr)
145-
}
146-
port, err := strconv.Atoi(portStr)
147-
if err != nil || port <= 0 {
148-
return t, fmt.Errorf("tcp target %q: bad port", addr)
149-
}
150-
t.Host, t.Port = host, port
151-
} else {
152-
t.Host = addr
153-
}
154-
var nameParts []string
155-
for _, f := range fields[1:] {
156-
k, v, isKV := strings.Cut(f, "=")
157-
if !isKV {
158-
nameParts = append(nameParts, f)
159-
continue
160-
}
161-
switch k {
162-
case "pace":
163-
t.Pace = v
164-
case "interval":
165-
n, err := strconv.Atoi(v)
166-
if err != nil || n <= 0 {
167-
return t, fmt.Errorf("interval=%q invalid", v)
168-
}
169-
t.IntervalSec = n
170-
} // unknown k=v silently ignored: forward compatibility
171-
}
172-
if len(nameParts) > 0 {
173-
t.Name = strings.Join(nameParts, " ")
174-
} else {
175-
t.Name = addr
176-
}
177-
return t, nil
178-
}

fogping_test.go

Lines changed: 57 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,9 @@ import (
77
"strings"
88
"testing"
99
"time"
10+
"unicode/utf8"
1011
)
1112

12-
func TestParseListLine(t *testing.T) {
13-
tt, err := parseListLine("59.43.247.1 HK CN2 pace=fast", "icmp")
14-
if err != nil || tt.Name != "HK CN2" || tt.Pace != "fast" {
15-
t.Fatalf("icmp line: %+v %v", tt, err)
16-
}
17-
tt, err = parseListLine("10.0.0.5:443 gw interval=30", "tcp")
18-
if err != nil || tt.Host != "10.0.0.5" || tt.Port != 443 || tt.IntervalSec != 30 {
19-
t.Fatalf("tcp line: %+v %v", tt, err)
20-
}
21-
if _, err := parseListLine("noport name", "tcp"); err == nil {
22-
t.Fatal("tcp without port should fail")
23-
}
24-
}
25-
2613
func TestProbeParams(t *testing.T) {
2714
g := ProbeCfg{IntervalSec: 60, Packets: 20}
2815
if iv, pk := probeParams(TargetCfg{Pace: "fast"}, g); iv.Seconds() != 15 || pk != 30 {
@@ -69,22 +56,65 @@ func BenchmarkCalcStats24h(b *testing.B) {
6956
}
7057
}
7158

72-
func FuzzParseListLine(f *testing.F) {
73-
f.Add("1.2.3.4 name pace=fast", "icmp")
74-
f.Add("10.0.0.5:443 gw interval=30", "tcp")
75-
f.Add("host:99999 x", "tcp")
76-
f.Add("::1 v6", "icmp")
77-
f.Fuzz(func(t *testing.T, line, typ string) {
78-
if typ != "icmp" && typ != "tcp" {
79-
typ = "icmp"
59+
// normalizeTarget is the only gate between the web API and the targets table.
60+
func TestNormalizeTarget(t *testing.T) {
61+
ok := []TargetCfg{
62+
{Host: " 59.43.247.1 ", Name: "HK CN2", Pace: "fast"},
63+
{Type: "tcp", Host: "10.0.0.5", Port: 443, IntervalSec: 30},
64+
{Host: "example.com", Pace: "normal"},
65+
}
66+
for _, c := range ok {
67+
if err := normalizeTarget(&c); err != nil {
68+
t.Fatalf("%+v: %v", c, err)
8069
}
81-
if len(line) == 0 || line[0] == '#' {
70+
}
71+
c := TargetCfg{Type: "tcp", Host: "10.0.0.5", Port: 443}
72+
normalizeTarget(&c)
73+
if c.Name != "10.0.0.5:443" {
74+
t.Fatalf("tcp default name: %q", c.Name)
75+
}
76+
c = TargetCfg{Host: "1.1.1.1", Pace: "normal"}
77+
normalizeTarget(&c)
78+
if c.Pace != "" || c.Name != "1.1.1.1" || c.Type != "icmp" {
79+
t.Fatalf("defaults: %+v", c)
80+
}
81+
bad := []TargetCfg{
82+
{Host: ""},
83+
{Host: "a b"},
84+
{Type: "tcp", Host: "h"},
85+
{Type: "tcp", Host: "h", Port: 70000},
86+
{Type: "udp", Host: "h"},
87+
{Host: "h", Pace: "turbo"},
88+
{Host: "h", IntervalSec: -1},
89+
{Host: "h", Name: strings.Repeat("x", 65)},
90+
{Host: "h", Name: "a\x00b"},
91+
}
92+
for _, c := range bad {
93+
if err := normalizeTarget(&c); err == nil {
94+
t.Fatalf("accepted %+v", c)
95+
}
96+
}
97+
}
98+
99+
func FuzzNormalizeTarget(f *testing.F) {
100+
f.Add("icmp", "1.1.1.1", 0, "", "fast", 0)
101+
f.Add("tcp", "10.0.0.5", 443, "gw", "", 30)
102+
f.Add("", " ", -1, "\x00", "normal", 99999)
103+
f.Fuzz(func(t *testing.T, typ, host string, port int, name, pace string, iv int) {
104+
c := TargetCfg{Type: typ, Host: host, Port: port, Name: name, Pace: pace, IntervalSec: iv}
105+
if normalizeTarget(&c) != nil {
82106
return
83107
}
84-
// 只要求不 panic、不接受空 host
85-
tt, err := parseListLine(line, typ)
86-
if err == nil && tt.Host == "" {
87-
t.Fatalf("accepted empty host: %q", line)
108+
// whatever gets in must be something the prober can run and the UI can show
109+
switch {
110+
case c.Host == "" || strings.ContainsAny(c.Host, " \t\n"):
111+
t.Fatalf("bad host accepted: %q", c.Host)
112+
case c.Type != "icmp" && c.Type != "tcp":
113+
t.Fatalf("bad type accepted: %q", c.Type)
114+
case c.Type == "tcp" && (c.Port < 1 || c.Port > 65535):
115+
t.Fatalf("bad port accepted: %d", c.Port)
116+
case c.Name == "" || utf8.RuneCountInString(c.Name) > 64:
117+
t.Fatalf("bad name accepted: %q", c.Name)
88118
}
89119
})
90120
}

ingest.go

Lines changed: 0 additions & 94 deletions
This file was deleted.

main.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,8 @@ func main() {
5050
if err != nil {
5151
log.Fatalf("store init failed: %v", err)
5252
}
53-
os.MkdirAll(cfg.TargetsDir, 0o755)
54-
if _, err := ingestLists(cfg.TargetsDir, store); err != nil {
55-
log.Printf("ingest: %v", err)
56-
}
5753
if n, err := store.TargetRows(); err == nil && n == 0 {
58-
if err := store.ImportTargets([]TargetCfg{demoTarget}); err == nil {
54+
if _, err := store.CreateTarget(demoTarget); err == nil {
5955
log.Printf("new database — seeded a demo target (www.google.com)")
6056
}
6157
}
@@ -67,7 +63,6 @@ func main() {
6763
}
6864
stop := make(chan struct{})
6965
go store.flushLoop(stop)
70-
go ingestLoop(cfg.TargetsDir, store, run, stop)
7166
go housekeeping(cfg, store, stop)
7267

7368
mode := "read-only targets (restart with --edit to change them in the web UI)"
@@ -77,7 +72,7 @@ func main() {
7772
log.Printf("fogping %s up · %d targets · %s · listening on %s · data in %s · %d-day retention",
7873
version, len(run.Targets()), mode, cfg.Listen, cfg.DataDir, cfg.RetentionDays)
7974
if len(run.Targets()) == 0 {
80-
log.Printf(`no active targets — add them with --edit, or: echo "1.2.3.4 my-link" >> %s/ping.list`, cfg.TargetsDir)
75+
log.Printf("no active targets — restart with --edit and add them in the web UI")
8176
}
8277
log.Printf("➜ open http://localhost%s for the smoke graph", portOf(cfg.Listen))
8378
if len(users) == 0 {

runner.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ import (
66
"sync"
77
)
88

9-
// Runner owns the set of running probe loops, keyed by target id. Every change
10-
// web edit, list import — goes through Reload, which diffs the DB against what is
9+
// Runner owns the set of running probe loops, keyed by target id. Every change
10+
// goes through Reload, which diffs the DB against what is
1111
// running. One mutex, one writer path; the web handlers only read snapshots.
1212
type Runner struct {
1313
mu sync.Mutex

0 commit comments

Comments
 (0)