@@ -5,6 +5,7 @@ package net
55
66import (
77 "context"
8+ "errors"
89 "fmt"
910 "regexp"
1011 "strconv"
@@ -295,6 +296,290 @@ func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, p
295296 return connectionsPidMaxWithoutUidsWithContext (ctx , kind , pid , maxConn , true )
296297}
297298
298- func connectionsPidMaxWithoutUidsWithContext (_ context.Context , _ string , _ int32 , _ int , _ bool ) ([]ConnectionStat , error ) {
299- return []ConnectionStat {}, common .ErrNotImplementedError
299+ func connectionsPidMaxWithoutUidsWithContext (ctx context.Context , _ string , pid int32 , maxConn int , _ bool ) ([]ConnectionStat , error ) {
300+ // If pid is 0, return all connections
301+ if pid == 0 {
302+ return getAIXConnections (ctx , maxConn )
303+ }
304+
305+ // For specific PID, filter connections to just that process
306+ return getAIXConnectionsForPid (ctx , pid , maxConn )
307+ }
308+
309+ // getAIXConnections retrieves all network connections from AIX
310+ func getAIXConnections (ctx context.Context , maxConn int ) ([]ConnectionStat , error ) {
311+ var conns []ConnectionStat
312+
313+ // Get all listening sockets using netstat
314+ output , err := invoke .CommandWithContext (ctx , "netstat" , "-Aan" )
315+ if err != nil {
316+ return conns , err
317+ }
318+
319+ lines := strings .Split (string (output ), "\n " )
320+ count := 0
321+
322+ for _ , line := range lines {
323+ if maxConn > 0 && count >= maxConn {
324+ break
325+ }
326+
327+ line = strings .TrimSpace (line )
328+ if line == "" || strings .HasPrefix (line , "Address" ) {
329+ continue
330+ }
331+
332+ // Parse netstat output to find sockets
333+ // Format: Address Family Type Use Recv-Q Send-Q Inode Conn Routes
334+ fields := strings .Fields (line )
335+ if len (fields ) < 2 {
336+ continue
337+ }
338+
339+ sockAddr := fields [0 ]
340+
341+ // Try to resolve the socket to get connection info using rmsock
342+ // For TCP connections
343+ connStat , err := resolveAIXSockToConnection (ctx , sockAddr , "tcp" )
344+ if err == nil && connStat != nil {
345+ conns = append (conns , * connStat )
346+ count ++
347+ continue
348+ }
349+
350+ // Try for UDP connections
351+ connStat , err = resolveAIXSockToConnection (ctx , sockAddr , "udp" )
352+ if err == nil && connStat != nil {
353+ conns = append (conns , * connStat )
354+ count ++
355+ }
356+ }
357+
358+ return conns , nil
359+ }
360+
361+ // getAIXConnectionsForPid retrieves network connections for a specific process on AIX
362+ func getAIXConnectionsForPid (ctx context.Context , pid int32 , maxConn int ) ([]ConnectionStat , error ) {
363+ var conns []ConnectionStat
364+
365+ // Get all listening sockets using netstat
366+ output , err := invoke .CommandWithContext (ctx , "netstat" , "-Aan" )
367+ if err != nil {
368+ return conns , err
369+ }
370+
371+ lines := strings .Split (string (output ), "\n " )
372+ count := 0
373+
374+ for _ , line := range lines {
375+ if maxConn > 0 && count >= maxConn {
376+ break
377+ }
378+
379+ line = strings .TrimSpace (line )
380+ if line == "" || strings .HasPrefix (line , "Address" ) || strings .HasPrefix (line , "PCB/ADDR" ) {
381+ continue
382+ }
383+
384+ // Parse netstat output: PCB/ADDR Proto Recv-Q Send-Q Local Address Foreign Address (state)
385+ // Example: f1000f00055cc3c0 tcp4 0 0 192.168.242.122.22 24.236.207.124.40326 ESTABLISHED
386+ fields := strings .Fields (line )
387+ if len (fields ) < 7 {
388+ continue
389+ }
390+
391+ sockAddr := fields [0 ]
392+ proto := fields [1 ]
393+ localAddr := fields [4 ]
394+ remoteAddr := fields [5 ]
395+ state := fields [6 ]
396+
397+ // Determine protocol type (tcp or udp)
398+ var protocol string
399+ switch {
400+ case strings .HasPrefix (proto , "tcp" ):
401+ protocol = "tcp"
402+ case strings .HasPrefix (proto , "udp" ):
403+ protocol = "udp"
404+ default :
405+ continue
406+ }
407+
408+ // Try to resolve the socket to get PID using rmsock
409+ resolvedPid := resolveAIXSockToPid (ctx , sockAddr , protocol )
410+ if resolvedPid != pid {
411+ // This connection doesn't belong to our target PID
412+ continue
413+ }
414+
415+ // Parse addresses
416+ laddr := parseAIXAddress (localAddr )
417+ raddr := parseAIXAddress (remoteAddr )
418+
419+ // Determine socket type and family
420+ var socketType uint32
421+ var socketFamily uint32
422+
423+ // Set socket type based on protocol
424+ if protocol == "tcp" {
425+ socketType = syscall .SOCK_STREAM
426+ } else {
427+ socketType = syscall .SOCK_DGRAM
428+ }
429+
430+ // Set socket family based on proto string (tcp4, tcp6, udp4, udp6)
431+ if strings .HasSuffix (proto , "6" ) {
432+ socketFamily = syscall .AF_INET6
433+ } else {
434+ socketFamily = syscall .AF_INET
435+ }
436+
437+ connStat := ConnectionStat {
438+ Fd : 0 ,
439+ Family : socketFamily ,
440+ Type : socketType ,
441+ Laddr : laddr ,
442+ Raddr : raddr ,
443+ Status : state ,
444+ Pid : pid ,
445+ }
446+
447+ conns = append (conns , connStat )
448+ count ++
449+ }
450+
451+ return conns , nil
452+ }
453+
454+ // resolveAIXSockToConnection uses AIX rmsock command to resolve a socket address to connection info
455+ func resolveAIXSockToConnection (ctx context.Context , sockAddr , protocol string ) (* ConnectionStat , error ) {
456+ if protocol != "tcp" && protocol != "udp" {
457+ return nil , fmt .Errorf ("unsupported protocol: %s" , protocol )
458+ }
459+
460+ // Execute rmsock to resolve socket
461+ // Format for TCP: rmsock <socket_address> tcpcb
462+ // Format for UDP: rmsock <socket_address> inpcb
463+ var tcpOrUDP string
464+ if protocol == "tcp" {
465+ tcpOrUDP = "tcpcb"
466+ } else {
467+ tcpOrUDP = "inpcb"
468+ }
469+
470+ output , err := invoke .CommandWithContext (ctx , "rmsock" , sockAddr , tcpOrUDP )
471+ if err != nil {
472+ return nil , err
473+ }
474+
475+ // Parse rmsock output to extract connection info
476+ outputStr := string (output )
477+
478+ // Try to find PID in the output
479+ pid := parseAIXRmsockPid (outputStr )
480+ if pid == 0 {
481+ return nil , errors .New ("could not extract PID from rmsock output" )
482+ }
483+
484+ // Build connection stat from parsed info
485+ connStat := & ConnectionStat {
486+ Fd : 0 ,
487+ Family : 0 ,
488+ Type : 0 ,
489+ Laddr : Addr {IP : "" , Port : 0 },
490+ Raddr : Addr {IP : "" , Port : 0 },
491+ Status : "" ,
492+ Pid : pid ,
493+ }
494+
495+ return connStat , nil
496+ }
497+
498+ // resolveAIXSockToConnectionForPid resolves socket to connection only if it matches the target PID
499+ func resolveAIXSockToConnectionForPid (ctx context.Context , sockAddr , protocol string , targetPid int32 ) (* ConnectionStat , error ) {
500+ connStat , err := resolveAIXSockToConnection (ctx , sockAddr , protocol )
501+ if err != nil {
502+ return nil , err
503+ }
504+
505+ if connStat == nil {
506+ return nil , errors .New ("connection stat is nil" )
507+ }
508+
509+ if connStat .Pid != targetPid {
510+ return nil , fmt .Errorf ("PID mismatch: expected %d, got %d" , targetPid , connStat .Pid )
511+ }
512+
513+ return connStat , nil
514+ }
515+
516+ // parseAIXRmsockPid extracts PID from rmsock output
517+ // Expected format: "The socket 0xf1000f00055be808 is being held by process 14287304 (sshd)."
518+ func parseAIXRmsockPid (output string ) int32 {
519+ // Use regex to extract PID from rmsock output
520+ // Pattern: "process <PID> ("
521+ re := regexp .MustCompile (`process\s+(\d+)\s+\(` )
522+ matches := re .FindStringSubmatch (output )
523+ if len (matches ) > 1 {
524+ if pid , err := strconv .ParseInt (matches [1 ], 10 , 32 ); err == nil {
525+ return int32 (pid )
526+ }
527+ }
528+ return 0
529+ }
530+
531+ // resolveAIXSockToPid uses rmsock to get the PID holding a socket, returns 0 if unable to resolve
532+ func resolveAIXSockToPid (ctx context.Context , sockAddr , protocol string ) int32 {
533+ if protocol != "tcp" && protocol != "udp" {
534+ return 0
535+ }
536+
537+ var tcpOrUDP string
538+ if protocol == "tcp" {
539+ tcpOrUDP = "tcpcb"
540+ } else {
541+ tcpOrUDP = "inpcb"
542+ }
543+
544+ output , err := invoke .CommandWithContext (ctx , "rmsock" , sockAddr , tcpOrUDP )
545+ // Note: rmsock may exit with status 1 even on successful resolution
546+ // So we try to parse the output regardless of error status
547+
548+ outputStr := string (output )
549+ pid := parseAIXRmsockPid (outputStr )
550+
551+ if pid == 0 && err != nil {
552+ // If we got a "Wait for exiting processes" message, it's a transient cleanup situation - skip silently
553+ if strings .Contains (outputStr , "Wait for exiting processes" ) {
554+ return 0
555+ }
556+ // For other errors, log debug info if we couldn't parse a PID
557+ // Uncomment for debugging: fmt.Fprintf(os.Stderr, "DEBUG: rmsock %s %s failed: %v, output: %s\n", sockAddr, tcpOrUdp, err, outputStr)
558+ }
559+
560+ return pid
561+ }
562+
563+ // parseAIXAddress parses an AIX address string like "192.168.242.122.22" or "24.236.207.124.40326"
564+ // Format: IP_OCTETS separated by dots, with port as last octet(s) after the IP
565+ func parseAIXAddress (addrStr string ) Addr {
566+ if addrStr == "*.*" {
567+ return Addr {IP : "" , Port : 0 }
568+ }
569+
570+ parts := strings .Split (addrStr , "." )
571+ if len (parts ) < 2 {
572+ return Addr {IP : "" , Port : 0 }
573+ }
574+
575+ // Last part is the port
576+ port := 0
577+ if p , err := strconv .Atoi (parts [len (parts )- 1 ]); err == nil {
578+ port = p
579+ }
580+
581+ // Join all but last part as IP
582+ ip := strings .Join (parts [:len (parts )- 1 ], "." )
583+
584+ return Addr {IP : ip , Port : uint32 (port )}
300585}
0 commit comments