blob: 5ad5c2ec57369588cb3a0a79222f94f2c79865d9 [file] [log] [blame]
Philip Zeyliger5f26a342025-07-04 01:30:29 +00001package loop
2
3import (
4 "context"
5 "net"
6 "testing"
7 "time"
8
9 "tailscale.com/portlist"
10)
11
12// TestPortMonitor_NewPortMonitor tests the creation of a new PortMonitor.
13func TestPortMonitor_NewPortMonitor(t *testing.T) {
14 agent := createTestAgent(t)
15 interval := 2 * time.Second
16
17 pm := NewPortMonitor(agent, interval)
18
19 if pm == nil {
20 t.Fatal("NewPortMonitor returned nil")
21 }
22
23 if pm.agent != agent {
24 t.Errorf("expected agent %v, got %v", agent, pm.agent)
25 }
26
27 if pm.interval != interval {
28 t.Errorf("expected interval %v, got %v", interval, pm.interval)
29 }
30
31 if pm.running {
32 t.Error("expected monitor to not be running initially")
33 }
34
35 if pm.poller == nil {
36 t.Error("expected poller to be initialized")
37 }
38
39 if !pm.poller.IncludeLocalhost {
40 t.Error("expected IncludeLocalhost to be true")
41 }
42}
43
44// TestPortMonitor_DefaultInterval tests that a default interval is set when invalid.
45func TestPortMonitor_DefaultInterval(t *testing.T) {
46 agent := createTestAgent(t)
47
48 pm := NewPortMonitor(agent, 0)
49 if pm.interval != 5*time.Second {
50 t.Errorf("expected default interval 5s, got %v", pm.interval)
51 }
52
53 pm2 := NewPortMonitor(agent, -1*time.Second)
54 if pm2.interval != 5*time.Second {
55 t.Errorf("expected default interval 5s, got %v", pm2.interval)
56 }
57}
58
59// TestPortMonitor_StartStop tests starting and stopping the monitor.
60func TestPortMonitor_StartStop(t *testing.T) {
61 agent := createTestAgent(t)
62 pm := NewPortMonitor(agent, 100*time.Millisecond)
63
64 // Test starting
65 ctx := context.Background()
66 err := pm.Start(ctx)
67 if err != nil {
68 t.Fatalf("failed to start port monitor: %v", err)
69 }
70
71 if !pm.running {
72 t.Error("expected monitor to be running after start")
73 }
74
75 // Test double start fails
76 err = pm.Start(ctx)
77 if err == nil {
78 t.Error("expected error when starting already running monitor")
79 }
80
81 // Test stopping
82 pm.Stop()
83 if pm.running {
84 t.Error("expected monitor to not be running after stop")
85 }
86
87 // Test double stop is safe
88 pm.Stop() // should not panic
89}
90
91// TestPortMonitor_GetPorts tests getting the cached port list.
92func TestPortMonitor_GetPorts(t *testing.T) {
93 agent := createTestAgent(t)
94 pm := NewPortMonitor(agent, 100*time.Millisecond)
95
96 // Initially should be empty
97 ports := pm.GetPorts()
98 if len(ports) != 0 {
99 t.Errorf("expected empty ports initially, got %d", len(ports))
100 }
101
102 // Start monitoring to populate ports
103 ctx := context.Background()
104 err := pm.Start(ctx)
105 if err != nil {
106 t.Fatalf("failed to start port monitor: %v", err)
107 }
108 defer pm.Stop()
109
110 // Allow some time for initial scan
111 time.Sleep(200 * time.Millisecond)
112
113 // Should have some ports now (at least system ports)
114 ports = pm.GetPorts()
115 // We can't guarantee specific ports, but there should be at least some TCP ports
116 // on most systems (like SSH, etc.)
117 t.Logf("Found %d TCP ports", len(ports))
118
119 // Verify all returned ports are TCP
120 for _, port := range ports {
121 if port.Proto != "tcp" {
122 t.Errorf("expected TCP port, got %s", port.Proto)
123 }
124 }
125}
126
127// TestPortMonitor_PortDetection tests actual port detection with a test server.
128func TestPortMonitor_PortDetection(t *testing.T) {
129 agent := createTestAgent(t)
130 pm := NewPortMonitor(agent, 50*time.Millisecond) // Fast polling for test
131
132 ctx := context.Background()
133 err := pm.Start(ctx)
134 if err != nil {
135 t.Fatalf("failed to start port monitor: %v", err)
136 }
137 defer pm.Stop()
138
139 // Allow initial scan
140 time.Sleep(100 * time.Millisecond)
141
142 // Get initial port count
143 initialPorts := pm.GetPorts()
144 initialCount := len(initialPorts)
145
146 // Start a test server
147 listener, err := net.Listen("tcp", "127.0.0.1:0")
148 if err != nil {
149 t.Fatalf("failed to start test listener: %v", err)
150 }
151 defer listener.Close()
152
153 addr := listener.Addr().(*net.TCPAddr)
154 testPort := uint16(addr.Port)
155
156 t.Logf("Started test server on port %d", testPort)
157
158 // Wait for port to be detected
159 detected := false
160 for i := 0; i < 50; i++ { // Wait up to 2.5 seconds
161 time.Sleep(50 * time.Millisecond)
162 ports := pm.GetPorts()
163 for _, port := range ports {
164 if port.Port == testPort {
165 detected = true
166 break
167 }
168 }
169 if detected {
170 break
171 }
172 }
173
174 if !detected {
175 t.Errorf("test port %d was not detected", testPort)
176 }
177
178 // Verify port count increased
179 currentPorts := pm.GetPorts()
180 if len(currentPorts) <= initialCount {
181 t.Errorf("expected port count to increase from %d, got %d", initialCount, len(currentPorts))
182 }
183
184 // Close the listener
185 listener.Close()
186
187 // Wait for port to be removed
188 removed := false
189 for i := 0; i < 50; i++ { // Wait up to 2.5 seconds
190 time.Sleep(50 * time.Millisecond)
191 ports := pm.GetPorts()
192 found := false
193 for _, port := range ports {
194 if port.Port == testPort {
195 found = true
196 break
197 }
198 }
199 if !found {
200 removed = true
201 break
202 }
203 }
204
205 if !removed {
206 t.Errorf("test port %d was not removed after listener closed", testPort)
207 }
208}
209
210// TestPortMonitor_FilterTCPPorts tests the TCP port filtering.
211func TestPortMonitor_FilterTCPPorts(t *testing.T) {
212 ports := []portlist.Port{
213 {Proto: "tcp", Port: 80},
214 {Proto: "udp", Port: 53},
215 {Proto: "tcp", Port: 443},
216 {Proto: "udp", Port: 123},
217 }
218
219 tcpPorts := filterTCPPorts(ports)
220
221 if len(tcpPorts) != 2 {
222 t.Errorf("expected 2 TCP ports, got %d", len(tcpPorts))
223 }
224
225 for _, port := range tcpPorts {
226 if port.Proto != "tcp" {
227 t.Errorf("expected TCP port, got %s", port.Proto)
228 }
229 }
230}
231
232// TestPortMonitor_SortPorts tests the port sorting.
233func TestPortMonitor_SortPorts(t *testing.T) {
234 ports := []portlist.Port{
235 {Proto: "tcp", Port: 443},
236 {Proto: "tcp", Port: 80},
237 {Proto: "tcp", Port: 8080},
238 {Proto: "tcp", Port: 22},
239 }
240
241 sortPorts(ports)
242
243 expected := []uint16{22, 80, 443, 8080}
244 for i, port := range ports {
245 if port.Port != expected[i] {
246 t.Errorf("expected port %d at index %d, got %d", expected[i], i, port.Port)
247 }
248 }
249}
250
251// TestPortMonitor_FindAddedPorts tests finding added ports.
252func TestPortMonitor_FindAddedPorts(t *testing.T) {
253 previous := []portlist.Port{
254 {Proto: "tcp", Port: 80},
255 {Proto: "tcp", Port: 443},
256 }
257
258 current := []portlist.Port{
259 {Proto: "tcp", Port: 80},
260 {Proto: "tcp", Port: 443},
261 {Proto: "tcp", Port: 8080},
262 {Proto: "tcp", Port: 22},
263 }
264
265 added := findAddedPorts(previous, current)
266
267 if len(added) != 2 {
268 t.Errorf("expected 2 added ports, got %d", len(added))
269 }
270
271 addedPorts := make(map[uint16]bool)
272 for _, port := range added {
273 addedPorts[port.Port] = true
274 }
275
276 if !addedPorts[8080] || !addedPorts[22] {
277 t.Errorf("expected ports 8080 and 22 to be added, got %v", added)
278 }
279}
280
281// TestPortMonitor_FindRemovedPorts tests finding removed ports.
282func TestPortMonitor_FindRemovedPorts(t *testing.T) {
283 previous := []portlist.Port{
284 {Proto: "tcp", Port: 80},
285 {Proto: "tcp", Port: 443},
286 {Proto: "tcp", Port: 8080},
287 {Proto: "tcp", Port: 22},
288 }
289
290 current := []portlist.Port{
291 {Proto: "tcp", Port: 80},
292 {Proto: "tcp", Port: 443},
293 }
294
295 removed := findRemovedPorts(previous, current)
296
297 if len(removed) != 2 {
298 t.Errorf("expected 2 removed ports, got %d", len(removed))
299 }
300
301 removedPorts := make(map[uint16]bool)
302 for _, port := range removed {
303 removedPorts[port.Port] = true
304 }
305
306 if !removedPorts[8080] || !removedPorts[22] {
307 t.Errorf("expected ports 8080 and 22 to be removed, got %v", removed)
308 }
309}
310
311// createTestAgent creates a minimal test agent for testing.
312func createTestAgent(t *testing.T) *Agent {
313 // Create a minimal agent for testing
314 // We need to initialize the required fields for the PortMonitor to work
315 agent := &Agent{
316 subscribers: make([]chan *AgentMessage, 0),
317 }
318 return agent
319}