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