github.com/elastic/gosigar@v0.14.3/sys/linux/inetdiag.go (about) 1 // +build linux 2 3 package linux 4 5 import ( 6 "bytes" 7 "encoding/binary" 8 "fmt" 9 "hash/fnv" 10 "io" 11 "net" 12 "os" 13 "syscall" 14 "unsafe" 15 16 "github.com/elastic/gosigar/sys" 17 "github.com/pkg/errors" 18 ) 19 20 // Enums / Constants 21 22 const ( 23 // AllTCPStates is a flag to request all sockets in any TCP state. 24 AllTCPStates = ^uint32(0) 25 26 // TCPDIAG_GETSOCK is the netlink message type for requesting TCP diag data. 27 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L7 28 TCPDIAG_GETSOCK = 18 29 30 // SOCK_DIAG_BY_FAMILY is the netlink message type for requestion socket 31 // diag data by family. This is newer and can be used with inet_diag_req_v2. 32 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/sock_diag.h#L6 33 SOCK_DIAG_BY_FAMILY = 20 34 ) 35 36 // AddressFamily is the address family of the socket. 37 type AddressFamily uint8 38 39 // https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/linux/socket.h#L159 40 const ( 41 AF_INET AddressFamily = 2 42 AF_INET6 = 10 43 ) 44 45 var addressFamilyNames = map[AddressFamily]string{ 46 AF_INET: "ipv4", 47 AF_INET6: "ipv6", 48 } 49 50 func (af AddressFamily) String() string { 51 if fam, found := addressFamilyNames[af]; found { 52 return fam 53 } 54 return fmt.Sprintf("UNKNOWN (%d)", af) 55 } 56 57 // TCPState represents the state of a TCP connection. 58 type TCPState uint8 59 60 // https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/net/tcp_states.h#L16 61 const ( 62 TCP_ESTABLISHED TCPState = iota + 1 63 TCP_SYN_SENT 64 TCP_SYN_RECV 65 TCP_FIN_WAIT1 66 TCP_FIN_WAIT2 67 TCP_TIME_WAIT 68 TCP_CLOSE 69 TCP_CLOSE_WAIT 70 TCP_LAST_ACK 71 TCP_LISTEN 72 TCP_CLOSING /* Now a valid state */ 73 ) 74 75 var tcpStateNames = map[TCPState]string{ 76 TCP_ESTABLISHED: "ESTAB", 77 TCP_SYN_SENT: "SYN-SENT", 78 TCP_SYN_RECV: "SYN-RECV", 79 TCP_FIN_WAIT1: "FIN-WAIT-1", 80 TCP_FIN_WAIT2: "FIN-WAIT-2", 81 TCP_TIME_WAIT: "TIME-WAIT", 82 TCP_CLOSE: "UNCONN", 83 TCP_CLOSE_WAIT: "CLOSE-WAIT", 84 TCP_LAST_ACK: "LAST-ACK", 85 TCP_LISTEN: "LISTEN", 86 TCP_CLOSING: "CLOSING", 87 } 88 89 func (s TCPState) String() string { 90 if state, found := tcpStateNames[s]; found { 91 return state 92 } 93 return "UNKNOWN" 94 } 95 96 // Extensions that can be used in the InetDiagReqV2 request to ask for 97 // additional data. 98 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L103 99 const ( 100 INET_DIAG_NONE = 0 101 INET_DIAG_MEMINFO = 1 << iota 102 INET_DIAG_INFO 103 INET_DIAG_VEGASINFO 104 INET_DIAG_CONG 105 INET_DIAG_TOS 106 INET_DIAG_TCLASS 107 INET_DIAG_SKMEMINFO 108 INET_DIAG_SHUTDOWN 109 INET_DIAG_DCTCPINFO 110 INET_DIAG_PROTOCOL /* response attribute only */ 111 INET_DIAG_SKV6ONLY 112 INET_DIAG_LOCALS 113 INET_DIAG_PEERS 114 INET_DIAG_PAD 115 INET_DIAG_MARK 116 ) 117 118 var ( 119 byteOrder = sys.GetEndian() 120 ) 121 122 // NetlinkInetDiag sends the given netlink request parses the responses with the 123 // assumption that they are inet_diag_msgs. This will allocate a temporary 124 // buffer for reading from the socket whose size will be the length of a page 125 // (usually 32k). Use NetlinkInetDiagWithBuf if you want to provide your own 126 // buffer. 127 func NetlinkInetDiag(request syscall.NetlinkMessage) ([]*InetDiagMsg, error) { 128 return NetlinkInetDiagWithBuf(request, nil, nil) 129 } 130 131 // NetlinkInetDiagWithBuf sends the given netlink request parses the responses 132 // with the assumption that they are inet_diag_msgs. readBuf will be used to 133 // hold the raw data read from the socket. If the length is not large enough to 134 // hold the socket contents the data will be truncated. If readBuf is nil then a 135 // temporary buffer will be allocated for each invocation. The resp writer, if 136 // non-nil, will receive a copy of all bytes read (this is useful for 137 // debugging). 138 func NetlinkInetDiagWithBuf(request syscall.NetlinkMessage, readBuf []byte, resp io.Writer) ([]*InetDiagMsg, error) { 139 s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_INET_DIAG) 140 if err != nil { 141 return nil, err 142 } 143 defer syscall.Close(s) 144 145 lsa := &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK} 146 if err := syscall.Sendto(s, serialize(request), 0, lsa); err != nil { 147 return nil, err 148 } 149 150 if len(readBuf) == 0 { 151 // Default size used in libnl. 152 readBuf = make([]byte, os.Getpagesize()) 153 } 154 155 var inetDiagMsgs []*InetDiagMsg 156 done: 157 for { 158 buf := readBuf 159 nr, _, err := syscall.Recvfrom(s, buf, 0) 160 if err != nil { 161 return nil, err 162 } 163 if nr < syscall.NLMSG_HDRLEN { 164 return nil, syscall.EINVAL 165 } 166 167 buf = buf[:nr] 168 169 // Dump raw data for inspection purposes. 170 if resp != nil { 171 if _, err := resp.Write(buf); err != nil { 172 return nil, err 173 } 174 } 175 176 msgs, err := syscall.ParseNetlinkMessage(buf) 177 if err != nil { 178 return nil, err 179 } 180 181 for _, m := range msgs { 182 if m.Header.Type == syscall.NLMSG_DONE { 183 break done 184 } 185 if m.Header.Type == syscall.NLMSG_ERROR { 186 return nil, ParseNetlinkError(m.Data) 187 } 188 189 inetDiagMsg, err := ParseInetDiagMsg(m.Data) 190 if err != nil { 191 return nil, err 192 } 193 inetDiagMsgs = append(inetDiagMsgs, inetDiagMsg) 194 } 195 } 196 return inetDiagMsgs, nil 197 } 198 199 func serialize(msg syscall.NetlinkMessage) []byte { 200 msg.Header.Len = uint32(syscall.SizeofNlMsghdr + len(msg.Data)) 201 b := make([]byte, msg.Header.Len) 202 byteOrder.PutUint32(b[0:4], msg.Header.Len) 203 byteOrder.PutUint16(b[4:6], msg.Header.Type) 204 byteOrder.PutUint16(b[6:8], msg.Header.Flags) 205 byteOrder.PutUint32(b[8:12], msg.Header.Seq) 206 byteOrder.PutUint32(b[12:16], msg.Header.Pid) 207 copy(b[16:], msg.Data) 208 return b 209 } 210 211 // Request messages. 212 213 var sizeofInetDiagReq = int(unsafe.Sizeof(InetDiagReq{})) 214 215 // InetDiagReq (inet_diag_req) is used to request diagnostic data from older 216 // kernels. 217 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L25 218 type InetDiagReq struct { 219 Family uint8 220 SrcLen uint8 221 DstLen uint8 222 Ext uint8 223 ID InetDiagSockID 224 States uint32 // States to dump. 225 DBs uint32 // Tables to dump. 226 } 227 228 func (r InetDiagReq) toWireFormat() []byte { 229 buf := bytes.NewBuffer(make([]byte, sizeofInetDiagReq)) 230 buf.Reset() 231 if err := binary.Write(buf, byteOrder, r); err != nil { 232 // This never returns an error. 233 panic(err) 234 } 235 return buf.Bytes() 236 } 237 238 // NewInetDiagReq returns a new NetlinkMessage whose payload is an InetDiagReq. 239 // Callers should set their own sequence number in the returned message header. 240 func NewInetDiagReq() syscall.NetlinkMessage { 241 hdr := syscall.NlMsghdr{ 242 Type: uint16(TCPDIAG_GETSOCK), 243 Flags: uint16(syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST), 244 Pid: uint32(0), 245 } 246 req := InetDiagReq{ 247 Family: uint8(AF_INET), // This returns both ipv4 and ipv6. 248 States: AllTCPStates, 249 } 250 251 return syscall.NetlinkMessage{Header: hdr, Data: req.toWireFormat()} 252 } 253 254 // V2 Request 255 256 var sizeofInetDiagReqV2 = int(unsafe.Sizeof(InetDiagReqV2{})) 257 258 // InetDiagReqV2 (inet_diag_req_v2) is used to request diagnostic data. 259 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L37 260 type InetDiagReqV2 struct { 261 Family uint8 262 Protocol uint8 263 Ext uint8 264 Pad uint8 265 States uint32 266 ID InetDiagSockID 267 } 268 269 func (r InetDiagReqV2) toWireFormat() []byte { 270 buf := bytes.NewBuffer(make([]byte, sizeofInetDiagReqV2)) 271 buf.Reset() 272 if err := binary.Write(buf, byteOrder, r); err != nil { 273 // This never returns an error. 274 panic(err) 275 } 276 return buf.Bytes() 277 } 278 279 // NewInetDiagReqV2 returns a new NetlinkMessage whose payload is an 280 // InetDiagReqV2. Callers should set their own sequence number in the returned 281 // message header. 282 func NewInetDiagReqV2(af AddressFamily) syscall.NetlinkMessage { 283 hdr := syscall.NlMsghdr{ 284 Type: uint16(SOCK_DIAG_BY_FAMILY), 285 Flags: uint16(syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST), 286 Pid: uint32(0), 287 } 288 req := InetDiagReqV2{ 289 Family: uint8(af), 290 Protocol: syscall.IPPROTO_TCP, 291 States: AllTCPStates, 292 } 293 294 return syscall.NetlinkMessage{Header: hdr, Data: req.toWireFormat()} 295 } 296 297 // Response messages. 298 299 // InetDiagMsg (inet_diag_msg) is the base info structure. It contains socket 300 // identity (addrs/ports/cookie) and the information shown by netstat. 301 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L86 302 type InetDiagMsg struct { 303 Family uint8 // Address family. 304 State uint8 // TCP State 305 Timer uint8 306 Retrans uint8 307 308 ID InetDiagSockID 309 310 Expires uint32 311 RQueue uint32 // Recv-Q 312 WQueue uint32 // Send-Q 313 UID uint32 // UID 314 Inode uint32 // Inode of socket. 315 } 316 317 // ParseInetDiagMsg parse an InetDiagMsg from a byte slice. It assumes the 318 // InetDiagMsg starts at the beginning of b. Invoke this method to parse the 319 // payload of a netlink response. 320 func ParseInetDiagMsg(b []byte) (*InetDiagMsg, error) { 321 r := bytes.NewReader(b) 322 inetDiagMsg := &InetDiagMsg{} 323 err := binary.Read(r, byteOrder, inetDiagMsg) 324 if err != nil { 325 return nil, errors.Wrap(err, "failed to unmarshal inet_diag_msg") 326 } 327 return inetDiagMsg, nil 328 } 329 330 // SrcPort returns the source (local) port. 331 func (m InetDiagMsg) SrcPort() int { return int(binary.BigEndian.Uint16(m.ID.SPort[:])) } 332 333 // DstPort returns the destination (remote) port. 334 func (m InetDiagMsg) DstPort() int { return int(binary.BigEndian.Uint16(m.ID.DPort[:])) } 335 336 // SrcIP returns the source (local) IP. 337 func (m InetDiagMsg) SrcIP() net.IP { return ip(m.ID.Src, AddressFamily(m.Family)) } 338 339 // DstIP returns the destination (remote) IP. 340 func (m InetDiagMsg) DstIP() net.IP { return ip(m.ID.Dst, AddressFamily(m.Family)) } 341 342 func (m InetDiagMsg) srcIPBytes() []byte { return ipBytes(m.ID.Src, AddressFamily(m.Family)) } 343 func (m InetDiagMsg) dstIPBytes() []byte { return ipBytes(m.ID.Dst, AddressFamily(m.Family)) } 344 345 func ip(data [16]byte, af AddressFamily) net.IP { 346 if af == AF_INET { 347 return net.IPv4(data[0], data[1], data[2], data[3]) 348 } 349 return net.IP(data[:]) 350 } 351 352 func ipBytes(data [16]byte, af AddressFamily) []byte { 353 if af == AF_INET { 354 return data[:4] 355 } 356 357 return data[:] 358 } 359 360 // FastHash returns a hash calculated using FNV-1 of the source and destination 361 // addresses. 362 func (m *InetDiagMsg) FastHash() uint64 { 363 // Hash using FNV-1 algorithm. 364 h := fnv.New64() 365 h.Write(m.srcIPBytes()) // Must trim non-zero garbage from ipv4 buffers. 366 h.Write(m.dstIPBytes()) 367 h.Write(m.ID.SPort[:]) 368 h.Write(m.ID.DPort[:]) 369 return h.Sum64() 370 } 371 372 // InetDiagSockID (inet_diag_sockid) contains the socket identity. 373 // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L13 374 type InetDiagSockID struct { 375 SPort [2]byte // Source port (big-endian). 376 DPort [2]byte // Destination port (big-endian). 377 Src [16]byte // Source IP 378 Dst [16]byte // Destination IP 379 If uint32 380 Cookie [2]uint32 381 }