From 171a3c9e31dde9688c154ba94be6cd5d8a78bf64 Mon Sep 17 00:00:00 2001 From: Andrew Kroh Date: Thu, 22 Dec 2016 02:44:57 -0500 Subject: [PATCH] Added utility code for interfacing with linux NETLINK_INET_DIAG. (#60) The NETLINK_INET_DIAG feature was introduced in Linux 2.6.14. It provides a dump of all sockets. --- .appveyor.yml | 6 +- .gitignore | 2 + .travis.yml | 2 +- CHANGELOG.md | 1 + examples/ss/ss.go | 110 +++++ sys/linux/inetdiag.go | 376 ++++++++++++++++++ sys/linux/inetdiag_test.go | 79 ++++ sys/linux/netlink.go | 107 +++++ sys/linux/netlink_test.go | 19 + sys/linux/sysconf_cgo.go | 1 + sys/linux/sysconf_nocgo.go | 1 + ...t-dump-rhel6-2.6.32-504.3.3.el6.x86_64.bin | Bin 0 -> 1076 bytes 12 files changed, 700 insertions(+), 4 deletions(-) create mode 100644 examples/ss/ss.go create mode 100644 sys/linux/inetdiag.go create mode 100644 sys/linux/inetdiag_test.go create mode 100644 sys/linux/netlink.go create mode 100644 sys/linux/netlink_test.go create mode 100644 sys/linux/testdata/inet-dump-rhel6-2.6.32-504.3.3.el6.x86_64.bin diff --git a/.appveyor.yml b/.appveyor.yml index daf2e2aff..bc473d10f 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -6,8 +6,8 @@ os: Windows Server 2012 R2 # Environment variables environment: - GOVERSION: 1.7.3 - GOROOT: c:\go1.7.3 + GOVERSION: 1.7.4 + GOROOT: c:\go1.7.4 GOPATH: c:\gopath # Custom clone folder (variables are not expanded here). @@ -17,7 +17,7 @@ clone_folder: c:\gopath\src\github.com\elastic\gosigar cache: - C:\ProgramData\chocolatey\bin -> .appveyor.yml - C:\ProgramData\chocolatey\lib -> .appveyor.yml -- C:\go1.7.3 -> .appveyor.yml +- C:\go1.7.4 -> .appveyor.yml - C:\tools\mingw64 -> .appveyor.yml # Scripts that run after cloning repository diff --git a/.gitignore b/.gitignore index 1bbaa4057..8b003f8c0 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,8 @@ examples/free/free examples/free/free.exe examples/ps/ps examples/ps/ps.exe +examples/ss/ss +examples/ss/ss.exe examples/uptime/uptime examples/uptime/uptime.exe diff --git a/.travis.yml b/.travis.yml index 2255930d5..d8d385c97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ os: - osx go: - - 1.7.3 + - 1.7.4 env: global: diff --git a/CHANGELOG.md b/CHANGELOG.md index 780680eed..3cc18f0cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - Added `Swap` implementation for Windows based on page file metrics. #55 - Added support to `github.com/gosigar/sys/windows` for querying and enabling privileges in a process token. +- Added utility code for interfacing with linux NETLINK_INET_DIAG. #60 ### Changed - Changed several `OpenProcess` calls on Windows to request the lowest possible diff --git a/examples/ss/ss.go b/examples/ss/ss.go new file mode 100644 index 000000000..d79546d6c --- /dev/null +++ b/examples/ss/ss.go @@ -0,0 +1,110 @@ +// +build linux + +package main + +import ( + "flag" + "fmt" + "io" + "os" + "strings" + "syscall" + "text/tabwriter" + "time" + + log "github.com/Sirupsen/logrus" + "github.com/elastic/gosigar/sys/linux" +) + +var ( + fs = flag.NewFlagSet("ss", flag.ExitOnError) + debug = fs.Bool("d", false, "enable debug output to stderr") + ipv6 = fs.Bool("6", false, "display only IP version 6 sockets") + v1 = fs.Bool("v1", false, "send inet_diag_msg v1 instead of v2") + diag = fs.String("diag", "", "dump raw information about TCP sockets to FILE") +) + +func enableLogger() { + log.SetOutput(os.Stderr) + log.SetLevel(log.DebugLevel) + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + TimestampFormat: time.RFC3339Nano, + }) +} + +func main() { + fs.Parse(os.Args[1:]) + + if *debug { + enableLogger() + } + + if err := sockets(); err != nil { + fmt.Fprintf(os.Stderr, "error: %v\n", err) + os.Exit(1) + } +} + +func sockets() error { + // Set address family based on flags. The requested address family only + // works with inet_diag_req_v2. v1 returns all tcp sockets. + af := linux.AF_INET + if *ipv6 { + af = linux.AF_INET6 + } + + // For debug purposes allow for sending either inet_diag_req and inet_diag_req_v2. + var req syscall.NetlinkMessage + if *v1 { + req = linux.NewInetDiagReq() + } else { + req = linux.NewInetDiagReqV2(af) + } + + // Write netlink response to a file for further analysis or for writing + // tests cases. + var diagWriter io.Writer + if *diag != "" { + f, err := os.OpenFile(*diag, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0600) + if err != nil { + return err + } + defer f.Close() + diagWriter = f + } + + log.Debugln("sending netlink request") + msgs, err := linux.NetlinkInetDiagWithBuf(req, nil, diagWriter) + if err != nil { + return err + } + log.Debugf("received %d inet_diag_msg responses", len(msgs)) + + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, strings.Join([]string{ + "State", + "Recv-Q", + "Send-Q", + "Local Address:Port", + "Remote Address:Port", + "UID", + "Inode", + "PID/Program", + }, "\t")) + defer w.Flush() + + for _, diag := range msgs { + // XXX: A real implementation of ss would find the process holding + // inode of the socket. It would read /proc//fd and find all sockets. + pidProgram := "not implemented" + + fmt.Fprintf(w, "%v\t%v\t%v\t%v:%v\t%v:%v\t%v\t%v\t%v\n", + linux.TCPState(diag.State), diag.RQueue, diag.WQueue, + diag.SrcIP().String(), diag.SrcPort(), + diag.DstIP().String(), diag.DstPort(), + diag.UID, diag.Inode, pidProgram) + } + + return nil +} diff --git a/sys/linux/inetdiag.go b/sys/linux/inetdiag.go new file mode 100644 index 000000000..a2851d233 --- /dev/null +++ b/sys/linux/inetdiag.go @@ -0,0 +1,376 @@ +// +build linux + +package linux + +import ( + "bytes" + "encoding/binary" + "fmt" + "hash/fnv" + "io" + "net" + "os" + "syscall" + "unsafe" + + "github.com/pkg/errors" +) + +// Enums / Constants + +const ( + // AllTCPStates is a flag to request all sockets in any TCP state. + AllTCPStates = ^uint32(0) + + // TCPDIAG_GETSOCK is the netlink message type for requesting TCP diag data. + // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L7 + TCPDIAG_GETSOCK = 18 + + // SOCK_DIAG_BY_FAMILY is the netlink message type for requestion socket + // diag data by family. This is newer and can be used with inet_diag_req_v2. + // https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/sock_diag.h#L6 + SOCK_DIAG_BY_FAMILY = 20 +) + +// AddressFamily is the address family of the socket. +type AddressFamily uint8 + +// https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/linux/socket.h#L159 +const ( + AF_INET AddressFamily = 2 + AF_INET6 = 10 +) + +var addressFamilyNames = map[AddressFamily]string{ + AF_INET: "ipv4", + AF_INET6: "ipv6", +} + +func (af AddressFamily) String() string { + if fam, found := addressFamilyNames[af]; found { + return fam + } + return fmt.Sprintf("UNKNOWN (%d)", af) +} + +// TCPState represents the state of a TCP connection. +type TCPState uint8 + +// https://github.com/torvalds/linux/blob/5924bbecd0267d87c24110cbe2041b5075173a25/include/net/tcp_states.h#L16 +const ( + TCP_ESTABLISHED TCPState = iota + 1 + TCP_SYN_SENT + TCP_SYN_RECV + TCP_FIN_WAIT1 + TCP_FIN_WAIT2 + TCP_TIME_WAIT + TCP_CLOSE + TCP_CLOSE_WAIT + TCP_LAST_ACK + TCP_LISTEN + TCP_CLOSING /* Now a valid state */ +) + +var tcpStateNames = map[TCPState]string{ + TCP_ESTABLISHED: "ESTAB", + TCP_SYN_SENT: "SYN-SENT", + TCP_SYN_RECV: "SYN-RECV", + TCP_FIN_WAIT1: "FIN-WAIT-1", + TCP_FIN_WAIT2: "FIN-WAIT-2", + TCP_TIME_WAIT: "TIME-WAIT", + TCP_CLOSE: "UNCONN", + TCP_CLOSE_WAIT: "CLOSE-WAIT", + TCP_LAST_ACK: "LAST-ACK", + TCP_LISTEN: "LISTEN", + TCP_CLOSING: "CLOSING", +} + +func (s TCPState) String() string { + if state, found := tcpStateNames[s]; found { + return state + } + return "UNKNOWN" +} + +// Extensions that can be used in the InetDiagReqV2 request to ask for +// additional data. +// https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L103 +const ( + INET_DIAG_NONE = 0 + INET_DIAG_MEMINFO = 1 << iota + INET_DIAG_INFO + INET_DIAG_VEGASINFO + INET_DIAG_CONG + INET_DIAG_TOS + INET_DIAG_TCLASS + INET_DIAG_SKMEMINFO + INET_DIAG_SHUTDOWN + INET_DIAG_DCTCPINFO + INET_DIAG_PROTOCOL /* response attribute only */ + INET_DIAG_SKV6ONLY + INET_DIAG_LOCALS + INET_DIAG_PEERS + INET_DIAG_PAD + INET_DIAG_MARK +) + +// NetlinkInetDiag sends the given netlink request parses the responses with the +// assumption that they are inet_diag_msgs. This will allocate a temporary +// buffer for reading from the socket whose size will be the length of a page +// (usually 32k). Use NetlinkInetDiagWithBuf if you want to provide your own +// buffer. +func NetlinkInetDiag(request syscall.NetlinkMessage) ([]*InetDiagMsg, error) { + return NetlinkInetDiagWithBuf(request, nil, nil) +} + +// NetlinkInetDiagWithBuf sends the given netlink request parses the responses +// with the assumption that they are inet_diag_msgs. readBuf will be used to +// hold the raw data read from the socket. If the length is not large enough to +// hold the socket contents the data will be truncated. If readBuf is nil then a +// temporary buffer will be allocated for each invocation. The resp writer, if +// non-nil, will receive a copy of all bytes read (this is useful for +// debugging). +func NetlinkInetDiagWithBuf(request syscall.NetlinkMessage, readBuf []byte, resp io.Writer) ([]*InetDiagMsg, error) { + s, err := syscall.Socket(syscall.AF_NETLINK, syscall.SOCK_RAW, syscall.NETLINK_INET_DIAG) + if err != nil { + return nil, err + } + defer syscall.Close(s) + + lsa := &syscall.SockaddrNetlink{Family: syscall.AF_NETLINK} + if err := syscall.Sendto(s, serialize(request), 0, lsa); err != nil { + return nil, err + } + + if len(readBuf) == 0 { + // Default size used in libnl. + readBuf = make([]byte, os.Getpagesize()) + } + + var inetDiagMsgs []*InetDiagMsg +done: + for { + buf := readBuf + nr, _, err := syscall.Recvfrom(s, buf, 0) + if err != nil { + return nil, err + } + if nr < syscall.NLMSG_HDRLEN { + return nil, syscall.EINVAL + } + + buf = buf[:nr] + + // Dump raw data for inspection purposes. + if resp != nil { + if _, err := resp.Write(buf); err != nil { + return nil, err + } + } + + msgs, err := syscall.ParseNetlinkMessage(buf) + if err != nil { + return nil, err + } + + for _, m := range msgs { + if m.Header.Type == syscall.NLMSG_DONE { + break done + } + if m.Header.Type == syscall.NLMSG_ERROR { + return nil, ParseNetlinkError(m.Data) + } + + inetDiagMsg, err := ParseInetDiagMsg(m.Data) + if err != nil { + return nil, err + } + inetDiagMsgs = append(inetDiagMsgs, inetDiagMsg) + } + } + return inetDiagMsgs, nil +} + +func serialize(msg syscall.NetlinkMessage) []byte { + msg.Header.Len = uint32(syscall.SizeofNlMsghdr + len(msg.Data)) + b := make([]byte, msg.Header.Len) + binary.LittleEndian.PutUint32(b[0:4], msg.Header.Len) + binary.LittleEndian.PutUint16(b[4:6], msg.Header.Type) + binary.LittleEndian.PutUint16(b[6:8], msg.Header.Flags) + binary.LittleEndian.PutUint32(b[8:12], msg.Header.Seq) + binary.LittleEndian.PutUint32(b[12:16], msg.Header.Pid) + copy(b[16:], msg.Data) + return b +} + +// Request messages. + +var sizeofInetDiagReq = int(unsafe.Sizeof(InetDiagReq{})) + +// InetDiagReq (inet_diag_req) is used to request diagnostic data from older +// kernels. +// https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L25 +type InetDiagReq struct { + Family uint8 + SrcLen uint8 + DstLen uint8 + Ext uint8 + ID InetDiagSockID + States uint32 // States to dump. + DBs uint32 // Tables to dump. +} + +func (r InetDiagReq) toWireFormat() []byte { + buf := bytes.NewBuffer(make([]byte, sizeofInetDiagReq)) + buf.Reset() + if err := binary.Write(buf, binary.LittleEndian, r); err != nil { + // This never returns an error. + panic(err) + } + return buf.Bytes() +} + +// NewInetDiagReq returns a new NetlinkMessage whose payload is an InetDiagReq. +// Callers should set their own sequence number in the returned message header. +func NewInetDiagReq() syscall.NetlinkMessage { + hdr := syscall.NlMsghdr{ + Type: uint16(TCPDIAG_GETSOCK), + Flags: uint16(syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST), + Pid: uint32(0), + } + req := InetDiagReq{ + Family: uint8(AF_INET), // This returns both ipv4 and ipv6. + States: AllTCPStates, + } + + return syscall.NetlinkMessage{Header: hdr, Data: req.toWireFormat()} +} + +// V2 Request + +var sizeofInetDiagReqV2 = int(unsafe.Sizeof(InetDiagReqV2{})) + +// InetDiagReqV2 (inet_diag_req_v2) is used to request diagnostic data. +// https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L37 +type InetDiagReqV2 struct { + Family uint8 + Protocol uint8 + Ext uint8 + Pad uint8 + States uint32 + ID InetDiagSockID +} + +func (r InetDiagReqV2) toWireFormat() []byte { + buf := bytes.NewBuffer(make([]byte, sizeofInetDiagReqV2)) + buf.Reset() + if err := binary.Write(buf, binary.LittleEndian, r); err != nil { + // This never returns an error. + panic(err) + } + return buf.Bytes() +} + +// NewInetDiagReqV2 returns a new NetlinkMessage whose payload is an +// InetDiagReqV2. Callers should set their own sequence number in the returned +// message header. +func NewInetDiagReqV2(af AddressFamily) syscall.NetlinkMessage { + hdr := syscall.NlMsghdr{ + Type: uint16(SOCK_DIAG_BY_FAMILY), + Flags: uint16(syscall.NLM_F_DUMP | syscall.NLM_F_REQUEST), + Pid: uint32(0), + } + req := InetDiagReqV2{ + Family: uint8(af), + Protocol: syscall.IPPROTO_TCP, + States: AllTCPStates, + } + + return syscall.NetlinkMessage{Header: hdr, Data: req.toWireFormat()} +} + +// Response messages. + +// InetDiagMsg (inet_diag_msg) is the base info structure. It contains socket +// identity (addrs/ports/cookie) and the information shown by netstat. +// https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L86 +type InetDiagMsg struct { + Family uint8 // Address family. + State uint8 // TCP State + Timer uint8 + Retrans uint8 + + ID InetDiagSockID + + Expires uint32 + RQueue uint32 // Recv-Q + WQueue uint32 // Send-Q + UID uint32 // UID + Inode uint32 // Inode of socket. +} + +// ParseInetDiagMsg parse an InetDiagMsg from a byte slice. It assumes the +// InetDiagMsg starts at the beginning of b. Invoke this method to parse the +// payload of a netlink response. +func ParseInetDiagMsg(b []byte) (*InetDiagMsg, error) { + r := bytes.NewReader(b) + inetDiagMsg := &InetDiagMsg{} + err := binary.Read(r, binary.LittleEndian, inetDiagMsg) + if err != nil { + return nil, errors.Wrap(err, "failed to unmarshal inet_diag_msg") + } + return inetDiagMsg, nil +} + +// SrcPort returns the source (local) port. +func (m InetDiagMsg) SrcPort() int { return int(binary.BigEndian.Uint16(m.ID.SPort[:])) } + +// DstPort returns the destination (remote) port. +func (m InetDiagMsg) DstPort() int { return int(binary.BigEndian.Uint16(m.ID.DPort[:])) } + +// SrcIP returns the source (local) IP. +func (m InetDiagMsg) SrcIP() net.IP { return ip(m.ID.Src, AddressFamily(m.Family)) } + +// DstIP returns the destination (remote) IP. +func (m InetDiagMsg) DstIP() net.IP { return ip(m.ID.Dst, AddressFamily(m.Family)) } + +func (m InetDiagMsg) srcIPBytes() []byte { return ipBytes(m.ID.Src, AddressFamily(m.Family)) } +func (m InetDiagMsg) dstIPBytes() []byte { return ipBytes(m.ID.Dst, AddressFamily(m.Family)) } + +func ip(data [16]byte, af AddressFamily) net.IP { + if af == AF_INET { + return net.IPv4(data[0], data[1], data[2], data[3]) + } + return net.IP(data[:]) +} + +func ipBytes(data [16]byte, af AddressFamily) []byte { + if af == AF_INET { + return data[:4] + } + + return data[:] +} + +// FastHash returns a hash calculated using FNV-1 of the source and destination +// addresses. +func (m *InetDiagMsg) FastHash() uint64 { + // Hash using FNV-1 algorithm. + h := fnv.New64() + h.Write(m.srcIPBytes()) // Must trim non-zero garbage from ipv4 buffers. + h.Write(m.dstIPBytes()) + h.Write(m.ID.SPort[:]) + h.Write(m.ID.DPort[:]) + return h.Sum64() +} + +// InetDiagSockID (inet_diag_sockid) contains the socket identity. +// https://github.com/torvalds/linux/blob/v4.0/include/uapi/linux/inet_diag.h#L13 +type InetDiagSockID struct { + SPort [2]byte // Source port (big-endian). + DPort [2]byte // Destination port (big-endian). + Src [16]byte // Source IP + Dst [16]byte // Destination IP + If uint32 + Cookie [2]uint32 +} diff --git a/sys/linux/inetdiag_test.go b/sys/linux/inetdiag_test.go new file mode 100644 index 000000000..d4ff64b5e --- /dev/null +++ b/sys/linux/inetdiag_test.go @@ -0,0 +1,79 @@ +// +build linux + +package linux + +import ( + "bytes" + "encoding/hex" + "io/ioutil" + "syscall" + "testing" + + "github.com/stretchr/testify/assert" +) + +// TestParseInetDiagMsgs reads netlink messages stored in a file (these can be +// captured with ss -diag ). +func TestParseInetDiagMsgs(t *testing.T) { + data, err := ioutil.ReadFile("testdata/inet-dump-rhel6-2.6.32-504.3.3.el6.x86_64.bin") + if err != nil { + t.Fatal(err) + } + + t.Log("Netlink data length: ", len(data)) + netlinkMsgs, err := syscall.ParseNetlinkMessage(data) + if err != nil { + t.Fatal(err) + } + + t.Logf("Parsed %d netlink messages", len(netlinkMsgs)) + done := false + for _, m := range netlinkMsgs { + if m.Header.Type == syscall.NLMSG_DONE { + done = true + break + } + + inetDiagMsg, err := ParseInetDiagMsg(m.Data) + if err != nil { + t.Fatal("parse error", err) + } + + if inetDiagMsg.DstPort() == 0 { + assert.EqualValues(t, TCP_LISTEN, inetDiagMsg.State) + } else { + assert.EqualValues(t, TCP_ESTABLISHED, inetDiagMsg.State) + } + } + + assert.True(t, done, "missing NLMSG_DONE message") +} + +// TestNetlinkInetDiag sends a inet_diag_req to the kernel, checks for errors, +// and inspects the responses based on some invariant rules. +func TestNetlinkInetDiag(t *testing.T) { + req := NewInetDiagReq() + req.Header.Seq = 12345 + + dump := new(bytes.Buffer) + msgs, err := NetlinkInetDiagWithBuf(req, nil, dump) + if err != nil { + t.Fatal(err) + } + + t.Logf("Received %d messages decoded from %d bytes", len(msgs), dump.Len()) + for _, m := range msgs { + if m.Family != uint8(AF_INET) && m.Family != uint8(AF_INET6) { + t.Errorf("invalid Family (%v)", m.Family) + } + + if m.DstPort() == 0 { + assert.True(t, m.DstIP().IsUnspecified(), "dport is 0, dst ip should be unspecified") + assert.EqualValues(t, m.State, TCP_LISTEN) + } + } + + if t.Failed() { + t.Log("Raw newlink response:\n", hex.Dump(dump.Bytes())) + } +} diff --git a/sys/linux/netlink.go b/sys/linux/netlink.go new file mode 100644 index 000000000..3c50f4569 --- /dev/null +++ b/sys/linux/netlink.go @@ -0,0 +1,107 @@ +package linux + +import ( + "encoding/binary" + "errors" +) + +// ParseNetlinkError parses the errno from the data section of a +// syscall.NetlinkMessage. If netlinkData is less than 4 bytes an error +// describing the problem will be returned. +func ParseNetlinkError(netlinkData []byte) error { + if len(netlinkData) >= 4 { + errno := -binary.LittleEndian.Uint32(netlinkData[:4]) + return NetlinkErrno(errno) + } + return errors.New("received netlink error (data too short to read errno)") +} + +// NetlinkErrno represent the error code contained in a netlink message of +// type NLMSG_ERROR. +type NetlinkErrno uint32 + +// Netlink error codes. +const ( + NLE_SUCCESS NetlinkErrno = iota + NLE_FAILURE + NLE_INTR + NLE_BAD_SOCK + NLE_AGAIN + NLE_NOMEM + NLE_EXIST + NLE_INVAL + NLE_RANGE + NLE_MSGSIZE + NLE_OPNOTSUPP + NLE_AF_NOSUPPORT + NLE_OBJ_NOTFOUND + NLE_NOATTR + NLE_MISSING_ATTR + NLE_AF_MISMATCH + NLE_SEQ_MISMATCH + NLE_MSG_OVERFLOW + NLE_MSG_TRUNC + NLE_NOADDR + NLE_SRCRT_NOSUPPORT + NLE_MSG_TOOSHORT + NLE_MSGTYPE_NOSUPPORT + NLE_OBJ_MISMATCH + NLE_NOCACHE + NLE_BUSY + NLE_PROTO_MISMATCH + NLE_NOACCESS + NLE_PERM + NLE_PKTLOC_FILE + NLE_PARSE_ERR + NLE_NODEV + NLE_IMMUTABLE + NLE_DUMP_INTR + NLE_ATTRSIZE +) + +// https://github.com/thom311/libnl/blob/libnl3_2_28/lib/error.c +var netlinkErrorMsgs = map[NetlinkErrno]string{ + NLE_SUCCESS: "Success", + NLE_FAILURE: "Unspecific failure", + NLE_INTR: "Interrupted system call", + NLE_BAD_SOCK: "Bad socket", + NLE_AGAIN: "Try again", + NLE_NOMEM: "Out of memory", + NLE_EXIST: "Object exists", + NLE_INVAL: "Invalid input data or parameter", + NLE_RANGE: "Input data out of range", + NLE_MSGSIZE: "Message size not sufficient", + NLE_OPNOTSUPP: "Operation not supported", + NLE_AF_NOSUPPORT: "Address family not supported", + NLE_OBJ_NOTFOUND: "Object not found", + NLE_NOATTR: "Attribute not available", + NLE_MISSING_ATTR: "Missing attribute", + NLE_AF_MISMATCH: "Address family mismatch", + NLE_SEQ_MISMATCH: "Message sequence number mismatch", + NLE_MSG_OVERFLOW: "Kernel reported message overflow", + NLE_MSG_TRUNC: "Kernel reported truncated message", + NLE_NOADDR: "Invalid address for specified address family", + NLE_SRCRT_NOSUPPORT: "Source based routing not supported", + NLE_MSG_TOOSHORT: "Netlink message is too short", + NLE_MSGTYPE_NOSUPPORT: "Netlink message type is not supported", + NLE_OBJ_MISMATCH: "Object type does not match cache", + NLE_NOCACHE: "Unknown or invalid cache type", + NLE_BUSY: "Object busy", + NLE_PROTO_MISMATCH: "Protocol mismatch", + NLE_NOACCESS: "No Access", + NLE_PERM: "Operation not permitted", + NLE_PKTLOC_FILE: "Unable to open packet location file", + NLE_PARSE_ERR: "Unable to parse object", + NLE_NODEV: "No such device", + NLE_IMMUTABLE: "Immutable attribute", + NLE_DUMP_INTR: "Dump inconsistency detected, interrupted", + NLE_ATTRSIZE: "Attribute max length exceeded", +} + +func (e NetlinkErrno) Error() string { + if msg, found := netlinkErrorMsgs[e]; found { + return msg + } + + return netlinkErrorMsgs[NLE_FAILURE] +} diff --git a/sys/linux/netlink_test.go b/sys/linux/netlink_test.go new file mode 100644 index 000000000..7508496ff --- /dev/null +++ b/sys/linux/netlink_test.go @@ -0,0 +1,19 @@ +package linux + +import ( + "bytes" + "encoding/binary" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseNetlinkErrorDataTooShort(t *testing.T) { + assert.Error(t, ParseNetlinkError(nil), "too short") +} + +func TestParseNetlinkErrorErrno(t *testing.T) { + buf := new(bytes.Buffer) + binary.Write(buf, binary.LittleEndian, -1*int32(NLE_MSG_TOOSHORT)) + assert.Equal(t, ParseNetlinkError(buf.Bytes()), NLE_MSG_TOOSHORT) +} diff --git a/sys/linux/sysconf_cgo.go b/sys/linux/sysconf_cgo.go index 411e0ce31..e557a66e6 100644 --- a/sys/linux/sysconf_cgo.go +++ b/sys/linux/sysconf_cgo.go @@ -7,6 +7,7 @@ package linux */ import "C" +// GetClockTicks returns the number of click ticks in one jiffie. func GetClockTicks() int { return int(C.sysconf(C._SC_CLK_TCK)) } diff --git a/sys/linux/sysconf_nocgo.go b/sys/linux/sysconf_nocgo.go index 3b7896724..005e0c123 100644 --- a/sys/linux/sysconf_nocgo.go +++ b/sys/linux/sysconf_nocgo.go @@ -2,6 +2,7 @@ package linux +// GetClockTicks returns the number of click ticks in one jiffie. func GetClockTicks() int { return 100 } diff --git a/sys/linux/testdata/inet-dump-rhel6-2.6.32-504.3.3.el6.x86_64.bin b/sys/linux/testdata/inet-dump-rhel6-2.6.32-504.3.3.el6.x86_64.bin new file mode 100644 index 0000000000000000000000000000000000000000..c25978888691c74b2ea97765d316fd5bed04c969 GIT binary patch literal 1076 zcma!GU|JTji;Q3c+7AK;60nY;=Y$y_H6yj&NCuVxl{4J` z{jqg|`TM4A!TrBL{Dyollz3|Zih;~^!D22rd_j_^0K0n)P{TI`sz=KJNB9EG1-bWf zl!}mVoi?9k{|;M_mKzfn|C7mB_$#X2cNK{5kcS$+Fmu&$n9H~ul76+E52uE)6ePX+ ztMLzH&g+ZwF1|jb9Nr!+QV!xDIQ|bK96;hXW?;G(6b^?MfMf}S1IIDr4W#Q14s(xk z6E+j1!Xd(8EqZzYi9f(%E)ydYFkhUn=3-#tZ;xSN4Gp@InDhMA8X%u(gXpTWfA&6Y jy5Qs`12(q|k{%Wu6994Hp!+c>ZvxX5Fn_|z2aqHHu9vfq literal 0 HcmV?d00001