239 lines
5.9 KiB
Go
239 lines
5.9 KiB
Go
// Licensed to Elasticsearch B.V. under one or more contributor
|
|
// license agreements. See the NOTICE file distributed with
|
|
// this work for additional information regarding copyright
|
|
// ownership. Elasticsearch B.V. licenses this file to you under
|
|
// the Apache License, Version 2.0 (the "License"); you may
|
|
// not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing,
|
|
// software distributed under the License is distributed on an
|
|
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
// KIND, either express or implied. See the License for the
|
|
// specific language governing permissions and limitations
|
|
// under the License.
|
|
|
|
// +build linux
|
|
|
|
package procs
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/elastic/beats/libbeat/logp"
|
|
"github.com/elastic/beats/packetbeat/protos/applayer"
|
|
"github.com/elastic/gosigar"
|
|
)
|
|
|
|
type socketInfo struct {
|
|
srcIP, dstIP net.IP
|
|
srcPort, dstPort uint16
|
|
|
|
uid uint32
|
|
inode uint64
|
|
}
|
|
|
|
var procFiles = map[applayer.Transport]struct {
|
|
ipv4, ipv6 string
|
|
}{
|
|
applayer.TransportUDP: {"/proc/net/udp", "/proc/net/udp6"},
|
|
applayer.TransportTCP: {"/proc/net/tcp", "/proc/net/tcp6"},
|
|
}
|
|
|
|
// GetLocalPortToPIDMapping returns the list of local port numbers and the PID
|
|
// that owns them.
|
|
func (proc *ProcessesWatcher) GetLocalPortToPIDMapping(transport applayer.Transport) (ports map[uint16]int, err error) {
|
|
sourceFiles, ok := procFiles[transport]
|
|
if !ok {
|
|
return nil, fmt.Errorf("unsupported transport protocol id: %d", transport)
|
|
}
|
|
var pids gosigar.ProcList
|
|
if err = pids.Get(); err != nil {
|
|
return nil, err
|
|
}
|
|
logp.Debug("procs", "getLocalPortsToPIDs()")
|
|
ipv4socks, err := socketsFromProc(sourceFiles.ipv4, false)
|
|
if err != nil {
|
|
logp.Err("GetLocalPortToPIDMapping: parsing '%s': %s", sourceFiles.ipv4, err)
|
|
return nil, err
|
|
}
|
|
ipv6socks, err := socketsFromProc(sourceFiles.ipv6, true)
|
|
if err != nil {
|
|
logp.Err("GetLocalPortToPIDMapping: parsing '%s': %s", sourceFiles.ipv6, err)
|
|
return nil, err
|
|
}
|
|
socksMap := map[uint64]*socketInfo{}
|
|
for _, s := range ipv4socks {
|
|
socksMap[s.inode] = s
|
|
}
|
|
for _, s := range ipv6socks {
|
|
socksMap[s.inode] = s
|
|
}
|
|
|
|
ports = make(map[uint16]int)
|
|
for _, pid := range pids.List {
|
|
inodes, err := findSocketsOfPid("", pid)
|
|
if err != nil {
|
|
logp.Err("FindSocketsOfPid: %s", err)
|
|
continue
|
|
}
|
|
|
|
for _, inode := range inodes {
|
|
if sockInfo, exists := socksMap[inode]; exists {
|
|
ports[sockInfo.srcPort] = pid
|
|
}
|
|
}
|
|
}
|
|
|
|
return ports, nil
|
|
}
|
|
|
|
func findSocketsOfPid(prefix string, pid int) (inodes []uint64, err error) {
|
|
dirname := filepath.Join(prefix, "/proc", strconv.Itoa(pid), "fd")
|
|
procfs, err := os.Open(dirname)
|
|
if err != nil {
|
|
return []uint64{}, fmt.Errorf("Open: %s", err)
|
|
}
|
|
defer procfs.Close()
|
|
names, err := procfs.Readdirnames(0)
|
|
if err != nil {
|
|
return []uint64{}, fmt.Errorf("Readdirnames: %s", err)
|
|
}
|
|
|
|
for _, name := range names {
|
|
link, err := os.Readlink(filepath.Join(dirname, name))
|
|
if err != nil {
|
|
logp.Debug("procs", "Readlink %s: %s", name, err)
|
|
continue
|
|
}
|
|
|
|
if strings.HasPrefix(link, "socket:[") {
|
|
inode, err := strconv.ParseInt(link[8:len(link)-1], 10, 64)
|
|
if err != nil {
|
|
logp.Debug("procs", "ParseInt: %s:", err)
|
|
continue
|
|
}
|
|
|
|
inodes = append(inodes, uint64(inode))
|
|
}
|
|
}
|
|
|
|
return inodes, nil
|
|
}
|
|
|
|
func socketsFromProc(filename string, ipv6 bool) ([]*socketInfo, error) {
|
|
file, err := os.Open(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
return parseProcNetProto(file, ipv6)
|
|
}
|
|
|
|
// Parses the /proc/net/(tcp|udp)6? file
|
|
func parseProcNetProto(input io.Reader, ipv6 bool) ([]*socketInfo, error) {
|
|
buf := bufio.NewReader(input)
|
|
|
|
sockets := []*socketInfo{}
|
|
var err error
|
|
var line []byte
|
|
for err != io.EOF {
|
|
line, err = buf.ReadBytes('\n')
|
|
if err != nil && err != io.EOF {
|
|
logp.Err("Error reading proc net file: %s", err)
|
|
return nil, err
|
|
}
|
|
words := bytes.Fields(line)
|
|
// Ignore empty lines and the header
|
|
if len(words) == 0 || bytes.Equal(words[0], []byte("sl")) {
|
|
continue
|
|
}
|
|
if len(words) < 10 {
|
|
logp.Debug("procs", "Less than 10 words (%d) or starting with 'sl': %s", len(words), words)
|
|
continue
|
|
}
|
|
|
|
var sock socketInfo
|
|
var err error
|
|
|
|
sock.srcIP, sock.srcPort, err = hexToIPPort(words[1], ipv6)
|
|
if err != nil {
|
|
logp.Debug("procs", "Error parsing IP and port: %s", err)
|
|
continue
|
|
}
|
|
|
|
sock.dstIP, sock.dstPort, err = hexToIPPort(words[2], ipv6)
|
|
if err != nil {
|
|
logp.Debug("procs", "Error parsing IP and port: %s", err)
|
|
continue
|
|
}
|
|
|
|
uid, _ := strconv.Atoi(string(words[7]))
|
|
sock.uid = uint32(uid)
|
|
inode, _ := strconv.Atoi(string(words[9]))
|
|
sock.inode = uint64(inode)
|
|
|
|
sockets = append(sockets, &sock)
|
|
}
|
|
return sockets, nil
|
|
}
|
|
|
|
func hexToIPPort(str []byte, ipv6 bool) (net.IP, uint16, error) {
|
|
words := bytes.Split(str, []byte(":"))
|
|
if len(words) < 2 {
|
|
return nil, 0, errors.New("Didn't find ':' as a separator")
|
|
}
|
|
|
|
ip, err := hexToIP(string(words[0]), ipv6)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
port, err := strconv.ParseInt(string(words[1]), 16, 32)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return ip, uint16(port), nil
|
|
}
|
|
|
|
func hexToIP(word string, ipv6 bool) (net.IP, error) {
|
|
if ipv6 {
|
|
return hexToIpv6(word)
|
|
}
|
|
return hexToIpv4(word)
|
|
}
|
|
|
|
func hexToIpv4(word string) (net.IP, error) {
|
|
ip, err := strconv.ParseInt(word, 16, 64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return net.IPv4(byte(ip), byte(ip>>8), byte(ip>>16), byte(ip>>24)), nil
|
|
}
|
|
|
|
func hexToIpv6(word string) (net.IP, error) {
|
|
p := make(net.IP, net.IPv6len)
|
|
for i := 0; i < 4; i++ {
|
|
part, err := strconv.ParseUint(word[i*8:(i+1)*8], 16, 32)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p[i*4] = byte(part)
|
|
p[i*4+1] = byte(part >> 8)
|
|
p[i*4+2] = byte(part >> 16)
|
|
p[i*4+3] = byte(part >> 24)
|
|
}
|
|
return p, nil
|
|
}
|