mirror of
https://github.com/weaveworks/scope.git
synced 2026-03-03 18:20:27 +00:00
Since https://github.com/weaveworks/tcptracer-bpf/pull/39, tcptracer-bpf can generate "fd_install" events when a process installs a new file descriptor in its fd table. Those events must be requested explicitely on a per-pid basis with tracer.AddFdInstallWatcher(pid). This is useful to know about "accept" events that would otherwise be missed because kretprobes are not triggered for functions that were called before the installation of the kretprobe. This patch find all the processes that are currently blocked on an accept() syscall during the EbpfTracker initialization. feedInitialConnections() will use tracer.AddFdInstallWatcher() to subscribe to fd_install events. When a fd_install event is received, synthesise an accept event with the connection tuple and the network namespace (from /proc).
175 lines
3.8 KiB
Go
175 lines
3.8 KiB
Go
package procspy
|
|
|
|
import (
|
|
"bytes"
|
|
"net"
|
|
)
|
|
|
|
// Used to check whether we are parsing a header line
|
|
var slHeader = []byte("sl")
|
|
|
|
// ProcNet is an iterator to parse /proc/net/tcp{,6} files.
|
|
type ProcNet struct {
|
|
b []byte
|
|
c Connection
|
|
bytesLocal, bytesRemote [16]byte
|
|
seen map[uint64]struct{}
|
|
}
|
|
|
|
// NewProcNet gives a new ProcNet parser.
|
|
func NewProcNet(b []byte) *ProcNet {
|
|
return &ProcNet{
|
|
b: b,
|
|
c: Connection{},
|
|
seen: map[uint64]struct{}{},
|
|
}
|
|
}
|
|
|
|
// Next returns the next connection. All buffers are re-used, so if you want
|
|
// to keep the IPs you have to copy them.
|
|
func (p *ProcNet) Next() *Connection {
|
|
again:
|
|
if len(p.b) == 0 {
|
|
return nil
|
|
}
|
|
b := p.b
|
|
|
|
var (
|
|
sl, local, remote, state, inode []byte
|
|
)
|
|
|
|
sl, b = nextField(b) // 'sl' column
|
|
if bytes.Equal(sl, slHeader) {
|
|
// Skip header
|
|
p.b = nextLine(b)
|
|
goto again
|
|
}
|
|
local, b = nextField(b)
|
|
remote, b = nextField(b)
|
|
state, b = nextField(b)
|
|
switch parseHex(state) {
|
|
// Only process established or half-closed connections
|
|
case tcpEstablished, tcpFinWait1, tcpFinWait2, tcpCloseWait:
|
|
default:
|
|
p.b = nextLine(b)
|
|
goto again
|
|
}
|
|
_, b = nextField(b) // 'tx_queue' column
|
|
_, b = nextField(b) // 'rx_queue' column
|
|
_, b = nextField(b) // 'tr' column
|
|
_, b = nextField(b) // 'uid' column
|
|
_, b = nextField(b) // 'timeout' column
|
|
inode, b = nextField(b)
|
|
|
|
p.c.LocalAddress, p.c.LocalPort = scanAddressNA(local, &p.bytesLocal)
|
|
p.c.RemoteAddress, p.c.RemotePort = scanAddressNA(remote, &p.bytesRemote)
|
|
p.c.Inode = parseDec(inode)
|
|
p.b = nextLine(b)
|
|
if _, alreadySeen := p.seen[p.c.Inode]; alreadySeen {
|
|
goto again
|
|
}
|
|
p.seen[p.c.Inode] = struct{}{}
|
|
return &p.c
|
|
}
|
|
|
|
// scanAddressNA parses 'A12CF62E:00AA' to the address/port. Handles IPv4 and
|
|
// IPv6 addresses. The address is a big endian 32 bit ints, hex encoded. We
|
|
// just decode the hex and flip the bytes in every group of 4.
|
|
func scanAddressNA(in []byte, buf *[16]byte) (net.IP, uint16) {
|
|
col := bytes.IndexByte(in, ':')
|
|
if col == -1 {
|
|
return nil, 0
|
|
}
|
|
|
|
// Network address is big endian. Can be either ipv4 or ipv6.
|
|
address := hexDecode32bigNA(in[:col], buf)
|
|
return net.IP(address), uint16(parseHex(in[col+1:]))
|
|
}
|
|
|
|
// hexDecode32big decodes sequences of 32bit big endian bytes.
|
|
func hexDecode32bigNA(src []byte, buf *[16]byte) []byte {
|
|
blocks := len(src) / 8
|
|
for block := 0; block < blocks; block++ {
|
|
for i := 0; i < 4; i++ {
|
|
a := fromHexChar(src[block*8+i*2])
|
|
b := fromHexChar(src[block*8+i*2+1])
|
|
buf[block*4+3-i] = (a << 4) | b
|
|
}
|
|
}
|
|
return buf[:blocks*4]
|
|
}
|
|
|
|
func nextField(s []byte) ([]byte, []byte) {
|
|
// Skip whitespace.
|
|
for i, b := range s {
|
|
if b != ' ' {
|
|
s = s[i:]
|
|
break
|
|
}
|
|
}
|
|
|
|
// Up until the next whitespace field.
|
|
for i, b := range s {
|
|
if b == ' ' {
|
|
return s[:i], s[i:]
|
|
}
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
|
|
func nextLine(s []byte) []byte {
|
|
i := bytes.IndexByte(s, '\n')
|
|
if i == -1 {
|
|
return nil
|
|
}
|
|
return s[i+1:]
|
|
}
|
|
|
|
// Simplified copy of strconv.ParseUint(16).
|
|
func parseHex(s []byte) uint {
|
|
n := uint(0)
|
|
for i := 0; i < len(s); i++ {
|
|
n *= 16
|
|
n += uint(fromHexChar(s[i]))
|
|
}
|
|
return n
|
|
}
|
|
|
|
// Simplified copy of strconv.ParseUint(10).
|
|
func parseDec(s []byte) uint64 {
|
|
n := uint64(0)
|
|
for _, c := range s {
|
|
n *= 10
|
|
n += uint64(c - '0')
|
|
}
|
|
return n
|
|
}
|
|
|
|
// hexDecode32big decodes sequences of 32bit big endian bytes.
|
|
func hexDecode32big(src []byte) []byte {
|
|
dst := make([]byte, len(src)/2)
|
|
blocks := len(src) / 8
|
|
for block := 0; block < blocks; block++ {
|
|
for i := 0; i < 4; i++ {
|
|
a := fromHexChar(src[block*8+i*2])
|
|
b := fromHexChar(src[block*8+i*2+1])
|
|
dst[block*4+3-i] = (a << 4) | b
|
|
}
|
|
}
|
|
return dst
|
|
}
|
|
|
|
// fromHexChar converts a hex character into its value.
|
|
func fromHexChar(c byte) uint8 {
|
|
switch {
|
|
case '0' <= c && c <= '9':
|
|
return c - '0'
|
|
case 'a' <= c && c <= 'f':
|
|
return c - 'a' + 10
|
|
case 'A' <= c && c <= 'F':
|
|
return c - 'A' + 10
|
|
}
|
|
return 0
|
|
}
|