Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
drael committed May 31, 2014
1 parent 20f848c commit b8cd6e6
Show file tree
Hide file tree
Showing 5 changed files with 388 additions and 3 deletions.
32 changes: 32 additions & 0 deletions 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)
}
}
}
23 changes: 23 additions & 0 deletions 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))
}
30 changes: 30 additions & 0 deletions 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)
}
}
43 changes: 40 additions & 3 deletions 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

<b>TCP/UDP</b>
```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!

<b>TCP6/UDP6</b>
```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.
263 changes: 263 additions & 0 deletions 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 <rafael@sourcecode.net.br>
*/

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
}

0 comments on commit b8cd6e6

Please sign in to comment.