diff --git a/Examples/tcp.go b/Examples/tcp.go new file mode 100644 index 0000000..23680dd --- /dev/null +++ b/Examples/tcp.go @@ -0,0 +1,32 @@ +package main + +import ( + "fmt" + "github.com/drael/GOnetstat" +) + +/* Get TCP information and show like netstat. + Information like 'user' and 'name' of some processes will not show if you + don't have root permissions */ + +func main() { + d := GOnetstat.Tcp() + + // format header + fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress", + "State", "Pid/Program") + + for _, p := range(d) { + + // Check STATE to show only Listening connections + if p.State == "LISTEN" { + // format data like netstat output + ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port) + fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort) + pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name) + + fmt.Printf("tcp %16v %20v %16v %20v\n", ip_port, fip_port, + p.State, pid_program) + } + } +} \ No newline at end of file diff --git a/Examples/tcp_json.go b/Examples/tcp_json.go new file mode 100644 index 0000000..7e147ba --- /dev/null +++ b/Examples/tcp_json.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "encoding/json" + "github.com/drael/GOnetstat" +) + +/* Get TCP information and output in json. + Information like 'user' and 'name' of some processes will not show if you + don't have root permissions */ + +func main () { + d := GOnetstat.Tcp() + + // Marshal in prety print way + output, err := json.MarshalIndent(d, "", " ") + if err != nil { + fmt.Println(err) + } + + fmt.Println(string(output)) +} \ No newline at end of file diff --git a/Examples/udp.go b/Examples/udp.go new file mode 100644 index 0000000..49a5361 --- /dev/null +++ b/Examples/udp.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "github.com/drael/GOnetstat" +) + +/* Get Udp information and show like netstat. + Information like 'user' and 'name' of some processes will not show if you + don't have root permissions */ + + +func main() { + // Get Udp data, you can use GOnetstat.Tcp() to get TCP data + d := GOnetstat.Udp() + + // format header + fmt.Printf("Proto %16s %20s %14s %24s\n", "Local Adress", "Foregin Adress", + "State", "Pid/Program") + + for _, p := range(d) { + // format data like netstat output + ip_port := fmt.Sprintf("%v:%v", p.Ip, p.Port) + fip_port := fmt.Sprintf("%v:%v", p.ForeignIp, p.ForeignPort) + pid_program := fmt.Sprintf("%v/%v", p.Pid, p.Name) + + fmt.Printf("udp %16v %20v %16v %20v\n", ip_port, fip_port, + p.State, pid_program) + } +} \ No newline at end of file diff --git a/README.md b/README.md index 5b8c8d0..8bc25f4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,41 @@ -GOnetstat -========= +# GOnetstat -Netstat implementation in Go +Netstat implementation in Golang. + +This Package get data from /proc/net/tcp|6 and /proc/net/udp|6 and parse +/proc/[0-9]*/fd/[0-9]* to match the correct inode. + +## Usage + +TCP/UDP +```go +tcp_data := GOnetstat.Tcp() +udp_data := GOnetstat.Udp() +``` + +This will return a array of a Process struct like this + +```go +type Process struct { + User string + Name string + Pid string + Exe string + State string + Ip string + Port int64 + ForeignIp string + ForeignPort int64 +} +``` +So you can loop through data output and format the output of your program +in whatever way you want it. +See the Examples folder! + +TCP6/UDP6 +```go +tcp6_data := GOnetstat.Tcp6() +udp6_data := GOnetstat.Udp6() +``` +The return will be a array of a Process struct like mentioned above. +Still need to create a way to compress the ipv6 because is too long. diff --git a/gonetstat.go b/gonetstat.go new file mode 100644 index 0000000..f6e03ae --- /dev/null +++ b/gonetstat.go @@ -0,0 +1,263 @@ +/* + Simple Netstat implementation. + Get data from /proc/net/tcp and /proc/net/udp and + and parse /proc/[0-9]/fd/[0-9]. + + Author: Rafael Santos +*/ + +package GOnetstat + +import ( + "fmt" + "io/ioutil" + "strings" + "os" + "os/user" + "strconv" + "path/filepath" + "regexp" +) + + +const ( + PROC_TCP = "/proc/net/tcp" + PROC_UDP = "/proc/net/udp" + PROC_TCP6 = "/proc/net/tcp6" + PROC_UDP6 = "/proc/net/udp6" + +) + +var STATE = map[string]string { + "01": "ESTABLISHED", + "02": "SYN_SENT", + "03": "SYN_RECV", + "04": "FIN_WAIT1", + "05": "FIN_WAIT2", + "06": "TIME_WAIT", + "07": "CLOSE", + "08": "CLOSE_WAIT", + "09": "LAST_ACK", + "0A": "LISTEN", + "0B": "CLOSING", +} + + +type Process struct { + User string + Name string + Pid string + Exe string + State string + Ip string + Port int64 + ForeignIp string + ForeignPort int64 +} + + +func getData(t string) []string { + // Get data from tcp or udp file. + + var proc_t string + + if t == "tcp" { + proc_t = PROC_TCP + } else if t == "udp" { + proc_t = PROC_UDP + } else if t == "tcp6" { + proc_t = PROC_TCP6 + } else if t == "udp6" { + proc_t = PROC_UDP6 + } else { + fmt.Printf("%s is a invalid type, tcp and udp only!\n", t) + os.Exit(1) + } + + + data, err := ioutil.ReadFile(proc_t) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + lines := strings.Split(string(data), "\n") + + // Return lines without Header line and blank line on the end + return lines[1:len(lines) - 1] + +} + + +func hexToDec(h string) int64 { + // convert hexadecimal to decimal. + d, err := strconv.ParseInt(h, 16, 32) + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + return d +} + + +func convertIp(ip string) string { + // Convert the ipv4 to decimal. Have to rearrange the ip because the + // default value is in little Endian order. + + var out string + + // Check ip size if greater than 8 is a ipv6 type + if len(ip) > 8 { + i := []string{ ip[30:32], + ip[28:30], + ip[26:28], + ip[24:26], + ip[22:24], + ip[20:22], + ip[18:20], + ip[16:18], + ip[14:16], + ip[12:14], + ip[10:12], + ip[8:10], + ip[6:8], + ip[4:6], + ip[2:4], + ip[0:2]} + out = fmt.Sprintf("%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v:%v%v", + i[14], i[15], i[13], i[12], + i[10], i[11], i[8], i[9], + i[6], i[7], i[4], i[5], + i[2], i[3], i[0], i[1]) + + } else { + i := []int64{ hexToDec(ip[6:8]), + hexToDec(ip[4:6]), + hexToDec(ip[2:4]), + hexToDec(ip[0:2]) } + + out = fmt.Sprintf("%v.%v.%v.%v", i[0], i[1], i[2], i[3]) + } + return out +} + + +func findPid(inode string) string { + // Loop through all fd dirs of process on /proc to compare the inode and + // get the pid. + + pid := "-" + + d, err := filepath.Glob("/proc/[0-9]*/fd/[0-9]*") + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + re := regexp.MustCompile(inode) + for _, item := range(d) { + path, _ := os.Readlink(item) + out := re.FindString(path) + if len(out) != 0 { + pid = strings.Split(item, "/")[2] + } + } + return pid +} + + +func getProcessExe(pid string) string { + exe := fmt.Sprintf("/proc/%s/exe", pid) + path, _ := os.Readlink(exe) + return path +} + + +func getProcessName(exe string) string { + n := strings.Split(exe, "/") + name := n[len(n) -1] + return strings.Title(name) +} + + +func getUser(uid string) string { + u, _ := user.LookupId(uid) + return u.Username +} + + +func removeEmpty(array []string) []string { + // remove empty data from line + var new_array [] string + for _, i := range(array) { + if i != "" { + new_array = append(new_array, i) + } + } + return new_array +} + + +func netstat(t string) []Process { + // Return a array of Process with Name, Ip, Port, State .. etc + // Require Root acess to get information about some processes. + + var Processes []Process + + data := getData(t) + + for _, line := range(data) { + + // local ip and port + line_array := removeEmpty(strings.Split(strings.TrimSpace(line), " ")) + ip_port := strings.Split(line_array[1], ":") + ip := convertIp(ip_port[0]) + port := hexToDec(ip_port[1]) + + // foreign ip and port + fip_port := strings.Split(line_array[2], ":") + fip := convertIp(fip_port[0]) + fport := hexToDec(fip_port[1]) + + state := STATE[line_array[3]] + uid := getUser(line_array[7]) + pid := findPid(line_array[9]) + exe := getProcessExe(pid) + name := getProcessName(exe) + + p := Process{uid, name, pid, exe, state, ip, port, fip, fport} + + Processes = append(Processes, p) + + } + + return Processes +} + + +func Tcp() []Process { + // Get a slice of Process type with TCP data + data := netstat("tcp") + return data +} + + +func Udp() []Process { + // Get a slice of Process type with UDP data + data := netstat("udp") + return data +} + + +func Tcp6() []Process { + // Get a slice of Process type with TCP6 data + data := netstat("tcp6") + return data +} + + +func Udp6() []Process { + // Get a slice of Process type with UDP6 data + data := netstat("udp6") + return data +}