github.com/gofiber/fiber/v2@v2.47.0/internal/gopsutil/net/net_linux.go (about) 1 //go:build linux 2 // +build linux 3 4 package net 5 6 import ( 7 "bytes" 8 "context" 9 "encoding/hex" 10 "errors" 11 "fmt" 12 "io" 13 "net" 14 "os" 15 "strconv" 16 "strings" 17 "syscall" 18 19 "github.com/gofiber/fiber/v2/internal/gopsutil/common" 20 ) 21 22 const ( // Conntrack Column numbers 23 CT_ENTRIES = iota 24 CT_SEARCHED 25 CT_FOUND 26 CT_NEW 27 CT_INVALID 28 CT_IGNORE 29 CT_DELETE 30 CT_DELETE_LIST 31 CT_INSERT 32 CT_INSERT_FAILED 33 CT_DROP 34 CT_EARLY_DROP 35 CT_ICMP_ERROR 36 CT_EXPECT_NEW 37 CT_EXPECT_CREATE 38 CT_EXPECT_DELETE 39 CT_SEARCH_RESTART 40 ) 41 42 // NetIOCounters returnes network I/O statistics for every network 43 // interface installed on the system. If pernic argument is false, 44 // return only sum of all information (which name is 'all'). If true, 45 // every network interface installed on the system is returned 46 // separately. 47 func IOCounters(pernic bool) ([]IOCountersStat, error) { 48 return IOCountersWithContext(context.Background(), pernic) 49 } 50 51 func IOCountersWithContext(ctx context.Context, pernic bool) ([]IOCountersStat, error) { 52 filename := common.HostProc("net/dev") 53 return IOCountersByFileWithContext(ctx, pernic, filename) 54 } 55 56 func IOCountersByFile(pernic bool, filename string) ([]IOCountersStat, error) { 57 return IOCountersByFileWithContext(context.Background(), pernic, filename) 58 } 59 60 func IOCountersByFileWithContext(ctx context.Context, pernic bool, filename string) ([]IOCountersStat, error) { 61 lines, err := common.ReadLines(filename) 62 if err != nil { 63 return nil, err 64 } 65 66 parts := make([]string, 2) 67 68 statlen := len(lines) - 1 69 70 ret := make([]IOCountersStat, 0, statlen) 71 72 for _, line := range lines[2:] { 73 separatorPos := strings.LastIndex(line, ":") 74 if separatorPos == -1 { 75 continue 76 } 77 parts[0] = line[0:separatorPos] 78 parts[1] = line[separatorPos+1:] 79 80 interfaceName := strings.TrimSpace(parts[0]) 81 if interfaceName == "" { 82 continue 83 } 84 85 fields := strings.Fields(strings.TrimSpace(parts[1])) 86 bytesRecv, err := strconv.ParseUint(fields[0], 10, 64) 87 if err != nil { 88 return ret, err 89 } 90 packetsRecv, err := strconv.ParseUint(fields[1], 10, 64) 91 if err != nil { 92 return ret, err 93 } 94 errIn, err := strconv.ParseUint(fields[2], 10, 64) 95 if err != nil { 96 return ret, err 97 } 98 dropIn, err := strconv.ParseUint(fields[3], 10, 64) 99 if err != nil { 100 return ret, err 101 } 102 fifoIn, err := strconv.ParseUint(fields[4], 10, 64) 103 if err != nil { 104 return ret, err 105 } 106 bytesSent, err := strconv.ParseUint(fields[8], 10, 64) 107 if err != nil { 108 return ret, err 109 } 110 packetsSent, err := strconv.ParseUint(fields[9], 10, 64) 111 if err != nil { 112 return ret, err 113 } 114 errOut, err := strconv.ParseUint(fields[10], 10, 64) 115 if err != nil { 116 return ret, err 117 } 118 dropOut, err := strconv.ParseUint(fields[11], 10, 64) 119 if err != nil { 120 return ret, err 121 } 122 fifoOut, err := strconv.ParseUint(fields[12], 10, 64) 123 if err != nil { 124 return ret, err 125 } 126 127 nic := IOCountersStat{ 128 Name: interfaceName, 129 BytesRecv: bytesRecv, 130 PacketsRecv: packetsRecv, 131 Errin: errIn, 132 Dropin: dropIn, 133 Fifoin: fifoIn, 134 BytesSent: bytesSent, 135 PacketsSent: packetsSent, 136 Errout: errOut, 137 Dropout: dropOut, 138 Fifoout: fifoOut, 139 } 140 ret = append(ret, nic) 141 } 142 143 if !pernic { 144 return getIOCountersAll(ret) 145 } 146 147 return ret, nil 148 } 149 150 var netProtocols = []string{ 151 "ip", 152 "icmp", 153 "icmpmsg", 154 "tcp", 155 "udp", 156 "udplite", 157 } 158 159 // NetProtoCounters returns network statistics for the entire system 160 // If protocols is empty then all protocols are returned, otherwise 161 // just the protocols in the list are returned. 162 // Available protocols: 163 // 164 // ip,icmp,icmpmsg,tcp,udp,udplite 165 func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) { 166 return ProtoCountersWithContext(context.Background(), protocols) 167 } 168 169 func ProtoCountersWithContext(ctx context.Context, protocols []string) ([]ProtoCountersStat, error) { 170 if len(protocols) == 0 { 171 protocols = netProtocols 172 } 173 174 stats := make([]ProtoCountersStat, 0, len(protocols)) 175 protos := make(map[string]bool, len(protocols)) 176 for _, p := range protocols { 177 protos[p] = true 178 } 179 180 filename := common.HostProc("net/snmp") 181 lines, err := common.ReadLines(filename) 182 if err != nil { 183 return nil, err 184 } 185 186 linecount := len(lines) 187 for i := 0; i < linecount; i++ { 188 line := lines[i] 189 r := strings.IndexRune(line, ':') 190 if r == -1 { 191 return nil, errors.New(filename + " is not fomatted correctly, expected ':'.") 192 } 193 proto := strings.ToLower(line[:r]) 194 if !protos[proto] { 195 // skip protocol and data line 196 i++ 197 continue 198 } 199 200 // Read header line 201 statNames := strings.Split(line[r+2:], " ") 202 203 // Read data line 204 i++ 205 statValues := strings.Split(lines[i][r+2:], " ") 206 if len(statNames) != len(statValues) { 207 return nil, errors.New(filename + " is not fomatted correctly, expected same number of columns.") 208 } 209 stat := ProtoCountersStat{ 210 Protocol: proto, 211 Stats: make(map[string]int64, len(statNames)), 212 } 213 for j := range statNames { 214 value, err := strconv.ParseInt(statValues[j], 10, 64) 215 if err != nil { 216 return nil, err 217 } 218 stat.Stats[statNames[j]] = value 219 } 220 stats = append(stats, stat) 221 } 222 return stats, nil 223 } 224 225 // NetFilterCounters returns iptables conntrack statistics 226 // the currently in use conntrack count and the max. 227 // If the file does not exist or is invalid it will return nil. 228 func FilterCounters() ([]FilterStat, error) { 229 return FilterCountersWithContext(context.Background()) 230 } 231 232 func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) { 233 countfile := common.HostProc("sys/net/netfilter/nf_conntrack_count") 234 maxfile := common.HostProc("sys/net/netfilter/nf_conntrack_max") 235 236 count, err := common.ReadInts(countfile) 237 238 if err != nil { 239 return nil, err 240 } 241 stats := make([]FilterStat, 0, 1) 242 243 max, err := common.ReadInts(maxfile) 244 if err != nil { 245 return nil, err 246 } 247 248 payload := FilterStat{ 249 ConnTrackCount: count[0], 250 ConnTrackMax: max[0], 251 } 252 253 stats = append(stats, payload) 254 return stats, nil 255 } 256 257 // ConntrackStats returns more detailed info about the conntrack table 258 func ConntrackStats(percpu bool) ([]ConntrackStat, error) { 259 return ConntrackStatsWithContext(context.Background(), percpu) 260 } 261 262 // ConntrackStatsWithContext returns more detailed info about the conntrack table 263 func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) { 264 return conntrackStatsFromFile(common.HostProc("net/stat/nf_conntrack"), percpu) 265 } 266 267 // conntrackStatsFromFile returns more detailed info about the conntrack table 268 // from `filename` 269 // If 'percpu' is false, the result will contain exactly one item with totals/summary 270 func conntrackStatsFromFile(filename string, percpu bool) ([]ConntrackStat, error) { 271 lines, err := common.ReadLines(filename) 272 if err != nil { 273 return nil, err 274 } 275 276 statlist := NewConntrackStatList() 277 278 for _, line := range lines { 279 fields := strings.Fields(line) 280 if len(fields) == 17 && fields[0] != "entries" { 281 statlist.Append(NewConntrackStat( 282 common.HexToUint32(fields[CT_ENTRIES]), 283 common.HexToUint32(fields[CT_SEARCHED]), 284 common.HexToUint32(fields[CT_FOUND]), 285 common.HexToUint32(fields[CT_NEW]), 286 common.HexToUint32(fields[CT_INVALID]), 287 common.HexToUint32(fields[CT_IGNORE]), 288 common.HexToUint32(fields[CT_DELETE]), 289 common.HexToUint32(fields[CT_DELETE_LIST]), 290 common.HexToUint32(fields[CT_INSERT]), 291 common.HexToUint32(fields[CT_INSERT_FAILED]), 292 common.HexToUint32(fields[CT_DROP]), 293 common.HexToUint32(fields[CT_EARLY_DROP]), 294 common.HexToUint32(fields[CT_ICMP_ERROR]), 295 common.HexToUint32(fields[CT_EXPECT_NEW]), 296 common.HexToUint32(fields[CT_EXPECT_CREATE]), 297 common.HexToUint32(fields[CT_EXPECT_DELETE]), 298 common.HexToUint32(fields[CT_SEARCH_RESTART]), 299 )) 300 } 301 } 302 303 if percpu { 304 return statlist.Items(), nil 305 } 306 return statlist.Summary(), nil 307 } 308 309 // http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h 310 var TCPStatuses = map[string]string{ 311 "01": "ESTABLISHED", 312 "02": "SYN_SENT", 313 "03": "SYN_RECV", 314 "04": "FIN_WAIT1", 315 "05": "FIN_WAIT2", 316 "06": "TIME_WAIT", 317 "07": "CLOSE", 318 "08": "CLOSE_WAIT", 319 "09": "LAST_ACK", 320 "0A": "LISTEN", 321 "0B": "CLOSING", 322 } 323 324 type netConnectionKindType struct { 325 family uint32 326 sockType uint32 327 filename string 328 } 329 330 var kindTCP4 = netConnectionKindType{ 331 family: syscall.AF_INET, 332 sockType: syscall.SOCK_STREAM, 333 filename: "tcp", 334 } 335 var kindTCP6 = netConnectionKindType{ 336 family: syscall.AF_INET6, 337 sockType: syscall.SOCK_STREAM, 338 filename: "tcp6", 339 } 340 var kindUDP4 = netConnectionKindType{ 341 family: syscall.AF_INET, 342 sockType: syscall.SOCK_DGRAM, 343 filename: "udp", 344 } 345 var kindUDP6 = netConnectionKindType{ 346 family: syscall.AF_INET6, 347 sockType: syscall.SOCK_DGRAM, 348 filename: "udp6", 349 } 350 var kindUNIX = netConnectionKindType{ 351 family: syscall.AF_UNIX, 352 filename: "unix", 353 } 354 355 var netConnectionKindMap = map[string][]netConnectionKindType{ 356 "all": {kindTCP4, kindTCP6, kindUDP4, kindUDP6, kindUNIX}, 357 "tcp": {kindTCP4, kindTCP6}, 358 "tcp4": {kindTCP4}, 359 "tcp6": {kindTCP6}, 360 "udp": {kindUDP4, kindUDP6}, 361 "udp4": {kindUDP4}, 362 "udp6": {kindUDP6}, 363 "unix": {kindUNIX}, 364 "inet": {kindTCP4, kindTCP6, kindUDP4, kindUDP6}, 365 "inet4": {kindTCP4, kindUDP4}, 366 "inet6": {kindTCP6, kindUDP6}, 367 } 368 369 type inodeMap struct { 370 pid int32 371 fd uint32 372 } 373 374 type connTmp struct { 375 fd uint32 376 family uint32 377 sockType uint32 378 laddr Addr 379 raddr Addr 380 status string 381 pid int32 382 boundPid int32 383 path string 384 } 385 386 // Return a list of network connections opened. 387 func Connections(kind string) ([]ConnectionStat, error) { 388 return ConnectionsWithContext(context.Background(), kind) 389 } 390 391 func ConnectionsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) { 392 return ConnectionsPid(kind, 0) 393 } 394 395 // Return a list of network connections opened returning at most `max` 396 // connections for each running process. 397 func ConnectionsMax(kind string, max int) ([]ConnectionStat, error) { 398 return ConnectionsMaxWithContext(context.Background(), kind, max) 399 } 400 401 func ConnectionsMaxWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) { 402 return ConnectionsPidMax(kind, 0, max) 403 } 404 405 // Return a list of network connections opened, omitting `Uids`. 406 // WithoutUids functions are reliant on implementation details. They may be altered to be an alias for Connections or be 407 // removed from the API in the future. 408 func ConnectionsWithoutUids(kind string) ([]ConnectionStat, error) { 409 return ConnectionsWithoutUidsWithContext(context.Background(), kind) 410 } 411 412 func ConnectionsWithoutUidsWithContext(ctx context.Context, kind string) ([]ConnectionStat, error) { 413 return ConnectionsMaxWithoutUidsWithContext(ctx, kind, 0) 414 } 415 416 func ConnectionsMaxWithoutUidsWithContext(ctx context.Context, kind string, max int) ([]ConnectionStat, error) { 417 return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, 0, max) 418 } 419 420 // Return a list of network connections opened by a process. 421 func ConnectionsPid(kind string, pid int32) ([]ConnectionStat, error) { 422 return ConnectionsPidWithContext(context.Background(), kind, pid) 423 } 424 425 func ConnectionsPidWithoutUids(kind string, pid int32) ([]ConnectionStat, error) { 426 return ConnectionsPidWithoutUidsWithContext(context.Background(), kind, pid) 427 } 428 429 func ConnectionsPidWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) { 430 return ConnectionsPidMaxWithContext(ctx, kind, pid, 0) 431 } 432 433 func ConnectionsPidWithoutUidsWithContext(ctx context.Context, kind string, pid int32) ([]ConnectionStat, error) { 434 return ConnectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, 0) 435 } 436 437 // Return up to `max` network connections opened by a process. 438 func ConnectionsPidMax(kind string, pid int32, max int) ([]ConnectionStat, error) { 439 return ConnectionsPidMaxWithContext(context.Background(), kind, pid, max) 440 } 441 442 func ConnectionsPidMaxWithoutUids(kind string, pid int32, max int) ([]ConnectionStat, error) { 443 return ConnectionsPidMaxWithoutUidsWithContext(context.Background(), kind, pid, max) 444 } 445 446 func ConnectionsPidMaxWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) { 447 return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max, false) 448 } 449 450 func ConnectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int) ([]ConnectionStat, error) { 451 return connectionsPidMaxWithoutUidsWithContext(ctx, kind, pid, max, true) 452 } 453 454 func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, pid int32, max int, skipUids bool) ([]ConnectionStat, error) { 455 tmap, ok := netConnectionKindMap[kind] 456 if !ok { 457 return nil, fmt.Errorf("invalid kind, %s", kind) 458 } 459 root := common.HostProc() 460 var err error 461 var inodes map[string][]inodeMap 462 if pid == 0 { 463 inodes, err = getProcInodesAll(root, max) 464 } else { 465 inodes, err = getProcInodes(root, pid, max) 466 if len(inodes) == 0 { 467 // no connection for the pid 468 return []ConnectionStat{}, nil 469 } 470 } 471 if err != nil { 472 return nil, fmt.Errorf("cound not get pid(s), %d: %s", pid, err) 473 } 474 return statsFromInodes(root, pid, tmap, inodes, skipUids) 475 } 476 477 func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap, skipUids bool) ([]ConnectionStat, error) { 478 dupCheckMap := make(map[string]struct{}) 479 var ret []ConnectionStat 480 481 var err error 482 for _, t := range tmap { 483 var path string 484 var connKey string 485 var ls []connTmp 486 if pid == 0 { 487 path = fmt.Sprintf("%s/net/%s", root, t.filename) 488 } else { 489 path = fmt.Sprintf("%s/%d/net/%s", root, pid, t.filename) 490 } 491 switch t.family { 492 case syscall.AF_INET, syscall.AF_INET6: 493 ls, err = processInet(path, t, inodes, pid) 494 case syscall.AF_UNIX: 495 ls, err = processUnix(path, t, inodes, pid) 496 } 497 if err != nil { 498 return nil, err 499 } 500 for _, c := range ls { 501 // Build TCP key to id the connection uniquely 502 // socket type, src ip, src port, dst ip, dst port and state should be enough 503 // to prevent duplications. 504 connKey = fmt.Sprintf("%d-%s:%d-%s:%d-%s", c.sockType, c.laddr.IP, c.laddr.Port, c.raddr.IP, c.raddr.Port, c.status) 505 if _, ok := dupCheckMap[connKey]; ok { 506 continue 507 } 508 509 conn := ConnectionStat{ 510 Fd: c.fd, 511 Family: c.family, 512 Type: c.sockType, 513 Laddr: c.laddr, 514 Raddr: c.raddr, 515 Status: c.status, 516 Pid: c.pid, 517 } 518 if c.pid == 0 { 519 conn.Pid = c.boundPid 520 } else { 521 conn.Pid = c.pid 522 } 523 524 if !skipUids { 525 // fetch process owner Real, effective, saved set, and filesystem UIDs 526 proc := process{Pid: conn.Pid} 527 conn.Uids, _ = proc.getUids() 528 } 529 530 ret = append(ret, conn) 531 dupCheckMap[connKey] = struct{}{} 532 } 533 534 } 535 536 return ret, nil 537 } 538 539 // getProcInodes returnes fd of the pid. 540 func getProcInodes(root string, pid int32, max int) (map[string][]inodeMap, error) { 541 ret := make(map[string][]inodeMap) 542 543 dir := fmt.Sprintf("%s/%d/fd", root, pid) 544 f, err := os.Open(dir) 545 if err != nil { 546 return ret, err 547 } 548 defer f.Close() 549 files, err := f.Readdir(max) 550 if err != nil { 551 return ret, err 552 } 553 for _, fd := range files { 554 inodePath := fmt.Sprintf("%s/%d/fd/%s", root, pid, fd.Name()) 555 556 inode, err := os.Readlink(inodePath) 557 if err != nil { 558 continue 559 } 560 if !strings.HasPrefix(inode, "socket:[") { 561 continue 562 } 563 // the process is using a socket 564 l := len(inode) 565 inode = inode[8 : l-1] 566 _, ok := ret[inode] 567 if !ok { 568 ret[inode] = make([]inodeMap, 0) 569 } 570 fd, err := strconv.Atoi(fd.Name()) 571 if err != nil { 572 continue 573 } 574 575 i := inodeMap{ 576 pid: pid, 577 fd: uint32(fd), 578 } 579 ret[inode] = append(ret[inode], i) 580 } 581 return ret, nil 582 } 583 584 // Pids retunres all pids. 585 // Note: this is a copy of process_linux.Pids() 586 // FIXME: Import process occures import cycle. 587 // move to common made other platform breaking. Need consider. 588 func Pids() ([]int32, error) { 589 return PidsWithContext(context.Background()) 590 } 591 592 func PidsWithContext(ctx context.Context) ([]int32, error) { 593 var ret []int32 594 595 d, err := os.Open(common.HostProc()) 596 if err != nil { 597 return nil, err 598 } 599 defer d.Close() 600 601 fnames, err := d.Readdirnames(-1) 602 if err != nil { 603 return nil, err 604 } 605 for _, fname := range fnames { 606 pid, err := strconv.ParseInt(fname, 10, 32) 607 if err != nil { 608 // if not numeric name, just skip 609 continue 610 } 611 ret = append(ret, int32(pid)) 612 } 613 614 return ret, nil 615 } 616 617 // Note: the following is based off process_linux structs and methods 618 // we need these to fetch the owner of a process ID 619 // FIXME: Import process occures import cycle. 620 // see remarks on pids() 621 type process struct { 622 Pid int32 `json:"pid"` 623 uids []int32 624 } 625 626 // Uids returns user ids of the process as a slice of the int 627 func (p *process) getUids() ([]int32, error) { 628 err := p.fillFromStatus() 629 if err != nil { 630 return []int32{}, err 631 } 632 return p.uids, nil 633 } 634 635 // Get status from /proc/(pid)/status 636 func (p *process) fillFromStatus() error { 637 pid := p.Pid 638 statPath := common.HostProc(strconv.Itoa(int(pid)), "status") 639 contents, err := os.ReadFile(statPath) 640 if err != nil { 641 return err 642 } 643 lines := strings.Split(string(contents), "\n") 644 for _, line := range lines { 645 tabParts := strings.SplitN(line, "\t", 2) 646 if len(tabParts) < 2 { 647 continue 648 } 649 value := tabParts[1] 650 switch strings.TrimRight(tabParts[0], ":") { 651 case "Uid": 652 p.uids = make([]int32, 0, 4) 653 for _, i := range strings.Split(value, "\t") { 654 v, err := strconv.ParseInt(i, 10, 32) 655 if err != nil { 656 return err 657 } 658 p.uids = append(p.uids, int32(v)) 659 } 660 } 661 } 662 return nil 663 } 664 665 func getProcInodesAll(root string, max int) (map[string][]inodeMap, error) { 666 pids, err := Pids() 667 if err != nil { 668 return nil, err 669 } 670 ret := make(map[string][]inodeMap) 671 672 for _, pid := range pids { 673 t, err := getProcInodes(root, pid, max) 674 if err != nil { 675 // skip if permission error or no longer exists 676 if os.IsPermission(err) || os.IsNotExist(err) || err == io.EOF { 677 continue 678 } 679 return ret, err 680 } 681 if len(t) == 0 { 682 continue 683 } 684 // TODO: update ret. 685 ret = updateMap(ret, t) 686 } 687 return ret, nil 688 } 689 690 // decodeAddress decode addresse represents addr in proc/net/* 691 // ex: 692 // "0500000A:0016" -> "10.0.0.5", 22 693 // "0085002452100113070057A13F025401:0035" -> "2400:8500:1301:1052:a157:7:154:23f", 53 694 func decodeAddress(family uint32, src string) (Addr, error) { 695 t := strings.Split(src, ":") 696 if len(t) != 2 { 697 return Addr{}, fmt.Errorf("does not contain port, %s", src) 698 } 699 addr := t[0] 700 port, err := strconv.ParseUint(t[1], 16, 16) 701 if err != nil { 702 return Addr{}, fmt.Errorf("invalid port, %s", src) 703 } 704 decoded, err := hex.DecodeString(addr) 705 if err != nil { 706 return Addr{}, fmt.Errorf("decode error, %s", err) 707 } 708 var ip net.IP 709 // Assumes this is little_endian 710 if family == syscall.AF_INET { 711 ip = net.IP(Reverse(decoded)) 712 } else { // IPv6 713 ip, err = parseIPv6HexString(decoded) 714 if err != nil { 715 return Addr{}, err 716 } 717 } 718 return Addr{ 719 IP: ip.String(), 720 Port: uint32(port), 721 }, nil 722 } 723 724 // Reverse reverses array of bytes. 725 func Reverse(s []byte) []byte { 726 return ReverseWithContext(context.Background(), s) 727 } 728 729 func ReverseWithContext(ctx context.Context, s []byte) []byte { 730 for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { 731 s[i], s[j] = s[j], s[i] 732 } 733 return s 734 } 735 736 // parseIPv6HexString parse array of bytes to IPv6 string 737 func parseIPv6HexString(src []byte) (net.IP, error) { 738 if len(src) != 16 { 739 return nil, fmt.Errorf("invalid IPv6 string") 740 } 741 742 buf := make([]byte, 0, 16) 743 for i := 0; i < len(src); i += 4 { 744 r := Reverse(src[i : i+4]) 745 buf = append(buf, r...) 746 } 747 return net.IP(buf), nil 748 } 749 750 func processInet(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) { 751 752 if strings.HasSuffix(file, "6") && !common.PathExists(file) { 753 // IPv6 not supported, return empty. 754 return []connTmp{}, nil 755 } 756 757 // Read the contents of the /proc file with a single read sys call. 758 // This minimizes duplicates in the returned connections 759 // For more info: 760 // https://github.com/shirou/gopsutil/pull/361 761 contents, err := os.ReadFile(file) 762 if err != nil { 763 return nil, err 764 } 765 766 lines := bytes.Split(contents, []byte("\n")) 767 768 var ret []connTmp 769 // skip first line 770 for _, line := range lines[1:] { 771 l := strings.Fields(string(line)) 772 if len(l) < 10 { 773 continue 774 } 775 laddr := l[1] 776 raddr := l[2] 777 status := l[3] 778 inode := l[9] 779 pid := int32(0) 780 fd := uint32(0) 781 i, exists := inodes[inode] 782 if exists { 783 pid = i[0].pid 784 fd = i[0].fd 785 } 786 if filterPid > 0 && filterPid != pid { 787 continue 788 } 789 if kind.sockType == syscall.SOCK_STREAM { 790 status = TCPStatuses[status] 791 } else { 792 status = "NONE" 793 } 794 la, err := decodeAddress(kind.family, laddr) 795 if err != nil { 796 continue 797 } 798 ra, err := decodeAddress(kind.family, raddr) 799 if err != nil { 800 continue 801 } 802 803 ret = append(ret, connTmp{ 804 fd: fd, 805 family: kind.family, 806 sockType: kind.sockType, 807 laddr: la, 808 raddr: ra, 809 status: status, 810 pid: pid, 811 }) 812 } 813 814 return ret, nil 815 } 816 817 func processUnix(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) { 818 // Read the contents of the /proc file with a single read sys call. 819 // This minimizes duplicates in the returned connections 820 // For more info: 821 // https://github.com/shirou/gopsutil/pull/361 822 contents, err := os.ReadFile(file) 823 if err != nil { 824 return nil, err 825 } 826 827 lines := bytes.Split(contents, []byte("\n")) 828 829 var ret []connTmp 830 // skip first line 831 for _, line := range lines[1:] { 832 tokens := strings.Fields(string(line)) 833 if len(tokens) < 6 { 834 continue 835 } 836 st, err := strconv.Atoi(tokens[4]) 837 if err != nil { 838 return nil, err 839 } 840 841 inode := tokens[6] 842 843 var pairs []inodeMap 844 pairs, exists := inodes[inode] 845 if !exists { 846 pairs = []inodeMap{ 847 {}, 848 } 849 } 850 for _, pair := range pairs { 851 if filterPid > 0 && filterPid != pair.pid { 852 continue 853 } 854 var path string 855 if len(tokens) == 8 { 856 path = tokens[len(tokens)-1] 857 } 858 ret = append(ret, connTmp{ 859 fd: pair.fd, 860 family: kind.family, 861 sockType: uint32(st), 862 laddr: Addr{ 863 IP: path, 864 }, 865 pid: pair.pid, 866 status: "NONE", 867 path: path, 868 }) 869 } 870 } 871 872 return ret, nil 873 } 874 875 func updateMap(src map[string][]inodeMap, add map[string][]inodeMap) map[string][]inodeMap { 876 for key, value := range add { 877 a, exists := src[key] 878 if !exists { 879 src[key] = value 880 continue 881 } 882 src[key] = append(a, value...) 883 } 884 return src 885 }