-
-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathtor.go
150 lines (130 loc) · 5.12 KB
/
tor.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
package caddywaf
import (
"fmt" // Import fmt for improved error formatting
"io"
"net/http"
"os"
"sort"
"strings"
"time"
"github.com/caddyserver/caddy/v2"
"go.uber.org/zap"
)
var torExitNodeURL = "https://check.torproject.org/torbulkexitlist"
type TorConfig struct {
Enabled bool `json:"enabled,omitempty"`
TORIPBlacklistFile string `json:"tor_ip_blacklist_file,omitempty"`
UpdateInterval string `json:"update_interval,omitempty"`
RetryOnFailure bool `json:"retry_on_failure,omitempty"` // Enable/disable retries
RetryInterval string `json:"retry_interval,omitempty"` // Retry interval (e.g., "5m")
lastUpdated time.Time
logger *zap.Logger
}
// Provision sets up the Tor blocking configuration.
func (t *TorConfig) Provision(ctx caddy.Context) error {
t.logger = ctx.Logger()
if t.Enabled {
if err := t.updateTorExitNodes(); err != nil {
return fmt.Errorf("provisioning tor: %w", err) // Improved error wrapping
}
go t.scheduleUpdates()
}
return nil
}
// updateTorExitNodes fetches the latest Tor exit nodes and updates the IP blacklist.
func (t *TorConfig) updateTorExitNodes() error {
t.logger.Debug("Updating Tor exit nodes...") // Debug log at start of update
resp, err := http.Get(torExitNodeURL)
if err != nil {
return fmt.Errorf("http get failed for %s: %w", torExitNodeURL, err) // Improved error message with URL
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("http get returned status %s for %s", resp.Status, torExitNodeURL) // Check for non-200 status
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("failed to read response body from %s: %w", torExitNodeURL, err) // Improved error message with URL
}
torIPs := strings.Split(string(data), "\n")
existingIPs, err := t.readExistingBlacklist()
if err != nil {
return fmt.Errorf("failed to read existing blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
}
// Merge and deduplicate IPs
allIPs := append(existingIPs, torIPs...)
sort.Strings(allIPs)
uniqueIPs := unique(allIPs)
// Write updated blacklist to file
if err := t.writeBlacklist(uniqueIPs); err != nil {
return fmt.Errorf("failed to write updated blacklist to file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
}
t.lastUpdated = time.Now()
t.logger.Info("Tor exit nodes updated", zap.Int("count", len(uniqueIPs))) // Improved log message
t.logger.Debug("Tor exit node update completed successfully") // Debug log at end of update
return nil
}
// scheduleUpdates periodically updates the Tor exit node list.
func (t *TorConfig) scheduleUpdates() {
interval, err := time.ParseDuration(t.UpdateInterval)
if err != nil {
t.logger.Error("Invalid update interval", zap.String("interval", t.UpdateInterval), zap.Error(err))
return
}
var retryInterval time.Duration
if t.RetryOnFailure {
retryInterval, err = time.ParseDuration(t.RetryInterval)
if err != nil {
t.logger.Error("Invalid retry interval, disabling retries", zap.String("retry_interval", t.RetryInterval), zap.Error(err))
t.RetryOnFailure = false // Disable retries if the interval is invalid
}
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
// Use for range to iterate over the ticker channel
for range ticker.C {
if updateErr := t.updateTorExitNodes(); updateErr != nil { // Renamed err to updateErr for clarity
if t.RetryOnFailure {
t.logger.Error("Failed to update Tor exit nodes, retrying shortly", zap.Error(updateErr)) // Use updateErr
time.Sleep(retryInterval)
continue
} else {
t.logger.Error("Failed to update Tor exit nodes, will retry at next scheduled interval", zap.Error(updateErr)) // Use updateErr
}
}
}
}
// readExistingBlacklist reads the current IP blacklist file.
func (t *TorConfig) readExistingBlacklist() ([]string, error) {
data, err := os.ReadFile(t.TORIPBlacklistFile)
if err != nil {
if os.IsNotExist(err) {
t.logger.Debug("Blacklist file does not exist, assuming empty list", zap.String("path", t.TORIPBlacklistFile)) // Debug log for non-existent file
return nil, nil
}
return nil, fmt.Errorf("failed to read IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
}
return strings.Split(string(data), "\n"), nil
}
// writeBlacklist writes the updated IP blacklist to the file.
func (t *TorConfig) writeBlacklist(ips []string) error {
data := strings.Join(ips, "\n")
err := os.WriteFile(t.TORIPBlacklistFile, []byte(data), 0644)
if err != nil {
return fmt.Errorf("failed to write IP blacklist file %s: %w", t.TORIPBlacklistFile, err) // Improved error message with filename
}
t.logger.Debug("Blacklist file updated", zap.String("path", t.TORIPBlacklistFile), zap.Int("entry_count", len(ips))) // Debug log for file update
return nil
}
// unique removes duplicate entries from a slice of strings.
func unique(slice []string) []string {
keys := make(map[string]bool)
result := []string{}
for _, entry := range slice {
if _, exists := keys[entry]; !exists {
keys[entry] = true
result = append(result, entry)
}
}
return result
}