Skip to content

Commit 3666490

Browse files
authored
Merge pull request #10 from Daniel-Ric/feature/2026-02-03/enhance-tool-with-ip-lookup-options-mh6iqt
Load IANA TLDs for lookup pool, add per-host timeouts and graceful Ctrl+C
2 parents a35fdbf + dee7ba2 commit 3666490

4 files changed

Lines changed: 139 additions & 16 deletions

File tree

internal/cli/cli.go

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,32 @@ type App struct {
1818
func NewApp() *App {
1919
return &App{
2020
inputTimeout: 3 * time.Second,
21-
lookupTimeout: 12 * time.Second,
21+
lookupTimeout: 45 * time.Second,
2222
}
2323
}
2424

2525
func (a *App) Run() error {
2626
for {
2727
config, err := a.collectConfig()
2828
if err != nil {
29+
if errors.Is(err, errAborted) {
30+
return nil
31+
}
2932
return err
3033
}
3134

3235
if err := a.execute(config); err != nil {
36+
if errors.Is(err, errAborted) {
37+
return nil
38+
}
3339
return err
3440
}
3541

3642
again, err := a.askAgain()
3743
if err != nil {
44+
if errors.Is(err, errAborted) {
45+
return nil
46+
}
3847
return err
3948
}
4049
if !again {
@@ -225,7 +234,11 @@ func (a *App) askDomainEndings() ([]string, error) {
225234
return nil, err
226235
}
227236
if index == 1 {
228-
return domainEndingPool, nil
237+
endings, err := loadDomainEndings()
238+
if err != nil {
239+
return endings, nil
240+
}
241+
return endings, nil
229242
}
230243
var errMsg string
231244
for {
@@ -300,12 +313,13 @@ func (a *App) executeLookup(config LookupConfig) error {
300313

301314
resultText, err := withSpinner("IP Lookup", "Domains werden überprüft", 120*time.Millisecond, func() (string, error) {
302315
result, lookupErr := ping.LookupDomains(ctx, ping.LookupConfig{
303-
Edition: config.Edition,
304-
Port: config.Port,
305-
BaseHost: config.BaseHost,
306-
Subdomains: config.Subdomains,
307-
DomainEndings: config.Endings,
308-
Concurrency: 24,
316+
Edition: config.Edition,
317+
Port: config.Port,
318+
BaseHost: config.BaseHost,
319+
Subdomains: config.Subdomains,
320+
DomainEndings: config.Endings,
321+
Concurrency: 24,
322+
PerHostTimeout: 2 * time.Second,
309323
})
310324
if lookupErr != nil && !errors.Is(lookupErr, context.DeadlineExceeded) {
311325
return "", lookupErr

internal/cli/lookup_endings.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package cli
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"fmt"
7+
"net/http"
8+
"strings"
9+
"sync"
10+
"time"
11+
)
12+
13+
const ianaTLDSource = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt"
14+
15+
var (
16+
domainEndingsOnce sync.Once
17+
domainEndingsCache []string
18+
domainEndingsErr error
19+
)
20+
21+
func loadDomainEndings() ([]string, error) {
22+
domainEndingsOnce.Do(func() {
23+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
24+
defer cancel()
25+
26+
iana, err := fetchIANATLDs(ctx)
27+
if err != nil {
28+
domainEndingsErr = fmt.Errorf("konnte endungs-pool nicht laden: %w", err)
29+
domainEndingsCache = domainEndingPool
30+
return
31+
}
32+
domainEndingsCache = mergeUniqueEndings(iana, domainEndingPool)
33+
})
34+
35+
if domainEndingsErr != nil {
36+
return domainEndingsCache, domainEndingsErr
37+
}
38+
return domainEndingsCache, nil
39+
}
40+
41+
func fetchIANATLDs(ctx context.Context) ([]string, error) {
42+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, ianaTLDSource, nil)
43+
if err != nil {
44+
return nil, err
45+
}
46+
resp, err := http.DefaultClient.Do(req)
47+
if err != nil {
48+
return nil, err
49+
}
50+
defer resp.Body.Close()
51+
if resp.StatusCode != http.StatusOK {
52+
return nil, fmt.Errorf("iana antwort: %s", resp.Status)
53+
}
54+
55+
scanner := bufio.NewScanner(resp.Body)
56+
list := make([]string, 0, 2000)
57+
for scanner.Scan() {
58+
line := strings.TrimSpace(scanner.Text())
59+
if line == "" || strings.HasPrefix(line, "#") {
60+
continue
61+
}
62+
list = append(list, strings.ToLower(line))
63+
}
64+
if err := scanner.Err(); err != nil {
65+
return nil, err
66+
}
67+
if len(list) == 0 {
68+
return nil, fmt.Errorf("keine endungen gefunden")
69+
}
70+
return list, nil
71+
}
72+
73+
func mergeUniqueEndings(primary, secondary []string) []string {
74+
seen := make(map[string]struct{}, len(primary)+len(secondary))
75+
list := make([]string, 0, len(primary)+len(secondary))
76+
for _, value := range primary {
77+
value = normalizeEnding(value)
78+
if value == "" {
79+
continue
80+
}
81+
if _, ok := seen[value]; ok {
82+
continue
83+
}
84+
seen[value] = struct{}{}
85+
list = append(list, value)
86+
}
87+
for _, value := range secondary {
88+
value = normalizeEnding(value)
89+
if value == "" {
90+
continue
91+
}
92+
if _, ok := seen[value]; ok {
93+
continue
94+
}
95+
seen[value] = struct{}{}
96+
list = append(list, value)
97+
}
98+
return list
99+
}

internal/cli/ui.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ const (
1717
colorBold = "\033[1m"
1818
)
1919

20+
var errAborted = errors.New("abgebrochen")
21+
2022
func supportsColor() bool {
2123
if os.Getenv("NO_COLOR") != "" {
2224
return false
@@ -74,7 +76,7 @@ func selectOption(title string, options []string) (int, error) {
7476

7577
switch b {
7678
case 3:
77-
return 0, errors.New("abgebrochen")
79+
return 0, errAborted
7880
case 'w', 'k':
7981
if selected > 0 {
8082
selected--

internal/ping/lookup.go

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,17 @@ import (
66
"strings"
77
"sync"
88
"sync/atomic"
9+
"time"
910
)
1011

1112
type LookupConfig struct {
12-
Edition Edition
13-
Port int
14-
BaseHost string
15-
Subdomains []string
16-
DomainEndings []string
17-
Concurrency int
13+
Edition Edition
14+
Port int
15+
BaseHost string
16+
Subdomains []string
17+
DomainEndings []string
18+
Concurrency int
19+
PerHostTimeout time.Duration
1820
}
1921

2022
type LookupMatch struct {
@@ -47,6 +49,10 @@ func LookupDomains(ctx context.Context, config LookupConfig) (LookupResult, erro
4749
if concurrency <= 0 {
4850
concurrency = 16
4951
}
52+
perHostTimeout := config.PerHostTimeout
53+
if perHostTimeout <= 0 {
54+
perHostTimeout = 2 * time.Second
55+
}
5056

5157
total := len(subdomains) * len(endings)
5258
if total == 0 {
@@ -67,7 +73,9 @@ func LookupDomains(ctx context.Context, config LookupConfig) (LookupResult, erro
6773
default:
6874
}
6975

70-
res, err := Execute(ctx, config.Edition, host, config.Port)
76+
hostCtx, cancel := context.WithTimeout(ctx, perHostTimeout)
77+
res, err := Execute(hostCtx, config.Edition, host, config.Port)
78+
cancel()
7179
atomic.AddInt64(&completed, 1)
7280
if err != nil {
7381
continue

0 commit comments

Comments
 (0)