youtubebeat/vendor/github.com/elastic/beats/packetbeat/procs/procs_linux.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
}