├── Examples
├── tcp.go
├── tcp_json.go
└── udp.go
├── LICENSE.md
├── README.md
└── gonetstat.go
/Examples/tcp.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/drael/GOnetstat"
6 | )
7 |
8 | /* Get TCP information and show like netstat.
9 | Information like 'user' and 'name' of some processes will not show if you
10 | don't have root permissions */
11 |
12 | func main() {
13 | d := GOnetstat.Tcp()
14 |
15 | // format header
16 | fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress",
17 | "State", "Pid/Program")
18 |
19 | for _, p := range(d) {
20 |
21 | // Check STATE to show only Listening connections
22 | if p.State == "LISTEN" {
23 | // format data like netstat output
24 | ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port)
25 | fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort)
26 | pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name)
27 |
28 | fmt.Printf("tcp %16v %20v %16v %20v\n", ip_port, fip_port,
29 | p.State, pid_program)
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/Examples/tcp_json.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "encoding/json"
6 | "github.com/drael/GOnetstat"
7 | )
8 |
9 | /* Get TCP information and output in json.
10 | Information like 'user' and 'name' of some processes will not show if you
11 | don't have root permissions */
12 |
13 | func main () {
14 | d := GOnetstat.Tcp()
15 |
16 | // Marshal in prety print way
17 | output, err := json.MarshalIndent(d, "", " ")
18 | if err != nil {
19 | fmt.Println(err)
20 | }
21 |
22 | fmt.Println(string(output))
23 | }
--------------------------------------------------------------------------------
/Examples/udp.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "github.com/drael/GOnetstat"
6 | )
7 |
8 | /* Get Udp information and show like netstat.
9 | Information like 'user' and 'name' of some processes will not show if you
10 | don't have root permissions */
11 |
12 |
13 | func main() {
14 | // Get Udp data, you can use GOnetstat.Tcp() to get TCP data
15 | d := GOnetstat.Udp()
16 |
17 | // format header
18 | fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress",
19 | "State", "Pid/Program")
20 |
21 | for _, p := range(d) {
22 | // format data like netstat output
23 | ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port)
24 | fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort)
25 | pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name)
26 |
27 | fmt.Printf("udp %16v %20v %16v %20v\n", ip_port, fip_port,
28 | p.State, pid_program)
29 | }
30 | }
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Rafael Santos
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GOnetstat
2 |
3 | Netstat implementation in Golang.
4 |
5 | This Package get data from /proc/net/tcp|6 and /proc/net/udp|6 and parse
6 | /proc/[0-9]*/fd/[0-9]* to match the correct inode.
7 |
8 | ## Usage
9 |
10 | TCP/UDP
11 | ```go
12 | tcp_data := GOnetstat.Tcp()
13 | udp_data := GOnetstat.Udp()
14 | ```
15 |
16 | This will return a array of a Process struct like this
17 |
18 | ```go
19 | type Process struct {
20 | User string
21 | Name string
22 | Pid string
23 | Exe string
24 | State string
25 | Ip string
26 | Port int64
27 | ForeignIp string
28 | ForeignPort int64
29 | }
30 | ```
31 | So you can loop through data output and format the output of your program
32 | in whatever way you want it.
33 | See the Examples folder!
34 |
35 | TCP6/UDP6
36 | ```go
37 | tcp6_data := GOnetstat.Tcp6()
38 | udp6_data := GOnetstat.Udp6()
39 | ```
40 | The return will be a array of a Process struct like mentioned above.
41 | Still need to create a way to compress the ipv6 because is too long.
42 |
--------------------------------------------------------------------------------
/gonetstat.go:
--------------------------------------------------------------------------------
1 | /*
2 | Simple Netstat implementation.
3 | Get data from /proc/net/tcp and /proc/net/udp and
4 | and parse /proc/[0-9]/fd/[0-9].
5 |
6 | Author: Rafael Santos
7 | */
8 |
9 | package GOnetstat
10 |
11 | import (
12 | "fmt"
13 | "io/ioutil"
14 | "os"
15 | "os/user"
16 | "path/filepath"
17 | "regexp"
18 | "strconv"
19 | "strings"
20 | )
21 |
22 |
23 | const (
24 | PROC_TCP = "/proc/net/tcp"
25 | PROC_UDP = "/proc/net/udp"
26 | PROC_TCP6 = "/proc/net/tcp6"
27 | PROC_UDP6 = "/proc/net/udp6"
28 |
29 | )
30 |
31 | var STATE = map[string]string {
32 | "01": "ESTABLISHED",
33 | "02": "SYN_SENT",
34 | "03": "SYN_RECV",
35 | "04": "FIN_WAIT1",
36 | "05": "FIN_WAIT2",
37 | "06": "TIME_WAIT",
38 | "07": "CLOSE",
39 | "08": "CLOSE_WAIT",
40 | "09": "LAST_ACK",
41 | "0A": "LISTEN",
42 | "0B": "CLOSING",
43 | }
44 |
45 |
46 | type Process struct {
47 | User string
48 | Name string
49 | Pid string
50 | Exe string
51 | State string
52 | Ip string
53 | Port int64
54 | ForeignIp string
55 | ForeignPort int64
56 | }
57 |
58 | type iNode struct {
59 | path string
60 | link string
61 | }
62 |
63 | func getData(t string) []string {
64 | // Get data from tcp or udp file.
65 |
66 | var proc_t string
67 |
68 | if t == "tcp" {
69 | proc_t = PROC_TCP
70 | } else if t == "udp" {
71 | proc_t = PROC_UDP
72 | } else if t == "tcp6" {
73 | proc_t = PROC_TCP6
74 | } else if t == "udp6" {
75 | proc_t = PROC_UDP6
76 | } else {
77 | fmt.Printf("%s is a invalid type, tcp and udp only!\n", t)
78 | os.Exit(1)
79 | }
80 |
81 |
82 | data, err := ioutil.ReadFile(proc_t)
83 | if err != nil {
84 | fmt.Println(err)
85 | os.Exit(1)
86 | }
87 | lines := strings.Split(string(data), "\n")
88 |
89 | // Return lines without Header line and blank line on the end
90 | return lines[1:len(lines) - 1]
91 |
92 | }
93 |
94 |
95 | func hexToDec(h string) int64 {
96 | // convert hexadecimal to decimal.
97 | d, err := strconv.ParseInt(h, 16, 32)
98 | if err != nil {
99 | fmt.Println(err)
100 | os.Exit(1)
101 | }
102 |
103 | return d
104 | }
105 |
106 |
107 | func convertIp(ip string) string {
108 | // Convert the ipv4 to decimal. Have to rearrange the ip because the
109 | // default value is in little Endian order.
110 |
111 | var out string
112 |
113 | // Check ip size if greater than 8 is a ipv6 type
114 | if len(ip) > 8 {
115 | i := []string{ ip[30:32],
116 | ip[28:30],
117 | ip[26:28],
118 | ip[24:26],
119 | ip[22:24],
120 | ip[20:22],
121 | ip[18:20],
122 | ip[16:18],
123 | ip[14:16],
124 | ip[12:14],
125 | ip[10:12],
126 | ip[8:10],
127 | ip[6:8],
128 | ip[4:6],
129 | ip[2:4],
130 | ip[0:2]}
131 | out = fmt.Sprintf("%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v",
132 | i[14], i[15], i[13], i[12],
133 | i[10], i[11], i[8], i[9],
134 | i[6], i[7], i[4], i[5],
135 | i[2], i[3], i[0], i[1])
136 |
137 | } else {
138 | i := []int64{ hexToDec(ip[6:8]),
139 | hexToDec(ip[4:6]),
140 | hexToDec(ip[2:4]),
141 | hexToDec(ip[0:2]) }
142 |
143 | out = fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[3])
144 | }
145 | return out
146 | }
147 |
148 |
149 | func findPid(inode string, inodes *[]iNode) string {
150 | // Loop through all fd dirs of process on /proc to compare the inode and
151 | // get the pid.
152 |
153 | pid := "-"
154 |
155 | re := regexp.MustCompile(inode)
156 | for _, item := range *inodes {
157 | out := re.FindString(item.link)
158 | if len(out) != 0 {
159 | pid = strings.Split(item.path, "/")[2]
160 | }
161 | }
162 | return pid
163 | }
164 |
165 |
166 | func getProcessExe(pid string) string {
167 | exe := fmt.Sprintf("/proc/%s/exe", pid)
168 | path, _ := os.Readlink(exe)
169 | return path
170 | }
171 |
172 |
173 | func getProcessName(exe string) string {
174 | n := strings.Split(exe, "/")
175 | name := n[len(n) -1]
176 | return strings.Title(name)
177 | }
178 |
179 |
180 | func getUser(uid string) string {
181 | u, err := user.LookupId(uid)
182 | if err != nil {
183 | return "Unknown"
184 | }
185 | return u.Username
186 | }
187 |
188 |
189 | func removeEmpty(array []string) []string {
190 | // remove empty data from line
191 | var new_array [] string
192 | for _, i := range(array) {
193 | if i != "" {
194 | new_array = append(new_array, i)
195 | }
196 | }
197 | return new_array
198 | }
199 |
200 | func processNetstatLine(line string, fileDescriptors *[]iNode, output chan<- Process) {
201 | line_array := removeEmpty(strings.Split(strings.TrimSpace(line), " "))
202 | ip_port := strings.Split(line_array[1], ":")
203 | ip := convertIp(ip_port[0])
204 | port := hexToDec(ip_port[1])
205 |
206 | // foreign ip and port
207 | fip_port := strings.Split(line_array[2], ":")
208 | fip := convertIp(fip_port[0])
209 | fport := hexToDec(fip_port[1])
210 |
211 | state := STATE[line_array[3]]
212 | uid := getUser(line_array[7])
213 | pid := findPid(line_array[9], fileDescriptors)
214 | exe := getProcessExe(pid)
215 | name := getProcessName(exe)
216 | output <- Process{uid, name, pid, exe, state, ip, port, fip, fport}
217 | }
218 |
219 | func getFileDescriptors() []string {
220 | d, err := filepath.Glob("/proc/[0-9]*/fd/[0-9]*")
221 | if err != nil {
222 | fmt.Println(err)
223 | os.Exit(1)
224 | }
225 | return d
226 | }
227 |
228 | func getInodes() []iNode {
229 | fileDescriptors := getFileDescriptors()
230 | inodes := make([]iNode, len(fileDescriptors))
231 | res := make(chan iNode, len(fileDescriptors))
232 |
233 | go func(fileDescriptors *[]string, output chan<-iNode) {
234 | for _, item := range *fileDescriptors {
235 | link, _ := os.Readlink(item)
236 | output <- iNode{item, link}
237 | }
238 | }(&fileDescriptors, res)
239 |
240 | for _, _ = range fileDescriptors {
241 | inode := <- res
242 | inodes = append(inodes, inode)
243 | }
244 | return inodes
245 | }
246 |
247 |
248 | func netstat(t string) []Process {
249 | // Return a array of Process with Name, Ip, Port, State .. etc
250 | // Require Root acess to get information about some processes.
251 |
252 |
253 | data := getData(t)
254 | Processes := make([]Process, len(data))
255 | res := make(chan Process, len(data))
256 |
257 | inodes := getInodes()
258 |
259 |
260 | for _, line := range data {
261 | go processNetstatLine(line, &inodes, res)
262 | }
263 |
264 | for i, _ := range data {
265 | p := <- res
266 | Processes[i] = p
267 | }
268 |
269 | return Processes
270 | }
271 |
272 |
273 | func Tcp() []Process {
274 | // Get a slice of Process type with TCP data
275 | data := netstat("tcp")
276 | return data
277 | }
278 |
279 |
280 | func Udp() []Process {
281 | // Get a slice of Process type with UDP data
282 | data := netstat("udp")
283 | return data
284 | }
285 |
286 |
287 | func Tcp6() []Process {
288 | // Get a slice of Process type with TCP6 data
289 | data := netstat("tcp6")
290 | return data
291 | }
292 |
293 |
294 | func Udp6() []Process {
295 | // Get a slice of Process type with UDP6 data
296 | data := netstat("udp6")
297 | return data
298 | }
299 |
--------------------------------------------------------------------------------