Skip to content

Commit e1aaf4c

Browse files
authored
Merge pull request #9 from yuuki/fast_proc_net_tcp
Improve performance by removing the inode and pid process
2 parents c7dcaf0 + acbfa1a commit e1aaf4c

File tree

6 files changed

+235
-77
lines changed

6 files changed

+235
-77
lines changed

netutil/netutil.go

Lines changed: 0 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package netutil
22

33
import (
4-
"fmt"
54
"net"
65
"strings"
7-
8-
gnet "github.com/shirou/gopsutil/net"
96
)
107

118
// ResolveAddr lookup first hostname from IP Address.
@@ -33,41 +30,3 @@ func LocalIPAddrs() ([]string, error) {
3330
}
3431
return addrStrings, nil
3532
}
36-
37-
// FilterByLocalListeningPorts filters ConnectionStat slice by the local listening ports.
38-
// eg. [199, 111, 46131, 53, 8953, 25, 2812, 80, 8081, 22]
39-
// -----------------------------------------------------------------------------------
40-
// [y_uuki@host ~]$ netstat -tln
41-
// Active Internet connections (only servers)
42-
// Proto Recv-Q Send-Q Local Address Foreign Address State
43-
// tcp 0 0 0.0.0.0:199 0.0.0.0:* LISTEN
44-
// tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
45-
// tcp 0 0 0.0.0.0:46131 0.0.0.0:* LISTEN
46-
// tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN
47-
// tcp 0 0 127.0.0.1:8953 0.0.0.0:* LISTEN
48-
// tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
49-
// tcp 0 0 0.0.0.0:2812 0.0.0.0:* LISTEN
50-
// tcp 0 0 :::80 :::* LISTEN
51-
// tcp 0 0 :::8081 :::* LISTEN
52-
// tcp 0 0 :::22 :::* LISTEN
53-
func FilterByLocalListeningPorts(conns []gnet.ConnectionStat) ([]string, error) {
54-
ports := []string{}
55-
for _, conn := range conns {
56-
if conn.Status != "LISTEN" {
57-
continue
58-
}
59-
if conn.Laddr.IP == "0.0.0.0" || conn.Laddr.IP == "127.0.0.1" || conn.Laddr.IP == "::" {
60-
ports = append(ports, fmt.Sprintf("%d", conn.Laddr.Port))
61-
}
62-
}
63-
return ports, nil
64-
}
65-
66-
// LocalListeningPorts returns the local listening ports.
67-
func LocalListeningPorts() ([]string, error) {
68-
conns, err := gnet.Connections("tcp")
69-
if err != nil {
70-
return nil, err
71-
}
72-
return FilterByLocalListeningPorts(conns)
73-
}

netutil/netutil_linux.go

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,18 @@
33
package netutil
44

55
import (
6+
"bytes"
7+
"encoding/hex"
68
"fmt"
9+
"io/ioutil"
10+
"log"
11+
"net"
12+
"strconv"
13+
"strings"
714

815
"github.com/elastic/gosigar/sys/linux"
16+
17+
gnet "github.com/shirou/gopsutil/net"
918
)
1019

1120
// NetlinkConnections returns connection stats.
@@ -48,3 +57,111 @@ func NetlinkLocalListeningPorts() ([]string, error) {
4857
}
4958
return ports, nil
5059
}
60+
61+
const (
62+
tcpProcFilename = "/proc/net/tcp"
63+
)
64+
65+
// Addr is <addr>:<port>.
66+
type Addr struct {
67+
IP string `json:"ip"`
68+
Port uint32 `json:"port"`
69+
}
70+
71+
// ConnectionStat represents staticstics for a connection.
72+
type ConnectionStat struct {
73+
Laddr Addr
74+
Raddr Addr
75+
Status linux.TCPState
76+
}
77+
78+
// ProcfsConnections returns connection stats.
79+
// ref. https://github.com/shirou/gopsutil/blob/c23bcca55e77b8389d84b09db8c5ac2b472070ef/net/net_linux.go#L656
80+
func ProcfsConnections() ([]*ConnectionStat, error) {
81+
body, err := ioutil.ReadFile(tcpProcFilename)
82+
if err != nil {
83+
return nil, err
84+
}
85+
86+
lines := bytes.Split(body, []byte("\n"))
87+
conns := make([]*ConnectionStat, 0, len(lines)-1)
88+
for _, line := range lines[1:] {
89+
l := strings.Fields(string(line))
90+
if len(l) < 10 {
91+
continue
92+
}
93+
laddr := l[1]
94+
raddr := l[2]
95+
status, err := strconv.ParseUint(l[3], 16, 8)
96+
if err != nil {
97+
log.Printf("decode error: %v", err)
98+
}
99+
la, err := decodeAddress(laddr)
100+
if err != nil {
101+
continue
102+
}
103+
ra, err := decodeAddress(raddr)
104+
if err != nil {
105+
continue
106+
}
107+
108+
conns = append(conns, &ConnectionStat{
109+
Laddr: la,
110+
Raddr: ra,
111+
Status: linux.TCPState(status),
112+
})
113+
}
114+
115+
return conns, nil
116+
}
117+
118+
// decodeAddress decode addresse represents addr in proc/net/*
119+
// ex:
120+
// "0500000A:0016" -> "10.0.0.5", 22
121+
// "0085002452100113070057A13F025401:0035" -> "2400:8500:1301:1052:a157:7:154:23f", 53
122+
// ref. https://github.com/shirou/gopsutil/blob/c23bcca55e77b8389d84b09db8c5ac2b472070ef/net/net_linux.go#L600
123+
func decodeAddress(src string) (Addr, error) {
124+
t := strings.Split(src, ":")
125+
if len(t) != 2 {
126+
return Addr{}, fmt.Errorf("does not contain port, %s", src)
127+
}
128+
addr := t[0]
129+
port, err := strconv.ParseInt("0x"+t[1], 0, 64)
130+
if err != nil {
131+
return Addr{}, fmt.Errorf("invalid port, %s", src)
132+
}
133+
decoded, err := hex.DecodeString(addr)
134+
if err != nil {
135+
return Addr{}, fmt.Errorf("decode error, %s", err)
136+
}
137+
var ip net.IP
138+
// Assumes this is little_endian
139+
ip = net.IP(gnet.Reverse(decoded))
140+
return Addr{
141+
IP: ip.String(),
142+
Port: uint32(port),
143+
}, nil
144+
}
145+
146+
// FilterByLocalListeningPorts filters ConnectionStat slice by the local listening ports.
147+
func FilterByLocalListeningPorts(conns []*ConnectionStat) ([]string, error) {
148+
ports := []string{}
149+
for _, conn := range conns {
150+
if conn.Status != linux.TCP_LISTEN {
151+
continue
152+
}
153+
if conn.Laddr.IP == "0.0.0.0" || conn.Laddr.IP == "127.0.0.1" || conn.Laddr.IP == "::" {
154+
ports = append(ports, fmt.Sprintf("%d", conn.Laddr.Port))
155+
}
156+
}
157+
return ports, nil
158+
}
159+
160+
// LocalListeningPorts returns the local listening ports.
161+
func LocalListeningPorts() ([]string, error) {
162+
conns, err := ProcfsConnections()
163+
if err != nil {
164+
return nil, err
165+
}
166+
return FilterByLocalListeningPorts(conns)
167+
}

netutil/netutil_unix.go

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// +build freebsd,darwin
2+
3+
package netutil
4+
5+
import (
6+
"fmt"
7+
8+
gnet "github.com/shirou/gopsutil/net"
9+
)
10+
11+
// FilterByLocalListeningPorts filters ConnectionStat slice by the local listening ports.
12+
// eg. [199, 111, 46131, 53, 8953, 25, 2812, 80, 8081, 22]
13+
// -----------------------------------------------------------------------------------
14+
// [y_uuki@host ~]$ netstat -tln
15+
// Active Internet connections (only servers)
16+
// Proto Recv-Q Send-Q Local Address Foreign Address State
17+
// tcp 0 0 0.0.0.0:199 0.0.0.0:* LISTEN
18+
// tcp 0 0 0.0.0.0:111 0.0.0.0:* LISTEN
19+
// tcp 0 0 0.0.0.0:46131 0.0.0.0:* LISTEN
20+
// tcp 0 0 127.0.0.1:53 0.0.0.0:* LISTEN
21+
// tcp 0 0 127.0.0.1:8953 0.0.0.0:* LISTEN
22+
// tcp 0 0 127.0.0.1:25 0.0.0.0:* LISTEN
23+
// tcp 0 0 0.0.0.0:2812 0.0.0.0:* LISTEN
24+
// tcp 0 0 :::80 :::* LISTEN
25+
// tcp 0 0 :::8081 :::* LISTEN
26+
// tcp 0 0 :::22 :::* LISTEN
27+
func FilterByLocalListeningPorts(conns []gnet.ConnectionStat) ([]string, error) {
28+
ports := []string{}
29+
for _, conn := range conns {
30+
if conn.Status != "LISTEN" {
31+
continue
32+
}
33+
if conn.Laddr.IP == "0.0.0.0" || conn.Laddr.IP == "127.0.0.1" || conn.Laddr.IP == "::" {
34+
ports = append(ports, fmt.Sprintf("%d", conn.Laddr.Port))
35+
}
36+
}
37+
return ports, nil
38+
}
39+
40+
// LocalListeningPorts returns the local listening ports.
41+
func LocalListeningPorts() ([]string, error) {
42+
conns, err := gnet.Connections("tcp")
43+
if err != nil {
44+
return nil, err
45+
}
46+
return FilterByLocalListeningPorts(conns)
47+
}

tcpflow/tcpflow.go

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"net"
77
"strconv"
88

9-
gnet "github.com/shirou/gopsutil/net"
109
"github.com/yuuki/lstf/netutil"
1110
)
1211

@@ -110,40 +109,6 @@ func (hf HostFlows) insert(flow *HostFlow) {
110109
return
111110
}
112111

113-
// GetHostFlowsByProcfs get host flows by procfs.
114-
func GetHostFlowsByProcfs() (HostFlows, error) {
115-
conns, err := gnet.Connections("tcp")
116-
if err != nil {
117-
return nil, err
118-
}
119-
ports, err := netutil.FilterByLocalListeningPorts(conns)
120-
if err != nil {
121-
return nil, err
122-
}
123-
flows := HostFlows{}
124-
for _, conn := range conns {
125-
if conn.Status == "LISTEN" {
126-
continue
127-
}
128-
lport := fmt.Sprintf("%d", conn.Laddr.Port)
129-
rport := fmt.Sprintf("%d", conn.Raddr.Port)
130-
if contains(ports, lport) {
131-
flows.insert(&HostFlow{
132-
Direction: FlowPassive,
133-
Local: &AddrPort{Addr: conn.Laddr.IP, Port: lport},
134-
Peer: &AddrPort{Addr: conn.Raddr.IP, Port: "many"},
135-
})
136-
} else {
137-
flows.insert(&HostFlow{
138-
Direction: FlowActive,
139-
Local: &AddrPort{Addr: conn.Laddr.IP, Port: "many"},
140-
Peer: &AddrPort{Addr: conn.Raddr.IP, Port: rport},
141-
})
142-
}
143-
}
144-
return flows, nil
145-
}
146-
147112
func contains(strs []string, s string) bool {
148113
for _, str := range strs {
149114
if str == s {

tcpflow/tcpflow_linux.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,37 @@ func GetHostFlowsByNetlink() (HostFlows, error) {
5252
}
5353
return flows, nil
5454
}
55+
56+
// GetHostFlowsByProcfs gets host flows from procfs.
57+
func GetHostFlowsByProcfs() (HostFlows, error) {
58+
conns, err := netutil.ProcfsConnections()
59+
if err != nil {
60+
return nil, err
61+
}
62+
ports, err := netutil.FilterByLocalListeningPorts(conns)
63+
if err != nil {
64+
return nil, err
65+
}
66+
flows := HostFlows{}
67+
for _, conn := range conns {
68+
if conn.Status == linux.TCP_LISTEN {
69+
continue
70+
}
71+
lport := fmt.Sprintf("%d", conn.Laddr.Port)
72+
rport := fmt.Sprintf("%d", conn.Raddr.Port)
73+
if contains(ports, lport) {
74+
flows.insert(&HostFlow{
75+
Direction: FlowPassive,
76+
Local: &AddrPort{Addr: conn.Laddr.IP, Port: lport},
77+
Peer: &AddrPort{Addr: conn.Raddr.IP, Port: "many"},
78+
})
79+
} else {
80+
flows.insert(&HostFlow{
81+
Direction: FlowActive,
82+
Local: &AddrPort{Addr: conn.Laddr.IP, Port: "many"},
83+
Peer: &AddrPort{Addr: conn.Raddr.IP, Port: rport},
84+
})
85+
}
86+
}
87+
return flows, nil
88+
}

tcpflow/tcpflow_unix.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,43 @@
22

33
package tcpflow
44

5+
import (
6+
"fmt"
7+
8+
gnet "github.com/shirou/gopsutil/net"
9+
"github.com/yuuki/lstf/netutil"
10+
)
11+
512
// GetHostFlows gets host flows.
613
func GetHostFlows() (HostFlows, error) {
7-
return GetHostFlowsByProcfs()
14+
conns, err := gnet.Connections("tcp")
15+
if err != nil {
16+
return nil, err
17+
}
18+
ports, err := netutil.FilterByLocalListeningPorts(conns)
19+
if err != nil {
20+
return nil, err
21+
}
22+
flows := HostFlows{}
23+
for _, conn := range conns {
24+
if conn.Status == "LISTEN" {
25+
continue
26+
}
27+
lport := fmt.Sprintf("%d", conn.Laddr.Port)
28+
rport := fmt.Sprintf("%d", conn.Raddr.Port)
29+
if contains(ports, lport) {
30+
flows.insert(&HostFlow{
31+
Direction: FlowPassive,
32+
Local: &AddrPort{Addr: conn.Laddr.IP, Port: lport},
33+
Peer: &AddrPort{Addr: conn.Raddr.IP, Port: "many"},
34+
})
35+
} else {
36+
flows.insert(&HostFlow{
37+
Direction: FlowActive,
38+
Local: &AddrPort{Addr: conn.Laddr.IP, Port: "many"},
39+
Peer: &AddrPort{Addr: conn.Raddr.IP, Port: rport},
40+
})
41+
}
42+
}
43+
return flows, nil
844
}

0 commit comments

Comments
 (0)