├── .gitignore ├── README.md ├── cmd └── main.go ├── endian_linux.go ├── go.mod ├── go.sum ├── goss.go ├── goss_fallback.go ├── goss_linux.go ├── inetdiag_linux.go └── userent_linux.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.a 3 | .DS_Store 4 | *.log 5 | *.log.* 6 | vendor -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## GoSS 2 | 3 | goss是用于分析网络连接工具,基于golang开发, 可作为golang包引入使用,实现原理参考ss命令. 4 | 他的最大特点是快, 当你的系统有上万个tcp链接要连接的时候, netstat等常规工具变成废铁了, 这时候他的作用就非常明显了. 5 | 6 | ## 原理 7 | 8 | ss 快的秘诀在于,它利用到了tcp_diag/udp_diag内核模块。tcp_diag/udp_tcp_diag 是一个用于分析统计的模块, 9 | 可以通过netlink获得Linux 内核中第一手的网络通讯信息,这就确保了获取网络连接的快捷高效. 10 | 11 | ### 注意 12 | 目前仅支持linux环境运行 13 | 14 | ### TODO 15 | - [ ] 支持windows 16 | - [ ] 支持darwin 17 | 18 | ### 使用 19 | 20 | main.go 21 | ```go 22 | package main 23 | 24 | import ( 25 | "encoding/json" 26 | "fmt" 27 | "github.com/dean2021/goss" 28 | ) 29 | 30 | func main() { 31 | connections, err := goss.Connections("all") 32 | if err != nil{ 33 | panic(err) 34 | } 35 | for _, conn := range connections { 36 | s, _ := json.Marshal(conn) 37 | fmt.Println(string(s)) 38 | } 39 | } 40 | ``` 41 | 输出: 42 | ```json 43 | {"proto":"tcp","recvq":0,"sendq":128,"local":{"addr":"0.0.0.0","port":"22"},"foreign":{"addr":"0.0.0.0","port":"0"},"state":"LISTEN","inode":17526,"process":null} 44 | {"proto":"tcp","recvq":0,"sendq":0,"local":{"addr":"10.211.55.18","port":"22"},"foreign":{"addr":"10.211.55.2","port":"60443"},"state":"ESTAB","inode":94365,"process":null} 45 | {"proto":"tcp","recvq":0,"sendq":0,"local":{"addr":"10.211.55.18","port":"22"},"foreign":{"addr":"10.211.55.2","port":"52681"},"state":"ESTAB","inode":40101,"process":null} 46 | {"proto":"tcp","recvq":0,"sendq":0,"local":{"addr":"10.211.55.18","port":"22"},"foreign":{"addr":"10.211.55.2","port":"60305"},"state":"ESTAB","inode":94290,"process":null} 47 | {"proto":"udp","recvq":0,"sendq":0,"local":{"addr":"127.0.0.1","port":"323"},"foreign":{"addr":"0.0.0.0","port":"0"},"state":"UNCONN","inode":14002,"process":null} 48 | {"proto":"udp","recvq":0,"sendq":0,"local":{"addr":"0.0.0.0","port":"8888"},"foreign":{"addr":"0.0.0.0","port":"0"},"state":"UNCONN","inode":95273,"process":{"inode":95273,"fd":4,"pid":27246,"p_name":"nc","p_pid":27222,"p_gid":27246}} 49 | {"proto":"udp","recvq":0,"sendq":0,"local":{"addr":"0.0.0.0","port":"68"},"foreign":{"addr":"0.0.0.0","port":"0"},"state":"UNCONN","inode":92582,"process":null} 50 | ``` 51 | 52 | 53 | ## 参考/感谢 54 | 55 | 1. github.com/elastic/gosigar/sys/linux 56 | 2. https://github.com/yuuki/lstf 57 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Dean. 2 | // Authors: Dean 3 | // Date: 2020/9/25 10:25 上午 4 | 5 | package main 6 | 7 | import ( 8 | "encoding/json" 9 | "fmt" 10 | 11 | "github.com/dean2021/goss" 12 | ) 13 | 14 | func main() { 15 | connections, err := goss.Connections(goss.AF_INET, "all") 16 | if err != nil { 17 | panic(err) 18 | } 19 | for _, conn := range connections { 20 | s, _ := json.Marshal(conn) 21 | fmt.Println(string(s)) 22 | } 23 | 24 | connectionsV6, err := goss.Connections(goss.AF_INET6, "all") 25 | if err != nil { 26 | panic(err) 27 | } 28 | for _, conn := range connectionsV6 { 29 | s, _ := json.Marshal(conn) 30 | fmt.Println(string(s)) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /endian_linux.go: -------------------------------------------------------------------------------- 1 | package goss 2 | 3 | import ( 4 | "encoding/binary" 5 | "unsafe" 6 | ) 7 | 8 | func GetEndian() binary.ByteOrder { 9 | var i int32 = 0x1 10 | v := (*[4]byte)(unsafe.Pointer(&i)) 11 | if v[0] == 0 { 12 | return binary.BigEndian 13 | } else { 14 | return binary.LittleEndian 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dean2021/goss 2 | 3 | go 1.14 4 | 5 | require ( 6 | github.com/pkg/errors v0.9.1 7 | ) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/elastic/gosigar v0.11.0 h1:L8Stala75cAVQo+HLJebmtaOr1032y357R0CjbKSrZc= 2 | github.com/elastic/gosigar v0.11.0/go.mod h1:cdorVVzy1fhmEqmtgqkoE3bYtCfSCkVyjTyCIo22xvs= 3 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 4 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 5 | -------------------------------------------------------------------------------- /goss.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Dean. 2 | // Authors: Dean 3 | // Date: 2020/9/25 10:25 上午 4 | 5 | package goss 6 | 7 | import ( 8 | "syscall" 9 | ) 10 | 11 | var netConnectionKindMap = map[string][]uint8{ 12 | "all": {syscall.IPPROTO_TCP, syscall.IPPROTO_UDP}, 13 | "tcp": {syscall.IPPROTO_TCP}, 14 | "udp": {syscall.IPPROTO_UDP}, 15 | } 16 | 17 | var netProtocolKindMap = map[uint8]string{ 18 | syscall.IPPROTO_TCP: "tcp", 19 | syscall.IPPROTO_UDP: "udp", 20 | } 21 | 22 | // AddrPort are : 23 | type AddrPort struct { 24 | Addr string `json:"addr"` 25 | Port string `json:"port"` 26 | } 27 | 28 | // Stat represents a socket statistics. 29 | type Stat struct { 30 | Proto string `json:"proto"` 31 | RecvQ uint32 `json:"recvq"` 32 | SendQ uint32 `json:"sendq"` 33 | Local *AddrPort `json:"local"` 34 | Foreign *AddrPort `json:"foreign"` 35 | State string `json:"state"` 36 | Inode uint32 `json:"inode"` 37 | Process *UserEnt `json:"process"` 38 | } 39 | 40 | // UserEnt represents a detail of network socket. 41 | // see https://github.com/shemminger/iproute2/blob/afa588490b7e87c5adfb05d5163074e20b6ff14a/misc/ss.c#L509. 42 | type UserEnt struct { 43 | Inode uint32 `json:"inode"` // inode number 44 | FD int `json:"fd"` // file discryptor 45 | Pid int `json:"pid"` // process id 46 | PName string `json:"p_name"` // process name 47 | PPid int `json:"p_pid"` // parent process id 48 | PGid int `json:"p_gid"` // process group id 49 | } 50 | -------------------------------------------------------------------------------- /goss_fallback.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Dean. 2 | // Authors: Dean 3 | // Date: 2020/9/25 10:25 上午 4 | 5 | //go:build !linux 6 | 7 | package goss 8 | 9 | import "errors" 10 | 11 | type AddressFamily uint8 12 | 13 | // https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/linux/socket.h#L159 14 | const ( 15 | AF_INET AddressFamily = 2 16 | AF_INET6 AddressFamily = 10 17 | ) 18 | 19 | func Connections(family AddressFamily, kind string) ([]*Stat, error) { 20 | return nil, errors.New("not implemented") 21 | } 22 | 23 | type InetDiagMsg uint8 24 | 25 | func ConnectionsWithProtocol(family AddressFamily, protocol uint8) ([]*InetDiagMsg, error) { 26 | return nil, errors.New("not implemented") 27 | } 28 | -------------------------------------------------------------------------------- /goss_linux.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Dean. 2 | // Authors: Dean 3 | // Date: 2020/9/25 10:25 上午 4 | 5 | //go:build linux 6 | 7 | package goss 8 | 9 | import ( 10 | "bytes" 11 | "encoding/binary" 12 | "strconv" 13 | "syscall" 14 | "unsafe" 15 | ) 16 | 17 | func Connections(family AddressFamily, kind string) ([]*Stat, error) { 18 | var connectionsStat []*Stat 19 | userEntries, err := BuildUserEntries() 20 | if err != nil { 21 | return nil, err 22 | } 23 | protocols := netConnectionKindMap[kind] 24 | for _, protocol := range protocols { 25 | conn, err := ConnectionsWithProtocol(family, protocol) 26 | if err != nil { 27 | return nil, err 28 | } 29 | for _, c := range conn { 30 | stats := &Stat{ 31 | Proto: netProtocolKindMap[protocol], 32 | RecvQ: c.RQueue, 33 | SendQ: c.WQueue, 34 | Local: &AddrPort{ 35 | Addr: c.SrcIP().String(), 36 | Port: strconv.Itoa(c.SrcPort()), 37 | }, 38 | Foreign: &AddrPort{ 39 | Addr: c.DstIP().String(), 40 | Port: strconv.Itoa(c.DstPort()), 41 | }, 42 | State: TCPState(c.State).String(), 43 | Inode: c.Inode, 44 | } 45 | 46 | if process, ok := userEntries[c.Inode]; ok { 47 | stats.Process = process 48 | } 49 | connectionsStat = append(connectionsStat, stats) 50 | } 51 | } 52 | return connectionsStat, nil 53 | } 54 | 55 | func ConnectionsWithProtocol(family AddressFamily, protocol uint8) ([]*InetDiagMsg, error) { 56 | hdr := syscall.NlMsghdr{ 57 | Type: uint16(SOCK_DIAG_BY_FAMILY), 58 | Flags: uint16(syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST), 59 | Pid: uint32(0), 60 | } 61 | req := InetDiagReqV2{ 62 | Family: uint8(family), 63 | Protocol: protocol, 64 | States: AllTCPStates, 65 | } 66 | var sizeofInetDiagReqV2 = int(unsafe.Sizeof(InetDiagReqV2{})) 67 | byteOrder := GetEndian() 68 | buf := bytes.NewBuffer(make([]byte, sizeofInetDiagReqV2)) 69 | buf.Reset() 70 | if err := binary.Write(buf, byteOrder, req); err != nil { 71 | // This never returns an error. 72 | return nil, err 73 | } 74 | b := buf.Bytes() 75 | req2 := syscall.NetlinkMessage{Header: hdr, Data: b} 76 | return NetlinkInetDiag(req2) 77 | } 78 | -------------------------------------------------------------------------------- /inetdiag_linux.go: -------------------------------------------------------------------------------- 1 | package goss 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "io" 7 | "net" 8 | "os" 9 | "syscall" 10 | "unsafe" 11 | 12 | "github.com/pkg/errors" 13 | ) 14 | 15 | // Enums / Constants 16 | 17 | const ( 18 | // AllTCPStates is a flag to request all sockets in any TCP state. 19 | AllTCPStates = ^uint32(0) 20 | 21 | // TCPDIAG_GETSOCK is the netlink message type for requesting TCP diag data. 22 | // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L7 23 | TCPDIAG_GETSOCK = 18 24 | 25 | // SOCK_DIAG_BY_FAMILY is the netlink message type for requestion socket 26 | // diag data by family. This is newer and can be used with inet_diag_req_v2. 27 | // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/sock_diag.h#L6 28 | SOCK_DIAG_BY_FAMILY = 20 29 | ) 30 | 31 | // TCPState represents the state of a TCP connection. 32 | type TCPState uint8 33 | 34 | // https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/net/tcp_states.h#L16 35 | const ( 36 | TCP_ESTABLISHED TCPState = iota + 1 37 | TCP_SYN_SENT 38 | TCP_SYN_RECV 39 | TCP_FIN_WAIT1 40 | TCP_FIN_WAIT2 41 | TCP_TIME_WAIT 42 | TCP_CLOSE 43 | TCP_CLOSE_WAIT 44 | TCP_LAST_ACK 45 | TCP_LISTEN 46 | TCP_CLOSING /* Now a valid state */ 47 | ) 48 | 49 | var tcpStateNames = map[TCPState]string{ 50 | TCP_ESTABLISHED: "ESTAB", 51 | TCP_SYN_SENT: "SYN-SENT", 52 | TCP_SYN_RECV: "SYN-RECV", 53 | TCP_FIN_WAIT1: "FIN-WAIT-1", 54 | TCP_FIN_WAIT2: "FIN-WAIT-2", 55 | TCP_TIME_WAIT: "TIME-WAIT", 56 | TCP_CLOSE: "UNCONN", 57 | TCP_CLOSE_WAIT: "CLOSE-WAIT", 58 | TCP_LAST_ACK: "LAST-ACK", 59 | TCP_LISTEN: "LISTEN", 60 | TCP_CLOSING: "CLOSING", 61 | } 62 | 63 | func (s TCPState) String() string { 64 | if state, found := tcpStateNames[s]; found { 65 | return state 66 | } 67 | return "UNKNOWN" 68 | } 69 | 70 | // AddressFamily is the address family of the socket. 71 | type AddressFamily uint8 72 | 73 | // https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/linux/socket.h#L159 74 | const ( 75 | AF_INET AddressFamily = 2 76 | AF_INET6 AddressFamily = 10 77 | ) 78 | 79 | // Request messages. 80 | 81 | var sizeofInetDiagReq = int(unsafe.Sizeof(InetDiagReq{})) 82 | 83 | // InetDiagReq (inet_diag_req) is used to request diagnostic data from older 84 | // kernels. 85 | // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L25 86 | type InetDiagReq struct { 87 | Family uint8 88 | SrcLen uint8 89 | DstLen uint8 90 | Ext uint8 91 | ID InetDiagSockID 92 | States uint32 // States to dump. 93 | DBs uint32 // Tables to dump. 94 | } 95 | 96 | func (r InetDiagReq) toWireFormat() []byte { 97 | buf := bytes.NewBuffer(make([]byte, sizeofInetDiagReq)) 98 | buf.Reset() 99 | if err := binary.Write(buf, byteOrder, r); err != nil { 100 | // This never returns an error. 101 | panic(err) 102 | } 103 | return buf.Bytes() 104 | } 105 | 106 | // NewInetDiagReq returns a new NetlinkMessage whose payload is an InetDiagReq. 107 | // Callers should set their own sequence number in the returned message header. 108 | func NewInetDiagReq() syscall.NetlinkMessage { 109 | hdr := syscall.NlMsghdr{ 110 | Type: uint16(TCPDIAG_GETSOCK), 111 | Flags: uint16(syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST), 112 | Pid: uint32(0), 113 | } 114 | req := InetDiagReq{ 115 | Family: uint8(AF_INET), // This returns both ipv4 and ipv6. 116 | States: AllTCPStates, 117 | } 118 | 119 | return syscall.NetlinkMessage{Header: hdr, Data: req.toWireFormat()} 120 | } 121 | 122 | // V2 Request 123 | 124 | var sizeofInetDiagReqV2 = int(unsafe.Sizeof(InetDiagReqV2{})) 125 | 126 | // InetDiagReqV2 (inet_diag_req_v2) is used to request diagnostic data. 127 | // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L37 128 | type InetDiagReqV2 struct { 129 | Family uint8 130 | Protocol uint8 131 | Ext uint8 132 | Pad uint8 133 | States uint32 134 | ID InetDiagSockID 135 | } 136 | 137 | // InetDiagSockID (inet_diag_sockid) contains the socket identity. 138 | // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L13 139 | type InetDiagSockID struct { 140 | SPort [2]byte // Source port (big-endian). 141 | DPort [2]byte // Destination port (big-endian). 142 | Src [16]byte // Source IP 143 | Dst [16]byte // Destination IP 144 | If uint32 145 | Cookie [2]uint32 146 | } 147 | 148 | var ( 149 | byteOrder = GetEndian() 150 | ) 151 | 152 | // NetlinkInetDiag sends the given netlink request parses the responses with the 153 | // assumption that they are inet_diag_msgs. This will allocate a temporary 154 | // buffer for reading from the socket whose size will be the length of a page 155 | // (usually 32k). Use NetlinkInetDiagWithBuf if you want to provide your own 156 | // buffer. 157 | func NetlinkInetDiag(request syscall.NetlinkMessage) ([]*InetDiagMsg, error) { 158 | return NetlinkInetDiagWithBuf(request, nil, nil) 159 | } 160 | 161 | // Response messages. 162 | 163 | // InetDiagMsg (inet_diag_msg) is the base info structure. It contains socket 164 | // identity (addrs/ports/cookie) and the information shown by netstat. 165 | // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L86 166 | type InetDiagMsg struct { 167 | Family uint8 // Address family. 168 | State uint8 // TCP State 169 | Timer uint8 170 | Retrans uint8 171 | 172 | ID InetDiagSockID 173 | 174 | Expires uint32 175 | RQueue uint32 // Recv-Q 176 | WQueue uint32 // Send-Q 177 | UID uint32 // UID 178 | Inode uint32 // Inode of socket. 179 | } 180 | 181 | // ParseInetDiagMsg parse an InetDiagMsg from a byte slice. It assumes the 182 | // InetDiagMsg starts at the beginning of b. Invoke this method to parse the 183 | // payload of a netlink response. 184 | func ParseInetDiagMsg(b []byte) (*InetDiagMsg, error) { 185 | r := bytes.NewReader(b) 186 | inetDiagMsg := &InetDiagMsg{} 187 | err := binary.Read(r, byteOrder, inetDiagMsg) 188 | if err != nil { 189 | return nil, errors.Wrap(err, "failed to unmarshal inet_diag_msg") 190 | } 191 | return inetDiagMsg, nil 192 | } 193 | 194 | // SrcPort returns the source (local) port. 195 | func (m InetDiagMsg) SrcPort() int { return int(binary.BigEndian.Uint16(m.ID.SPort[:])) } 196 | 197 | // DstPort returns the destination (remote) port. 198 | func (m InetDiagMsg) DstPort() int { return int(binary.BigEndian.Uint16(m.ID.DPort[:])) } 199 | 200 | // SrcIP returns the source (local) IP. 201 | func (m InetDiagMsg) SrcIP() net.IP { return ip(m.ID.Src, AddressFamily(m.Family)) } 202 | 203 | // DstIP returns the destination (remote) IP. 204 | func (m InetDiagMsg) DstIP() net.IP { return ip(m.ID.Dst, AddressFamily(m.Family)) } 205 | 206 | func (m InetDiagMsg) srcIPBytes() []byte { return ipBytes(m.ID.Src, AddressFamily(m.Family)) } 207 | func (m InetDiagMsg) dstIPBytes() []byte { return ipBytes(m.ID.Dst, AddressFamily(m.Family)) } 208 | 209 | func ip(data [16]byte, af AddressFamily) net.IP { 210 | if af == AF_INET { 211 | return net.IPv4(data[0], data[1], data[2], data[3]) 212 | } 213 | return net.IP(data[:]) 214 | } 215 | 216 | func ipBytes(data [16]byte, af AddressFamily) []byte { 217 | if af == AF_INET { 218 | return data[:4] 219 | } 220 | 221 | return data[:] 222 | } 223 | 224 | // NetlinkInetDiagWithBuf sends the given netlink request parses the responses 225 | // with the assumption that they are inet_diag_msgs. readBuf will be used to 226 | // hold the raw data read from the socket. If the length is not large enough to 227 | // hold the socket contents the data will be truncated. If readBuf is nil then a 228 | // temporary buffer will be allocated for each invocation. The resp writer, if 229 | // non-nil, will receive a copy of all bytes read (this is useful for 230 | // debugging). 231 | func NetlinkInetDiagWithBuf(request syscall.NetlinkMessage, readBuf []byte, resp io.Writer) ([]*InetDiagMsg, error) { 232 | s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_INET_DIAG) 233 | if err != nil { 234 | return nil, err 235 | } 236 | defer syscall.Close(s) 237 | 238 | lsa := &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK} 239 | if err := syscall.Sendto(s, serialize(request), 0, lsa); err != nil { 240 | return nil, err 241 | } 242 | 243 | if len(readBuf) == 0 { 244 | // Default size used in libnl. 245 | readBuf = make([]byte, os.Getpagesize()) 246 | } 247 | 248 | var inetDiagMsgs []*InetDiagMsg 249 | done: 250 | for { 251 | buf := readBuf 252 | nr, _, err := syscall.Recvfrom(s, buf, 0) 253 | if err != nil { 254 | return nil, err 255 | } 256 | if nr < syscall.NLMSG_HDRLEN { 257 | return nil, syscall.EINVAL 258 | } 259 | 260 | buf = buf[:nr] 261 | 262 | // Dump raw data for inspection purposes. 263 | if resp != nil { 264 | if _, err := resp.Write(buf); err != nil { 265 | return nil, err 266 | } 267 | } 268 | 269 | msgs, err := syscall.ParseNetlinkMessage(buf) 270 | if err != nil { 271 | return nil, err 272 | } 273 | 274 | for _, m := range msgs { 275 | if m.Header.Type == syscall.NLMSG_DONE { 276 | break done 277 | } 278 | if m.Header.Type == syscall.NLMSG_ERROR { 279 | return nil, ParseNetlinkError(m.Data) 280 | } 281 | 282 | inetDiagMsg, err := ParseInetDiagMsg(m.Data) 283 | if err != nil { 284 | return nil, err 285 | } 286 | inetDiagMsgs = append(inetDiagMsgs, inetDiagMsg) 287 | } 288 | } 289 | return inetDiagMsgs, nil 290 | } 291 | 292 | // NetlinkErrno represent the error code contained in a netlink message of 293 | // type NLMSG_ERROR. 294 | type NetlinkErrno uint32 295 | 296 | // Netlink error codes. 297 | const ( 298 | NLE_SUCCESS NetlinkErrno = iota 299 | NLE_FAILURE 300 | NLE_INTR 301 | NLE_BAD_SOCK 302 | NLE_AGAIN 303 | NLE_NOMEM 304 | NLE_EXIST 305 | NLE_INVAL 306 | NLE_RANGE 307 | NLE_MSGSIZE 308 | NLE_OPNOTSUPP 309 | NLE_AF_NOSUPPORT 310 | NLE_OBJ_NOTFOUND 311 | NLE_NOATTR 312 | NLE_MISSING_ATTR 313 | NLE_AF_MISMATCH 314 | NLE_SEQ_MISMATCH 315 | NLE_MSG_OVERFLOW 316 | NLE_MSG_TRUNC 317 | NLE_NOADDR 318 | NLE_SRCRT_NOSUPPORT 319 | NLE_MSG_TOOSHORT 320 | NLE_MSGTYPE_NOSUPPORT 321 | NLE_OBJ_MISMATCH 322 | NLE_NOCACHE 323 | NLE_BUSY 324 | NLE_PROTO_MISMATCH 325 | NLE_NOACCESS 326 | NLE_PERM 327 | NLE_PKTLOC_FILE 328 | NLE_PARSE_ERR 329 | NLE_NODEV 330 | NLE_IMMUTABLE 331 | NLE_DUMP_INTR 332 | NLE_ATTRSIZE 333 | ) 334 | 335 | // https://github.com/thom311/libnl/blob/libnl3_2_28/lib/error.c 336 | var netlinkErrorMsgs = map[NetlinkErrno]string{ 337 | NLE_SUCCESS: "Success", 338 | NLE_FAILURE: "Unspecific failure", 339 | NLE_INTR: "Interrupted system call", 340 | NLE_BAD_SOCK: "Bad socket", 341 | NLE_AGAIN: "Try again", 342 | NLE_NOMEM: "Out of memory", 343 | NLE_EXIST: "Object exists", 344 | NLE_INVAL: "Invalid input data or parameter", 345 | NLE_RANGE: "Input data out of range", 346 | NLE_MSGSIZE: "Message size not sufficient", 347 | NLE_OPNOTSUPP: "Operation not supported", 348 | NLE_AF_NOSUPPORT: "Address family not supported", 349 | NLE_OBJ_NOTFOUND: "Object not found", 350 | NLE_NOATTR: "Attribute not available", 351 | NLE_MISSING_ATTR: "Missing attribute", 352 | NLE_AF_MISMATCH: "Address family mismatch", 353 | NLE_SEQ_MISMATCH: "Message sequence number mismatch", 354 | NLE_MSG_OVERFLOW: "Kernel reported message overflow", 355 | NLE_MSG_TRUNC: "Kernel reported truncated message", 356 | NLE_NOADDR: "Invalid address for specified address family", 357 | NLE_SRCRT_NOSUPPORT: "Source based routing not supported", 358 | NLE_MSG_TOOSHORT: "Netlink message is too short", 359 | NLE_MSGTYPE_NOSUPPORT: "Netlink message type is not supported", 360 | NLE_OBJ_MISMATCH: "Object type does not match cache", 361 | NLE_NOCACHE: "Unknown or invalid cache type", 362 | NLE_BUSY: "Object busy", 363 | NLE_PROTO_MISMATCH: "Protocol mismatch", 364 | NLE_NOACCESS: "No Access", 365 | NLE_PERM: "Operation not permitted", 366 | NLE_PKTLOC_FILE: "Unable to open packet location file", 367 | NLE_PARSE_ERR: "Unable to parse object", 368 | NLE_NODEV: "No such device", 369 | NLE_IMMUTABLE: "Immutable attribute", 370 | NLE_DUMP_INTR: "Dump inconsistency detected, interrupted", 371 | NLE_ATTRSIZE: "Attribute max length exceeded", 372 | } 373 | 374 | func (e NetlinkErrno) Error() string { 375 | if msg, found := netlinkErrorMsgs[e]; found { 376 | return msg 377 | } 378 | 379 | return netlinkErrorMsgs[NLE_FAILURE] 380 | } 381 | 382 | // Netlink Error Code Handling 383 | 384 | // ParseNetlinkError parses the errno from the data section of a 385 | // syscall.NetlinkMessage. If netlinkData is less than 4 bytes an error 386 | // describing the problem will be returned. 387 | func ParseNetlinkError(netlinkData []byte) error { 388 | if len(netlinkData) >= 4 { 389 | errno := -GetEndian().Uint32(netlinkData[:4]) 390 | return NetlinkErrno(errno) 391 | } 392 | return errors.New("received netlink error (data too short to read errno)") 393 | } 394 | 395 | func serialize(msg syscall.NetlinkMessage) []byte { 396 | msg.Header.Len = uint32(syscall.SizeofNlMsghdr + len(msg.Data)) 397 | b := make([]byte, msg.Header.Len) 398 | byteOrder.PutUint32(b[0:4], msg.Header.Len) 399 | byteOrder.PutUint16(b[4:6], msg.Header.Type) 400 | byteOrder.PutUint16(b[6:8], msg.Header.Flags) 401 | byteOrder.PutUint32(b[8:12], msg.Header.Seq) 402 | byteOrder.PutUint32(b[12:16], msg.Header.Pid) 403 | copy(b[16:], msg.Data) 404 | return b 405 | } 406 | -------------------------------------------------------------------------------- /userent_linux.go: -------------------------------------------------------------------------------- 1 | package goss 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "io" 7 | "io/ioutil" 8 | "log" 9 | "net" 10 | "os" 11 | "path/filepath" 12 | "strconv" 13 | "strings" 14 | "syscall" 15 | ) 16 | 17 | var privateIPBlocks []*net.IPNet 18 | 19 | func init() { 20 | for _, cidr := range []string{ 21 | "10.0.0.0/8", // RFC1918 22 | "172.16.0.0/12", // RFC1918 23 | "192.168.0.0/16", // RFC1918 24 | "::1/128", // IPv6 loopback 25 | "fe80::/10", // IPv6 link-local 26 | "fc00::/7", // IPv6 unique local addr 27 | } { 28 | _, block, err := net.ParseCIDR(cidr) 29 | if err != nil { 30 | panic(fmt.Errorf("parse error on %q: %v", cidr, err)) 31 | } 32 | privateIPBlocks = append(privateIPBlocks, block) 33 | } 34 | } 35 | 36 | // UserEnts represents a hashmap of UserEnt as key is the inode. 37 | type UserEnts map[uint32]*UserEnt 38 | 39 | // ResolveAddr lookup first hostname from IP Address. 40 | func ResolveAddr(addr string) string { 41 | hostnames, _ := net.LookupAddr(addr) 42 | if len(hostnames) > 0 { 43 | return strings.TrimSuffix(hostnames[0], ".") 44 | } 45 | return addr 46 | } 47 | 48 | // LocalIPAddrs gets the string slice of localhost IPaddrs. 49 | func LocalIPAddrs() ([]string, error) { 50 | addrs, err := net.InterfaceAddrs() 51 | if err != nil { 52 | return nil, errors.New("failed to get local addresses:" + err.Error()) 53 | } 54 | addrStrings := make([]string, 0, len(addrs)) 55 | for _, a := range addrs { 56 | if ipnet, ok := a.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { 57 | if ipnet.IP.To4() != nil { 58 | addrStrings = append(addrStrings, ipnet.IP.String()) 59 | } 60 | } 61 | } 62 | return addrStrings, nil 63 | } 64 | 65 | // IsPrivateIP returns whether 'ip' is in private network space. 66 | func IsPrivateIP(ip net.IP) bool { 67 | if ip.IsLoopback() { 68 | return true 69 | } 70 | for _, block := range privateIPBlocks { 71 | if block.Contains(ip) { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | // BuildUserEntries scans under /proc/%pid/FD/. 79 | func BuildUserEntries() (UserEnts, error) { 80 | root := os.Getenv("PROC_ROOT") 81 | if root == "" { 82 | root = "/proc" 83 | } 84 | dir, err := ioutil.ReadDir(root) 85 | if err != nil { 86 | return nil, err 87 | } 88 | 89 | userEnts := make(UserEnts, 0) 90 | for _, d := range dir { 91 | // find only """ directory 92 | if !d.IsDir() { 93 | continue 94 | } 95 | pid, err := strconv.Atoi(d.Name()) 96 | if err != nil { 97 | continue 98 | } 99 | 100 | // skip self process 101 | if pid == os.Getpid() { 102 | continue 103 | } 104 | 105 | pidDir := filepath.Join(root, d.Name()) 106 | fdDir := filepath.Join(pidDir, "fd") 107 | 108 | // exists FD? 109 | fi, err := os.Stat(fdDir) 110 | switch { 111 | case err != nil: 112 | return nil, fmt.Errorf("stat %s: %v", fdDir, err) 113 | case !fi.IsDir(): 114 | continue 115 | } 116 | 117 | dir2, err := ioutil.ReadDir(fdDir) 118 | if err != nil { 119 | pathErr := err.(*os.PathError) 120 | errno := pathErr.Err.(syscall.Errno) 121 | if errno == syscall.EACCES { 122 | // ignore "open: permission denied" 123 | continue 124 | } 125 | return nil, fmt.Errorf("readdir %s: %v", errno, err) 126 | } 127 | 128 | var stat *procStat 129 | 130 | for _, d2 := range dir2 { 131 | fd, err := strconv.Atoi(d2.Name()) 132 | if err != nil { 133 | continue 134 | } 135 | fdpath := filepath.Join(fdDir, d2.Name()) 136 | lnk, err := os.Readlink(fdpath) 137 | if err != nil { 138 | pathErr := err.(*os.PathError) 139 | errno := pathErr.Err.(syscall.Errno) 140 | if errno == syscall.ENOENT { 141 | // ignore "readlink: no such file or directory" 142 | // because fdpath is disappear depending on timing 143 | log.Printf("%v\n", pathErr) 144 | continue 145 | } 146 | return nil, fmt.Errorf("readlink %s: %v", fdpath, err) 147 | } 148 | ino, err := parseSocketInode(lnk) 149 | if err != nil { 150 | return nil, err 151 | } 152 | if ino == 0 { 153 | continue 154 | } 155 | 156 | if stat == nil { 157 | stat, err = parseProcStat(root, pid) 158 | if err != nil { 159 | return nil, err 160 | } 161 | } 162 | 163 | userEnts[ino] = &UserEnt{ 164 | Inode: ino, 165 | FD: fd, 166 | Pid: pid, 167 | PName: stat.Pname, 168 | PPid: stat.Ppid, 169 | PGid: stat.Pgrp, 170 | } 171 | } 172 | } 173 | return userEnts, nil 174 | } 175 | 176 | type procStat struct { 177 | Pname string // process name 178 | Ppid int // parent process id 179 | Pgrp int // process group id 180 | } 181 | 182 | func parseProcStat(root string, pid int) (*procStat, error) { 183 | stat := fmt.Sprintf("%s/%d/stat", root, pid) 184 | f, err := os.Open(stat) 185 | if err != nil { 186 | return nil, fmt.Errorf("could not open %s: %w", stat, err) 187 | } 188 | defer f.Close() 189 | 190 | var ( 191 | pid2 int 192 | comm string 193 | state string 194 | ppid int 195 | pgrp int 196 | ) 197 | if _, err := fmt.Fscan(f, &pid2, &comm, &state, &ppid, &pgrp); err != nil { 198 | return nil, fmt.Errorf("could not scan '%s': %w", stat, err) 199 | } 200 | 201 | var pname string 202 | // workaround: Sscanf return io.ErrUnexpectedEOF without knowing why. 203 | if _, err := fmt.Sscanf(comm, "(%s)", &pname); err != nil && err != io.ErrUnexpectedEOF { 204 | return nil, fmt.Errorf("could not scan '%s': %w", comm, err) 205 | } 206 | 207 | return &procStat{ 208 | Pname: strings.TrimRight(pname, ")"), 209 | Ppid: ppid, 210 | Pgrp: pgrp, 211 | }, nil 212 | } 213 | 214 | func parseSocketInode(lnk string) (uint32, error) { 215 | const pattern = "socket:[" 216 | ind := strings.Index(lnk, pattern) 217 | if ind == -1 { 218 | return 0, nil 219 | } 220 | var ino uint32 221 | n, err := fmt.Sscanf(lnk, "socket:[%d]", &ino) 222 | if err != nil { 223 | return 0, err 224 | } 225 | if n != 1 { 226 | return 0, fmt.Errorf("'%s' should be pattern '[socket:\\%d]'", lnk, n) 227 | } 228 | return ino, nil 229 | } 230 | --------------------------------------------------------------------------------