blob: ec847bde6facc372973491bb537afabb324af8f5 [file] [log] [blame]
Philip Zeyliger5f26a342025-07-04 01:30:29 +00001package loop
2
3import (
4 "context"
Philip Zeyliger9b39aa62025-07-14 11:56:02 -07005 "os"
6 "os/exec"
Philip Zeyliger5f26a342025-07-04 01:30:29 +00007 "testing"
8 "time"
9
10 "tailscale.com/portlist"
11)
12
13// TestPortMonitor_NewPortMonitor tests the creation of a new PortMonitor.
14func TestPortMonitor_NewPortMonitor(t *testing.T) {
15 agent := createTestAgent(t)
16 interval := 2 * time.Second
17
18 pm := NewPortMonitor(agent, interval)
19
20 if pm == nil {
21 t.Fatal("NewPortMonitor returned nil")
22 }
23
24 if pm.agent != agent {
25 t.Errorf("expected agent %v, got %v", agent, pm.agent)
26 }
27
28 if pm.interval != interval {
29 t.Errorf("expected interval %v, got %v", interval, pm.interval)
30 }
31
32 if pm.running {
33 t.Error("expected monitor to not be running initially")
34 }
35
36 if pm.poller == nil {
37 t.Error("expected poller to be initialized")
38 }
39
40 if !pm.poller.IncludeLocalhost {
41 t.Error("expected IncludeLocalhost to be true")
42 }
43}
44
45// TestPortMonitor_DefaultInterval tests that a default interval is set when invalid.
46func TestPortMonitor_DefaultInterval(t *testing.T) {
47 agent := createTestAgent(t)
48
49 pm := NewPortMonitor(agent, 0)
50 if pm.interval != 5*time.Second {
51 t.Errorf("expected default interval 5s, got %v", pm.interval)
52 }
53
54 pm2 := NewPortMonitor(agent, -1*time.Second)
55 if pm2.interval != 5*time.Second {
56 t.Errorf("expected default interval 5s, got %v", pm2.interval)
57 }
58}
59
60// TestPortMonitor_StartStop tests starting and stopping the monitor.
61func TestPortMonitor_StartStop(t *testing.T) {
62 agent := createTestAgent(t)
63 pm := NewPortMonitor(agent, 100*time.Millisecond)
64
65 // Test starting
66 ctx := context.Background()
67 err := pm.Start(ctx)
68 if err != nil {
69 t.Fatalf("failed to start port monitor: %v", err)
70 }
71
72 if !pm.running {
73 t.Error("expected monitor to be running after start")
74 }
75
76 // Test double start fails
77 err = pm.Start(ctx)
78 if err == nil {
79 t.Error("expected error when starting already running monitor")
80 }
81
82 // Test stopping
83 pm.Stop()
84 if pm.running {
85 t.Error("expected monitor to not be running after stop")
86 }
87
88 // Test double stop is safe
89 pm.Stop() // should not panic
90}
91
92// TestPortMonitor_GetPorts tests getting the cached port list.
93func TestPortMonitor_GetPorts(t *testing.T) {
94 agent := createTestAgent(t)
95 pm := NewPortMonitor(agent, 100*time.Millisecond)
96
97 // Initially should be empty
98 ports := pm.GetPorts()
99 if len(ports) != 0 {
100 t.Errorf("expected empty ports initially, got %d", len(ports))
101 }
102
103 // Start monitoring to populate ports
104 ctx := context.Background()
105 err := pm.Start(ctx)
106 if err != nil {
107 t.Fatalf("failed to start port monitor: %v", err)
108 }
109 defer pm.Stop()
110
111 // Allow some time for initial scan
112 time.Sleep(200 * time.Millisecond)
113
114 // Should have some ports now (at least system ports)
115 ports = pm.GetPorts()
116 // We can't guarantee specific ports, but there should be at least some TCP ports
117 // on most systems (like SSH, etc.)
118 t.Logf("Found %d TCP ports", len(ports))
119
120 // Verify all returned ports are TCP
121 for _, port := range ports {
122 if port.Proto != "tcp" {
123 t.Errorf("expected TCP port, got %s", port.Proto)
124 }
125 }
126}
127
Philip Zeyliger5f26a342025-07-04 01:30:29 +0000128// TestPortMonitor_FilterTCPPorts tests the TCP port filtering.
129func TestPortMonitor_FilterTCPPorts(t *testing.T) {
130 ports := []portlist.Port{
131 {Proto: "tcp", Port: 80},
132 {Proto: "udp", Port: 53},
133 {Proto: "tcp", Port: 443},
134 {Proto: "udp", Port: 123},
135 }
136
137 tcpPorts := filterTCPPorts(ports)
138
139 if len(tcpPorts) != 2 {
140 t.Errorf("expected 2 TCP ports, got %d", len(tcpPorts))
141 }
142
143 for _, port := range tcpPorts {
144 if port.Proto != "tcp" {
145 t.Errorf("expected TCP port, got %s", port.Proto)
146 }
147 }
148}
149
150// TestPortMonitor_SortPorts tests the port sorting.
151func TestPortMonitor_SortPorts(t *testing.T) {
152 ports := []portlist.Port{
153 {Proto: "tcp", Port: 443},
154 {Proto: "tcp", Port: 80},
155 {Proto: "tcp", Port: 8080},
156 {Proto: "tcp", Port: 22},
157 }
158
159 sortPorts(ports)
160
161 expected := []uint16{22, 80, 443, 8080}
162 for i, port := range ports {
163 if port.Port != expected[i] {
164 t.Errorf("expected port %d at index %d, got %d", expected[i], i, port.Port)
165 }
166 }
167}
168
169// TestPortMonitor_FindAddedPorts tests finding added ports.
170func TestPortMonitor_FindAddedPorts(t *testing.T) {
171 previous := []portlist.Port{
172 {Proto: "tcp", Port: 80},
173 {Proto: "tcp", Port: 443},
174 }
175
176 current := []portlist.Port{
177 {Proto: "tcp", Port: 80},
178 {Proto: "tcp", Port: 443},
179 {Proto: "tcp", Port: 8080},
180 {Proto: "tcp", Port: 22},
181 }
182
183 added := findAddedPorts(previous, current)
184
185 if len(added) != 2 {
186 t.Errorf("expected 2 added ports, got %d", len(added))
187 }
188
189 addedPorts := make(map[uint16]bool)
190 for _, port := range added {
191 addedPorts[port.Port] = true
192 }
193
194 if !addedPorts[8080] || !addedPorts[22] {
195 t.Errorf("expected ports 8080 and 22 to be added, got %v", added)
196 }
197}
198
199// TestPortMonitor_FindRemovedPorts tests finding removed ports.
200func TestPortMonitor_FindRemovedPorts(t *testing.T) {
201 previous := []portlist.Port{
202 {Proto: "tcp", Port: 80},
203 {Proto: "tcp", Port: 443},
204 {Proto: "tcp", Port: 8080},
205 {Proto: "tcp", Port: 22},
206 }
207
208 current := []portlist.Port{
209 {Proto: "tcp", Port: 80},
210 {Proto: "tcp", Port: 443},
211 }
212
213 removed := findRemovedPorts(previous, current)
214
215 if len(removed) != 2 {
216 t.Errorf("expected 2 removed ports, got %d", len(removed))
217 }
218
219 removedPorts := make(map[uint16]bool)
220 for _, port := range removed {
221 removedPorts[port.Port] = true
222 }
223
224 if !removedPorts[8080] || !removedPorts[22] {
225 t.Errorf("expected ports 8080 and 22 to be removed, got %v", removed)
226 }
227}
228
Philip Zeyliger9b39aa62025-07-14 11:56:02 -0700229// TestPortMonitor_ShouldIgnoreProcess tests the shouldIgnoreProcess function.
230func TestPortMonitor_ShouldIgnoreProcess(t *testing.T) {
231 agent := createTestAgent(t)
232 pm := NewPortMonitor(agent, 100*time.Millisecond)
233
234 // Test with current process (should not be ignored)
235 currentPid := os.Getpid()
236 if pm.shouldIgnoreProcess(currentPid) {
237 t.Errorf("current process should not be ignored")
238 }
239
240 // Test with invalid PID
241 if pm.shouldIgnoreProcess(0) {
242 t.Errorf("invalid PID should not be ignored")
243 }
244 if pm.shouldIgnoreProcess(-1) {
245 t.Errorf("negative PID should not be ignored")
246 }
247
248 // Test with a process that has SKETCH_IGNORE_PORTS=1
249 cmd := exec.Command("sleep", "5")
250 cmd.Env = append(os.Environ(), "SKETCH_IGNORE_PORTS=1")
251 err := cmd.Start()
252 if err != nil {
253 t.Fatalf("failed to start test process: %v", err)
254 }
255 defer cmd.Process.Kill()
256
257 // Allow a moment for the process to start
258 time.Sleep(100 * time.Millisecond)
259
260 if !pm.shouldIgnoreProcess(cmd.Process.Pid) {
261 t.Errorf("process with SKETCH_IGNORE_PORTS=1 should be ignored")
262 }
263}
264
Philip Zeyliger5f26a342025-07-04 01:30:29 +0000265// createTestAgent creates a minimal test agent for testing.
266func createTestAgent(t *testing.T) *Agent {
267 // Create a minimal agent for testing
268 // We need to initialize the required fields for the PortMonitor to work
269 agent := &Agent{
270 subscribers: make([]chan *AgentMessage, 0),
271 }
272 return agent
273}