├── LICENSE ├── README.md ├── go.mod ├── main.go └── netstat ├── netstat.go ├── netstat_linux.go └── netstat_windows.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Cihangir Akturk 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 | ### Usage: 2 | 3 | ``` 4 | Usage of ./go-netstat: 5 | -4 display only IPv4 sockets 6 | -6 display only IPv6 sockets 7 | -all 8 | display both listening and non-listening sockets 9 | -help 10 | display this help screen 11 | -lis 12 | display only listening sockets 13 | -res 14 | lookup symbolic names for host addresses 15 | -tcp 16 | display TCP sockets 17 | -udp 18 | display UDP sockets 19 | ``` 20 | ### Installation: 21 | 22 | ``` 23 | $ go get github.com/cakturk/go-netstat 24 | ``` 25 | 26 | ### Using as a library 27 | #### [Godoc](https://godoc.org/github.com/cakturk/go-netstat/netstat) 28 | #### Getting the package 29 | ``` 30 | $ go get github.com/cakturk/go-netstat/netstat 31 | ``` 32 | 33 | ```go 34 | import ( 35 | "fmt" 36 | 37 | "github.com/cakturk/go-netstat/netstat" 38 | ) 39 | 40 | func displaySocks() error { 41 | // UDP sockets 42 | socks, err := netstat.UDPSocks(netstat.NoopFilter) 43 | if err != nil { 44 | return err 45 | } 46 | for _, e := range socks { 47 | fmt.Printf("%v\n", e) 48 | } 49 | 50 | // TCP sockets 51 | socks, err = netstat.TCPSocks(netstat.NoopFilter) 52 | if err != nil { 53 | return err 54 | } 55 | for _, e := range socks { 56 | fmt.Printf("%v\n", e) 57 | } 58 | 59 | // get only listening TCP sockets 60 | tabs, err := netstat.TCPSocks(func(s *netstat.SockTabEntry) bool { 61 | return s.State == netstat.Listen 62 | }) 63 | if err != nil { 64 | return err 65 | } 66 | for _, e := range tabs { 67 | fmt.Printf("%v\n", e) 68 | } 69 | 70 | // list all the TCP sockets in state FIN_WAIT_1 for your HTTP server 71 | tabs, err = netstat.TCPSocks(func(s *netstat.SockTabEntry) bool { 72 | return s.State == netstat.FinWait1 && s.LocalAddr.Port == 80 73 | }) 74 | // error handling, etc. 75 | 76 | return nil 77 | } 78 | ``` 79 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cakturk/go-netstat 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "net" 7 | "os" 8 | 9 | "github.com/cakturk/go-netstat/netstat" 10 | ) 11 | 12 | var ( 13 | udp = flag.Bool("udp", false, "display UDP sockets") 14 | tcp = flag.Bool("tcp", false, "display TCP sockets") 15 | listening = flag.Bool("lis", false, "display only listening sockets") 16 | all = flag.Bool("all", false, "display both listening and non-listening sockets") 17 | resolve = flag.Bool("res", false, "lookup symbolic names for host addresses") 18 | ipv4 = flag.Bool("4", false, "display only IPv4 sockets") 19 | ipv6 = flag.Bool("6", false, "display only IPv6 sockets") 20 | help = flag.Bool("help", false, "display this help screen") 21 | ) 22 | 23 | const ( 24 | protoIPv4 = 0x01 25 | protoIPv6 = 0x02 26 | ) 27 | 28 | func main() { 29 | flag.Parse() 30 | 31 | if *help { 32 | flag.Usage() 33 | os.Exit(0) 34 | } 35 | 36 | var proto uint 37 | if *ipv4 { 38 | proto |= protoIPv4 39 | } 40 | if *ipv6 { 41 | proto |= protoIPv6 42 | } 43 | if proto == 0x00 { 44 | proto = protoIPv4 | protoIPv6 45 | } 46 | 47 | if os.Geteuid() != 0 { 48 | fmt.Println("Not all processes could be identified, you would have to be root to see it all.") 49 | } 50 | fmt.Printf("Proto %-23s %-23s %-12s %-16s\n", "Local Addr", "Foreign Addr", "State", "PID/Program name") 51 | 52 | if *udp { 53 | if proto&protoIPv4 == protoIPv4 { 54 | tabs, err := netstat.UDPSocks(netstat.NoopFilter) 55 | if err == nil { 56 | displaySockInfo("udp", tabs) 57 | } 58 | } 59 | if proto&protoIPv6 == protoIPv6 { 60 | tabs, err := netstat.UDP6Socks(netstat.NoopFilter) 61 | if err == nil { 62 | displaySockInfo("udp6", tabs) 63 | } 64 | } 65 | } else { 66 | *tcp = true 67 | } 68 | 69 | if *tcp { 70 | var fn netstat.AcceptFn 71 | 72 | switch { 73 | case *all: 74 | fn = func(*netstat.SockTabEntry) bool { return true } 75 | case *listening: 76 | fn = func(s *netstat.SockTabEntry) bool { 77 | return s.State == netstat.Listen 78 | } 79 | default: 80 | fn = func(s *netstat.SockTabEntry) bool { 81 | return s.State != netstat.Listen 82 | } 83 | } 84 | 85 | if proto&protoIPv4 == protoIPv4 { 86 | tabs, err := netstat.TCPSocks(fn) 87 | if err == nil { 88 | displaySockInfo("tcp", tabs) 89 | } 90 | } 91 | if proto&protoIPv6 == protoIPv6 { 92 | tabs, err := netstat.TCP6Socks(fn) 93 | if err == nil { 94 | displaySockInfo("tcp6", tabs) 95 | } 96 | } 97 | } 98 | } 99 | 100 | func displaySockInfo(proto string, s []netstat.SockTabEntry) { 101 | lookup := func(skaddr *netstat.SockAddr) string { 102 | const IPv4Strlen = 17 103 | addr := skaddr.IP.String() 104 | if *resolve { 105 | names, err := net.LookupAddr(addr) 106 | if err == nil && len(names) > 0 { 107 | addr = names[0] 108 | } 109 | } 110 | if len(addr) > IPv4Strlen { 111 | addr = addr[:IPv4Strlen] 112 | } 113 | return fmt.Sprintf("%s:%d", addr, skaddr.Port) 114 | } 115 | 116 | for _, e := range s { 117 | p := "" 118 | if e.Process != nil { 119 | p = e.Process.String() 120 | } 121 | saddr := lookup(e.LocalAddr) 122 | daddr := lookup(e.RemoteAddr) 123 | fmt.Printf("%-5s %-23.23s %-23.23s %-12s %-16s\n", proto, saddr, daddr, e.State, p) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /netstat/netstat.go: -------------------------------------------------------------------------------- 1 | package netstat 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | ) 7 | 8 | // SockAddr represents an ip:port pair 9 | type SockAddr struct { 10 | IP net.IP 11 | Port uint16 12 | } 13 | 14 | func (s *SockAddr) String() string { 15 | return fmt.Sprintf("%v:%d", s.IP, s.Port) 16 | } 17 | 18 | // SockTabEntry type represents each line of the /proc/net/[tcp|udp] 19 | type SockTabEntry struct { 20 | ino string 21 | LocalAddr *SockAddr 22 | RemoteAddr *SockAddr 23 | State SkState 24 | UID uint32 25 | Process *Process 26 | } 27 | 28 | // Process holds the PID and process name to which each socket belongs 29 | type Process struct { 30 | Pid int 31 | Name string 32 | } 33 | 34 | func (p *Process) String() string { 35 | return fmt.Sprintf("%d/%s", p.Pid, p.Name) 36 | } 37 | 38 | // SkState type represents socket connection state 39 | type SkState uint8 40 | 41 | func (s SkState) String() string { 42 | return skStates[s] 43 | } 44 | 45 | // AcceptFn is used to filter socket entries. The value returned indicates 46 | // whether the element is to be appended to the socket list. 47 | type AcceptFn func(*SockTabEntry) bool 48 | 49 | // NoopFilter - a test function returning true for all elements 50 | func NoopFilter(*SockTabEntry) bool { return true } 51 | 52 | // TCPSocks returns a slice of active TCP sockets containing only those 53 | // elements that satisfy the accept function 54 | func TCPSocks(accept AcceptFn) ([]SockTabEntry, error) { 55 | return osTCPSocks(accept) 56 | } 57 | 58 | // TCP6Socks returns a slice of active TCP IPv4 sockets containing only those 59 | // elements that satisfy the accept function 60 | func TCP6Socks(accept AcceptFn) ([]SockTabEntry, error) { 61 | return osTCP6Socks(accept) 62 | } 63 | 64 | // UDPSocks returns a slice of active UDP sockets containing only those 65 | // elements that satisfy the accept function 66 | func UDPSocks(accept AcceptFn) ([]SockTabEntry, error) { 67 | return osUDPSocks(accept) 68 | } 69 | 70 | // UDP6Socks returns a slice of active UDP IPv6 sockets containing only those 71 | // elements that satisfy the accept function 72 | func UDP6Socks(accept AcceptFn) ([]SockTabEntry, error) { 73 | return osUDP6Socks(accept) 74 | } 75 | -------------------------------------------------------------------------------- /netstat/netstat_linux.go: -------------------------------------------------------------------------------- 1 | // Package netstat provides primitives for getting socket information on a 2 | // Linux based operating system. 3 | package netstat 4 | 5 | import ( 6 | "bufio" 7 | "bytes" 8 | "encoding/binary" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | "net" 14 | "os" 15 | "path" 16 | "strconv" 17 | "strings" 18 | ) 19 | 20 | const ( 21 | pathTCPTab = "/proc/net/tcp" 22 | pathTCP6Tab = "/proc/net/tcp6" 23 | pathUDPTab = "/proc/net/udp" 24 | pathUDP6Tab = "/proc/net/udp6" 25 | 26 | ipv4StrLen = 8 27 | ipv6StrLen = 32 28 | ) 29 | 30 | // Socket states 31 | const ( 32 | Established SkState = 0x01 33 | SynSent = 0x02 34 | SynRecv = 0x03 35 | FinWait1 = 0x04 36 | FinWait2 = 0x05 37 | TimeWait = 0x06 38 | Close = 0x07 39 | CloseWait = 0x08 40 | LastAck = 0x09 41 | Listen = 0x0a 42 | Closing = 0x0b 43 | ) 44 | 45 | var skStates = [...]string{ 46 | "UNKNOWN", 47 | "ESTABLISHED", 48 | "SYN_SENT", 49 | "SYN_RECV", 50 | "FIN_WAIT1", 51 | "FIN_WAIT2", 52 | "TIME_WAIT", 53 | "", // CLOSE 54 | "CLOSE_WAIT", 55 | "LAST_ACK", 56 | "LISTEN", 57 | "CLOSING", 58 | } 59 | 60 | // Errors returned by gonetstat 61 | var ( 62 | ErrNotEnoughFields = errors.New("gonetstat: not enough fields in the line") 63 | ) 64 | 65 | func parseIPv4(s string) (net.IP, error) { 66 | v, err := strconv.ParseUint(s, 16, 32) 67 | if err != nil { 68 | return nil, err 69 | } 70 | ip := make(net.IP, net.IPv4len) 71 | binary.LittleEndian.PutUint32(ip, uint32(v)) 72 | return ip, nil 73 | } 74 | 75 | func parseIPv6(s string) (net.IP, error) { 76 | ip := make(net.IP, net.IPv6len) 77 | const grpLen = 4 78 | i, j := 0, 4 79 | for len(s) != 0 { 80 | grp := s[0:8] 81 | u, err := strconv.ParseUint(grp, 16, 32) 82 | binary.LittleEndian.PutUint32(ip[i:j], uint32(u)) 83 | if err != nil { 84 | return nil, err 85 | } 86 | i, j = i+grpLen, j+grpLen 87 | s = s[8:] 88 | } 89 | return ip, nil 90 | } 91 | 92 | func parseAddr(s string) (*SockAddr, error) { 93 | fields := strings.Split(s, ":") 94 | if len(fields) < 2 { 95 | return nil, fmt.Errorf("netstat: not enough fields: %v", s) 96 | } 97 | var ip net.IP 98 | var err error 99 | switch len(fields[0]) { 100 | case ipv4StrLen: 101 | ip, err = parseIPv4(fields[0]) 102 | case ipv6StrLen: 103 | ip, err = parseIPv6(fields[0]) 104 | default: 105 | err = fmt.Errorf("netstat: bad formatted string: %v", fields[0]) 106 | } 107 | if err != nil { 108 | return nil, err 109 | } 110 | v, err := strconv.ParseUint(fields[1], 16, 16) 111 | if err != nil { 112 | return nil, err 113 | } 114 | return &SockAddr{IP: ip, Port: uint16(v)}, nil 115 | } 116 | 117 | func parseSocktab(r io.Reader, accept AcceptFn) ([]SockTabEntry, error) { 118 | br := bufio.NewScanner(r) 119 | tab := make([]SockTabEntry, 0, 4) 120 | 121 | // Discard title 122 | br.Scan() 123 | 124 | for br.Scan() { 125 | var e SockTabEntry 126 | line := br.Text() 127 | // Skip comments 128 | if i := strings.Index(line, "#"); i >= 0 { 129 | line = line[:i] 130 | } 131 | fields := strings.Fields(line) 132 | if len(fields) < 12 { 133 | return nil, fmt.Errorf("netstat: not enough fields: %v, %v", len(fields), fields) 134 | } 135 | addr, err := parseAddr(fields[1]) 136 | if err != nil { 137 | return nil, err 138 | } 139 | e.LocalAddr = addr 140 | addr, err = parseAddr(fields[2]) 141 | if err != nil { 142 | return nil, err 143 | } 144 | e.RemoteAddr = addr 145 | u, err := strconv.ParseUint(fields[3], 16, 8) 146 | if err != nil { 147 | return nil, err 148 | } 149 | e.State = SkState(u) 150 | u, err = strconv.ParseUint(fields[7], 10, 32) 151 | if err != nil { 152 | return nil, err 153 | } 154 | e.UID = uint32(u) 155 | e.ino = fields[9] 156 | if accept(&e) { 157 | tab = append(tab, e) 158 | } 159 | } 160 | return tab, br.Err() 161 | } 162 | 163 | type procFd struct { 164 | base string 165 | pid int 166 | sktab []SockTabEntry 167 | p *Process 168 | } 169 | 170 | const sockPrefix = "socket:[" 171 | 172 | func getProcName(s []byte) string { 173 | i := bytes.Index(s, []byte("(")) 174 | if i < 0 { 175 | return "" 176 | } 177 | j := bytes.LastIndex(s, []byte(")")) 178 | if i < 0 { 179 | return "" 180 | } 181 | if i > j { 182 | return "" 183 | } 184 | return string(s[i+1 : j]) 185 | } 186 | 187 | func (p *procFd) iterFdDir() { 188 | // link name is of the form socket:[5860846] 189 | fddir := path.Join(p.base, "/fd") 190 | fi, err := ioutil.ReadDir(fddir) 191 | if err != nil { 192 | return 193 | } 194 | var buf [128]byte 195 | 196 | for _, file := range fi { 197 | fd := path.Join(fddir, file.Name()) 198 | lname, err := os.Readlink(fd) 199 | if err != nil || !strings.HasPrefix(lname, sockPrefix) { 200 | continue 201 | } 202 | 203 | for i := range p.sktab { 204 | sk := &p.sktab[i] 205 | ss := sockPrefix + sk.ino + "]" 206 | if ss != lname { 207 | continue 208 | } 209 | if p.p == nil { 210 | stat, err := os.Open(path.Join(p.base, "stat")) 211 | if err != nil { 212 | return 213 | } 214 | n, err := stat.Read(buf[:]) 215 | stat.Close() 216 | if err != nil { 217 | return 218 | } 219 | z := bytes.SplitN(buf[:n], []byte(" "), 3) 220 | name := getProcName(z[1]) 221 | p.p = &Process{p.pid, name} 222 | } 223 | sk.Process = p.p 224 | } 225 | } 226 | } 227 | 228 | func extractProcInfo(sktab []SockTabEntry) { 229 | const basedir = "/proc" 230 | fi, err := ioutil.ReadDir(basedir) 231 | if err != nil { 232 | return 233 | } 234 | 235 | for _, file := range fi { 236 | if !file.IsDir() { 237 | continue 238 | } 239 | pid, err := strconv.Atoi(file.Name()) 240 | if err != nil { 241 | continue 242 | } 243 | base := path.Join(basedir, file.Name()) 244 | proc := procFd{base: base, pid: pid, sktab: sktab} 245 | proc.iterFdDir() 246 | } 247 | } 248 | 249 | // doNetstat - collect information about network port status 250 | func doNetstat(path string, fn AcceptFn) ([]SockTabEntry, error) { 251 | f, err := os.Open(path) 252 | if err != nil { 253 | return nil, err 254 | } 255 | tabs, err := parseSocktab(f, fn) 256 | f.Close() 257 | if err != nil { 258 | return nil, err 259 | } 260 | extractProcInfo(tabs) 261 | return tabs, nil 262 | } 263 | 264 | // TCPSocks returns a slice of active TCP sockets containing only those 265 | // elements that satisfy the accept function 266 | func osTCPSocks(accept AcceptFn) ([]SockTabEntry, error) { 267 | return doNetstat(pathTCPTab, accept) 268 | } 269 | 270 | // TCP6Socks returns a slice of active TCP IPv4 sockets containing only those 271 | // elements that satisfy the accept function 272 | func osTCP6Socks(accept AcceptFn) ([]SockTabEntry, error) { 273 | return doNetstat(pathTCP6Tab, accept) 274 | } 275 | 276 | // UDPSocks returns a slice of active UDP sockets containing only those 277 | // elements that satisfy the accept function 278 | func osUDPSocks(accept AcceptFn) ([]SockTabEntry, error) { 279 | return doNetstat(pathUDPTab, accept) 280 | } 281 | 282 | // UDP6Socks returns a slice of active UDP IPv6 sockets containing only those 283 | // elements that satisfy the accept function 284 | func osUDP6Socks(accept AcceptFn) ([]SockTabEntry, error) { 285 | return doNetstat(pathUDP6Tab, accept) 286 | } 287 | -------------------------------------------------------------------------------- /netstat/netstat_windows.go: -------------------------------------------------------------------------------- 1 | // +build amd64 2 | 3 | package netstat 4 | 5 | import ( 6 | "bytes" 7 | "encoding/binary" 8 | "errors" 9 | "fmt" 10 | "net" 11 | "reflect" 12 | "syscall" 13 | "unsafe" 14 | ) 15 | 16 | const ( 17 | errInsuffBuff = syscall.Errno(122) 18 | 19 | Th32csSnapProcess = uint32(0x00000002) 20 | InvalidHandleValue = ^uintptr(0) 21 | MaxPath = 260 22 | ) 23 | 24 | var ( 25 | errNoMoreFiles = errors.New("no more files have been found") 26 | 27 | modiphlpapi = syscall.NewLazyDLL("Iphlpapi.dll") 28 | modkernel32 = syscall.NewLazyDLL("Kernel32.dll") 29 | 30 | procGetTCPTable2 = modiphlpapi.NewProc("GetTcpTable2") 31 | procGetTCP6Table2 = modiphlpapi.NewProc("GetTcp6Table2") 32 | procGetExtendedUDPTable = modiphlpapi.NewProc("GetExtendedUdpTable") 33 | procCreateSnapshot = modkernel32.NewProc("CreateToolhelp32Snapshot") 34 | procProcess32First = modkernel32.NewProc("Process32First") 35 | procProcess32Next = modkernel32.NewProc("Process32Next") 36 | ) 37 | 38 | // Socket states 39 | const ( 40 | Close SkState = 0x01 41 | Listen = 0x02 42 | SynSent = 0x03 43 | SynRecv = 0x04 44 | Established = 0x05 45 | FinWait1 = 0x06 46 | FinWait2 = 0x07 47 | CloseWait = 0x08 48 | Closing = 0x09 49 | LastAck = 0x0a 50 | TimeWait = 0x0b 51 | DeleteTcb = 0x0c 52 | ) 53 | 54 | var skStates = [...]string{ 55 | "UNKNOWN", 56 | "", // CLOSE 57 | "LISTEN", 58 | "SYN_SENT", 59 | "SYN_RECV", 60 | "ESTABLISHED", 61 | "FIN_WAIT1", 62 | "FIN_WAIT2", 63 | "CLOSE_WAIT", 64 | "CLOSING", 65 | "LAST_ACK", 66 | "TIME_WAIT", 67 | "DELETE_TCB", 68 | } 69 | 70 | func memToIPv4(p unsafe.Pointer) net.IP { 71 | a := (*[net.IPv4len]byte)(p) 72 | ip := make(net.IP, net.IPv4len) 73 | copy(ip, a[:]) 74 | return ip 75 | } 76 | 77 | func memToIPv6(p unsafe.Pointer) net.IP { 78 | a := (*[net.IPv6len]byte)(p) 79 | ip := make(net.IP, net.IPv6len) 80 | copy(ip, a[:]) 81 | return ip 82 | } 83 | 84 | func memtohs(n unsafe.Pointer) uint16 { 85 | return binary.BigEndian.Uint16((*[2]byte)(n)[:]) 86 | } 87 | 88 | type WinSock struct { 89 | Addr uint32 90 | Port uint32 91 | } 92 | 93 | func (w *WinSock) Sock() *SockAddr { 94 | ip := memToIPv4(unsafe.Pointer(&w.Addr)) 95 | port := memtohs(unsafe.Pointer(&w.Port)) 96 | return &SockAddr{IP: ip, Port: port} 97 | } 98 | 99 | type WinSock6 struct { 100 | Addr [net.IPv6len]byte 101 | ScopeID uint32 102 | Port uint32 103 | } 104 | 105 | func (w *WinSock6) Sock() *SockAddr { 106 | ip := memToIPv6(unsafe.Pointer(&w.Addr[0])) 107 | port := memtohs(unsafe.Pointer(&w.Port)) 108 | return &SockAddr{IP: ip, Port: port} 109 | } 110 | 111 | type MibTCPRow2 struct { 112 | State uint32 113 | LocalAddr WinSock 114 | RemoteAddr WinSock 115 | WinPid 116 | OffloadState uint32 117 | } 118 | 119 | type WinPid uint32 120 | 121 | func (pid WinPid) Process(snp ProcessSnapshot) *Process { 122 | if pid < 1 { 123 | return nil 124 | } 125 | return &Process{ 126 | Pid: int(pid), 127 | Name: snp.ProcPIDToName(uint32(pid)), 128 | } 129 | } 130 | 131 | func (m *MibTCPRow2) LocalSock() *SockAddr { return m.LocalAddr.Sock() } 132 | func (m *MibTCPRow2) RemoteSock() *SockAddr { return m.RemoteAddr.Sock() } 133 | func (m *MibTCPRow2) SockState() SkState { return SkState(m.State) } 134 | 135 | type MibTCPTable2 struct { 136 | NumEntries uint32 137 | Table [1]MibTCPRow2 138 | } 139 | 140 | func (t *MibTCPTable2) Rows() []MibTCPRow2 { 141 | var s []MibTCPRow2 142 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 143 | hdr.Data = uintptr(unsafe.Pointer(&t.Table[0])) 144 | hdr.Len = int(t.NumEntries) 145 | hdr.Cap = int(t.NumEntries) 146 | return s 147 | } 148 | 149 | // MibTCP6Row2 structure contains information that describes an IPv6 TCP 150 | // connection. 151 | type MibTCP6Row2 struct { 152 | LocalAddr WinSock6 153 | RemoteAddr WinSock6 154 | State uint32 155 | WinPid 156 | OffloadState uint32 157 | } 158 | 159 | func (m *MibTCP6Row2) LocalSock() *SockAddr { return m.LocalAddr.Sock() } 160 | func (m *MibTCP6Row2) RemoteSock() *SockAddr { return m.RemoteAddr.Sock() } 161 | func (m *MibTCP6Row2) SockState() SkState { return SkState(m.State) } 162 | 163 | // MibTCP6Table2 structure contains a table of IPv6 TCP connections on the 164 | // local computer. 165 | type MibTCP6Table2 struct { 166 | NumEntries uint32 167 | Table [1]MibTCP6Row2 168 | } 169 | 170 | func (t *MibTCP6Table2) Rows() []MibTCP6Row2 { 171 | var s []MibTCP6Row2 172 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 173 | hdr.Data = uintptr(unsafe.Pointer(&t.Table[0])) 174 | hdr.Len = int(t.NumEntries) 175 | hdr.Cap = int(t.NumEntries) 176 | return s 177 | } 178 | 179 | // MibUDPRowOwnerPID structure contains an entry from the User Datagram 180 | // Protocol (UDP) listener table for IPv4 on the local computer. The entry also 181 | // includes the process ID (PID) that issued the call to the bind function for 182 | // the UDP endpoint 183 | type MibUDPRowOwnerPID struct { 184 | WinSock 185 | WinPid 186 | } 187 | 188 | func (m *MibUDPRowOwnerPID) LocalSock() *SockAddr { return m.Sock() } 189 | func (m *MibUDPRowOwnerPID) RemoteSock() *SockAddr { return &SockAddr{net.IPv4zero, 0} } 190 | func (m *MibUDPRowOwnerPID) SockState() SkState { return Close } 191 | 192 | // MibUDPTableOwnerPID structure contains the User Datagram Protocol (UDP) 193 | // listener table for IPv4 on the local computer. The table also includes the 194 | // process ID (PID) that issued the call to the bind function for each UDP 195 | // endpoint. 196 | type MibUDPTableOwnerPID struct { 197 | NumEntries uint32 198 | Table [1]MibUDPRowOwnerPID 199 | } 200 | 201 | func (t *MibUDPTableOwnerPID) Rows() []MibUDPRowOwnerPID { 202 | var s []MibUDPRowOwnerPID 203 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 204 | hdr.Data = uintptr(unsafe.Pointer(&t.Table[0])) 205 | hdr.Len = int(t.NumEntries) 206 | hdr.Cap = int(t.NumEntries) 207 | return s 208 | } 209 | 210 | // MibUDP6RowOwnerPID serves the same purpose as MibUDPRowOwnerPID, except that 211 | // the information in this case is for IPv6. 212 | type MibUDP6RowOwnerPID struct { 213 | WinSock6 214 | WinPid 215 | } 216 | 217 | func (m *MibUDP6RowOwnerPID) LocalSock() *SockAddr { return m.Sock() } 218 | func (m *MibUDP6RowOwnerPID) RemoteSock() *SockAddr { return &SockAddr{net.IPv4zero, 0} } 219 | func (m *MibUDP6RowOwnerPID) SockState() SkState { return Close } 220 | 221 | // MibUDP6TableOwnerPID serves the same purpose as MibUDPTableOwnerPID for IPv6 222 | type MibUDP6TableOwnerPID struct { 223 | NumEntries uint32 224 | Table [1]MibUDP6RowOwnerPID 225 | } 226 | 227 | func (t *MibUDP6TableOwnerPID) Rows() []MibUDP6RowOwnerPID { 228 | var s []MibUDP6RowOwnerPID 229 | hdr := (*reflect.SliceHeader)(unsafe.Pointer(&s)) 230 | hdr.Data = uintptr(unsafe.Pointer(&t.Table[0])) 231 | hdr.Len = int(t.NumEntries) 232 | hdr.Cap = int(t.NumEntries) 233 | return s 234 | } 235 | 236 | // Processentry32 describes an entry from a list of the processes residing in 237 | // the system address space when a snapshot was taken 238 | type Processentry32 struct { 239 | Size uint32 240 | CntUsage uint32 241 | Th32ProcessID uint32 242 | Th32DefaultHeapID uintptr 243 | Th32ModuleID uint32 244 | CntThreads uint32 245 | Th32ParentProcessID uint32 246 | PriClassBase int32 247 | Flags uint32 248 | ExeFile [MaxPath]byte 249 | } 250 | 251 | func rawGetTCPTable2(proc uintptr, tab unsafe.Pointer, size *uint32, order bool) error { 252 | var oint uintptr 253 | if order { 254 | oint = 1 255 | } 256 | r1, _, callErr := syscall.Syscall( 257 | proc, 258 | uintptr(3), 259 | uintptr(tab), 260 | uintptr(unsafe.Pointer(size)), 261 | oint) 262 | if callErr != 0 { 263 | return callErr 264 | } 265 | if r1 != 0 { 266 | return syscall.Errno(r1) 267 | } 268 | return nil 269 | } 270 | 271 | func getTCPTable2(proc uintptr, order bool) ([]byte, error) { 272 | var ( 273 | size uint32 274 | buf []byte 275 | ) 276 | 277 | // determine size 278 | err := rawGetTCPTable2(proc, unsafe.Pointer(nil), &size, false) 279 | if err != nil && err != errInsuffBuff { 280 | return nil, err 281 | } 282 | buf = make([]byte, size) 283 | table := unsafe.Pointer(&buf[0]) 284 | err = rawGetTCPTable2(proc, table, &size, true) 285 | if err != nil { 286 | return nil, err 287 | } 288 | return buf, nil 289 | } 290 | 291 | // GetTCPTable2 function retrieves the IPv4 TCP connection table 292 | func GetTCPTable2(order bool) (*MibTCPTable2, error) { 293 | b, err := getTCPTable2(procGetTCPTable2.Addr(), true) 294 | if err != nil { 295 | return nil, err 296 | } 297 | return (*MibTCPTable2)(unsafe.Pointer(&b[0])), nil 298 | } 299 | 300 | // GetTCP6Table2 function retrieves the IPv6 TCP connection table 301 | func GetTCP6Table2(order bool) (*MibTCP6Table2, error) { 302 | b, err := getTCPTable2(procGetTCP6Table2.Addr(), true) 303 | if err != nil { 304 | return nil, err 305 | } 306 | return (*MibTCP6Table2)(unsafe.Pointer(&b[0])), nil 307 | } 308 | 309 | // The UDPTableClass enumeration defines the set of values used to indicate 310 | // the type of table returned by calls to GetExtendedUDPTable 311 | type UDPTableClass uint 312 | 313 | // Possible table class values 314 | const ( 315 | UDPTableBasic UDPTableClass = iota 316 | UDPTableOwnerPID 317 | UDPTableOwnerModule 318 | ) 319 | 320 | func getExtendedUDPTable(table unsafe.Pointer, size *uint32, order bool, af uint32, cl UDPTableClass) error { 321 | var oint uintptr 322 | if order { 323 | oint = 1 324 | } 325 | r1, _, callErr := syscall.Syscall6( 326 | procGetExtendedUDPTable.Addr(), 327 | uintptr(6), 328 | uintptr(table), 329 | uintptr(unsafe.Pointer(size)), 330 | oint, 331 | uintptr(af), 332 | uintptr(cl), 333 | uintptr(0)) 334 | if callErr != 0 { 335 | return callErr 336 | } 337 | if r1 != 0 { 338 | return syscall.Errno(r1) 339 | } 340 | return nil 341 | } 342 | 343 | // GetExtendedUDPTable function retrieves a table that contains a list of UDP 344 | // endpoints available to the application 345 | func GetExtendedUDPTable(order bool, af uint32, cl UDPTableClass) ([]byte, error) { 346 | var size uint32 347 | err := getExtendedUDPTable(nil, &size, order, af, cl) 348 | if err != nil && err != errInsuffBuff { 349 | return nil, err 350 | } 351 | buf := make([]byte, size) 352 | err = getExtendedUDPTable(unsafe.Pointer(&buf[0]), &size, order, af, cl) 353 | if err != nil { 354 | return nil, err 355 | } 356 | return buf, nil 357 | } 358 | 359 | func GetUDPTableOwnerPID(order bool) (*MibUDPTableOwnerPID, error) { 360 | b, err := GetExtendedUDPTable(true, syscall.AF_INET, UDPTableOwnerPID) 361 | if err != nil { 362 | return nil, err 363 | } 364 | return (*MibUDPTableOwnerPID)(unsafe.Pointer(&b[0])), nil 365 | } 366 | 367 | func GetUDP6TableOwnerPID(order bool) (*MibUDP6TableOwnerPID, error) { 368 | b, err := GetExtendedUDPTable(true, syscall.AF_INET6, UDPTableOwnerPID) 369 | if err != nil { 370 | return nil, err 371 | } 372 | return (*MibUDP6TableOwnerPID)(unsafe.Pointer(&b[0])), nil 373 | } 374 | 375 | // ProcessSnapshot wraps the syscall.Handle, which represents a snapshot of 376 | // the specified processes. 377 | type ProcessSnapshot syscall.Handle 378 | 379 | // CreateToolhelp32Snapshot takes a snapshot of the specified processes, as 380 | // well as the heaps, modules, and threads used by these processes 381 | func CreateToolhelp32Snapshot(flags uint32, pid uint32) (ProcessSnapshot, error) { 382 | r1, _, callErr := syscall.Syscall( 383 | procCreateSnapshot.Addr(), 384 | uintptr(2), 385 | uintptr(flags), 386 | uintptr(pid), 0) 387 | ret := ProcessSnapshot(r1) 388 | if callErr != 0 { 389 | return ret, callErr 390 | } 391 | if r1 == InvalidHandleValue { 392 | return ret, fmt.Errorf("invalid handle value: %#v", r1) 393 | } 394 | return ret, nil 395 | } 396 | 397 | // ProcPIDToName translates PID to a name 398 | func (snp ProcessSnapshot) ProcPIDToName(pid uint32) string { 399 | var processEntry Processentry32 400 | processEntry.Size = uint32(unsafe.Sizeof(processEntry)) 401 | handle := syscall.Handle(snp) 402 | err := Process32First(handle, &processEntry) 403 | if err != nil { 404 | return "" 405 | } 406 | for { 407 | if processEntry.Th32ProcessID == pid { 408 | return StringFromNullTerminated(processEntry.ExeFile[:]) 409 | } 410 | err = Process32Next(handle, &processEntry) 411 | if err != nil { 412 | return "" 413 | } 414 | } 415 | } 416 | 417 | // Close releases underlying win32 handle 418 | func (snp ProcessSnapshot) Close() error { 419 | return syscall.CloseHandle(syscall.Handle(snp)) 420 | } 421 | 422 | // Process32First retrieves information about the first process encountered 423 | // in a system snapshot 424 | func Process32First(handle syscall.Handle, pe *Processentry32) error { 425 | pe.Size = uint32(unsafe.Sizeof(*pe)) 426 | r1, _, callErr := syscall.Syscall( 427 | procProcess32First.Addr(), 428 | uintptr(2), 429 | uintptr(handle), 430 | uintptr(unsafe.Pointer(pe)), 0) 431 | if callErr != 0 { 432 | return callErr 433 | } 434 | if r1 == 0 { 435 | return errNoMoreFiles 436 | } 437 | return nil 438 | } 439 | 440 | // Process32Next retrieves information about the next process 441 | // recorded in a system snapshot 442 | func Process32Next(handle syscall.Handle, pe *Processentry32) error { 443 | pe.Size = uint32(unsafe.Sizeof(*pe)) 444 | r1, _, callErr := syscall.Syscall( 445 | procProcess32Next.Addr(), 446 | uintptr(2), 447 | uintptr(handle), 448 | uintptr(unsafe.Pointer(pe)), 0) 449 | if callErr != 0 { 450 | return callErr 451 | } 452 | if r1 == 0 { 453 | return errNoMoreFiles 454 | } 455 | return nil 456 | } 457 | 458 | // StringFromNullTerminated returns a string from a nul-terminated byte slice 459 | func StringFromNullTerminated(b []byte) string { 460 | n := bytes.IndexByte(b, '\x00') 461 | if n < 1 { 462 | return "" 463 | } 464 | return string(b[:n]) 465 | } 466 | 467 | type winSockEnt interface { 468 | LocalSock() *SockAddr 469 | RemoteSock() *SockAddr 470 | SockState() SkState 471 | Process(snp ProcessSnapshot) *Process 472 | } 473 | 474 | func toSockTabEntry(ws winSockEnt, snp ProcessSnapshot) SockTabEntry { 475 | return SockTabEntry{ 476 | LocalAddr: ws.LocalSock(), 477 | RemoteAddr: ws.RemoteSock(), 478 | State: ws.SockState(), 479 | Process: ws.Process(snp), 480 | } 481 | } 482 | 483 | func osTCPSocks(accept AcceptFn) ([]SockTabEntry, error) { 484 | tbl, err := GetTCPTable2(true) 485 | if err != nil { 486 | return nil, err 487 | } 488 | snp, err := CreateToolhelp32Snapshot(Th32csSnapProcess, 0) 489 | if err != nil { 490 | return nil, err 491 | } 492 | var sktab []SockTabEntry 493 | s := tbl.Rows() 494 | for i := range s { 495 | ent := toSockTabEntry(&s[i], snp) 496 | if accept(&ent) { 497 | sktab = append(sktab, ent) 498 | } 499 | } 500 | snp.Close() 501 | return sktab, nil 502 | } 503 | 504 | func osTCP6Socks(accept AcceptFn) ([]SockTabEntry, error) { 505 | tbl, err := GetTCP6Table2(true) 506 | if err != nil { 507 | return nil, err 508 | } 509 | snp, err := CreateToolhelp32Snapshot(Th32csSnapProcess, 0) 510 | if err != nil { 511 | return nil, err 512 | } 513 | var sktab []SockTabEntry 514 | s := tbl.Rows() 515 | for i := range s { 516 | ent := toSockTabEntry(&s[i], snp) 517 | if accept(&ent) { 518 | sktab = append(sktab, ent) 519 | } 520 | } 521 | snp.Close() 522 | return sktab, nil 523 | } 524 | 525 | func osUDPSocks(accept AcceptFn) ([]SockTabEntry, error) { 526 | tbl, err := GetUDPTableOwnerPID(true) 527 | if err != nil { 528 | return nil, err 529 | } 530 | snp, err := CreateToolhelp32Snapshot(Th32csSnapProcess, 0) 531 | if err != nil { 532 | return nil, err 533 | } 534 | var sktab []SockTabEntry 535 | s := tbl.Rows() 536 | for i := range s { 537 | ent := toSockTabEntry(&s[i], snp) 538 | if accept(&ent) { 539 | sktab = append(sktab, ent) 540 | } 541 | } 542 | snp.Close() 543 | return sktab, nil 544 | } 545 | 546 | func osUDP6Socks(accept AcceptFn) ([]SockTabEntry, error) { 547 | tbl, err := GetUDP6TableOwnerPID(true) 548 | if err != nil { 549 | return nil, err 550 | } 551 | snp, err := CreateToolhelp32Snapshot(Th32csSnapProcess, 0) 552 | if err != nil { 553 | return nil, err 554 | } 555 | var sktab []SockTabEntry 556 | s := tbl.Rows() 557 | for i := range s { 558 | ent := toSockTabEntry(&s[i], snp) 559 | if accept(&ent) { 560 | sktab = append(sktab, ent) 561 | } 562 | } 563 | snp.Close() 564 | return sktab, nil 565 | } 566 | --------------------------------------------------------------------------------