├── 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 | --------------------------------------------------------------------------------