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  }