mirror of https://github.com/subgraph/fw-daemon
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
363 lines
7.8 KiB
363 lines
7.8 KiB
package main
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"errors"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/subgraph/fw-daemon/nfqueue"
|
|
)
|
|
|
|
type socketAddr struct {
|
|
ip net.IP
|
|
port uint16
|
|
}
|
|
|
|
func (sa socketAddr) String() string {
|
|
return fmt.Sprintf("%v:%d", sa.ip, sa.port)
|
|
}
|
|
|
|
type socketStatus struct {
|
|
local socketAddr
|
|
remote socketAddr
|
|
uid int
|
|
inode uint64
|
|
pid int
|
|
// XXX debugging
|
|
line string
|
|
}
|
|
|
|
func (ss *socketStatus) String() string {
|
|
return fmt.Sprintf("%s -> %s uid=%d inode=%d pid=%d", ss.local, ss.remote, ss.uid, ss.inode, ss.pid)
|
|
}
|
|
|
|
type ConnectionInfo struct {
|
|
proc *ProcInfo
|
|
local *socketAddr
|
|
remote *socketAddr
|
|
}
|
|
|
|
func (ci *ConnectionInfo) String() string {
|
|
return fmt.Sprintf("%v %s %s", ci.proc, ci.local, ci.remote)
|
|
}
|
|
|
|
func findProcessForPacket(pkt *nfqueue.Packet) *ProcInfo {
|
|
ss := getSocketForPacket(pkt)
|
|
if ss == nil {
|
|
return nil
|
|
}
|
|
return findProcessForSocket(ss)
|
|
}
|
|
func findProcessForSocket(ss *socketStatus) *ProcInfo {
|
|
|
|
exePath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", ss.pid))
|
|
if err != nil {
|
|
log.Warning("Error reading exe link for pid %d: %v", ss.pid, err)
|
|
return nil
|
|
}
|
|
bs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", ss.pid))
|
|
if err != nil {
|
|
log.Warning("Error reading cmdline for pid %d: %v", ss.pid, err)
|
|
return nil
|
|
}
|
|
for i, b := range bs {
|
|
if b == 0 {
|
|
bs[i] = byte(' ')
|
|
}
|
|
}
|
|
|
|
finfo, err := os.Stat(fmt.Sprintf("/proc/%d", ss.pid))
|
|
if err != nil {
|
|
log.Warning("Could not stat /proc/%d: %v", ss.pid, err)
|
|
return nil
|
|
}
|
|
finfo.Sys()
|
|
return &ProcInfo{
|
|
pid: ss.pid,
|
|
uid: ss.uid,
|
|
exePath: exePath,
|
|
cmdLine: string(bs),
|
|
}
|
|
}
|
|
|
|
func getSocketLinesForPacket(pkt *nfqueue.Packet) []string {
|
|
if pkt.Protocol == nfqueue.TCP {
|
|
return getSocketLines("tcp")
|
|
} else if pkt.Protocol == nfqueue.UDP {
|
|
return getSocketLines("udp")
|
|
} else {
|
|
log.Warning("Cannot lookup socket for protocol %s", pkt.Protocol)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func getSocketLines(proto string) []string {
|
|
path := fmt.Sprintf("/proc/net/%s", proto)
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
log.Warning("Error reading %s: %v", path, err)
|
|
return nil
|
|
}
|
|
lines := strings.Split(string(data), "\n")
|
|
if len(lines) > 0 {
|
|
lines = lines[1:]
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func (sa *socketAddr) parse(s string) error {
|
|
ipPort := strings.Split(s, ":")
|
|
if len(ipPort) != 2 {
|
|
return fmt.Errorf("badly formatted socket address field: %s", s)
|
|
}
|
|
ip, err := ParseIp(ipPort[0])
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing ip field [%s]: %v", ipPort[0], err)
|
|
}
|
|
port, err := ParsePort(ipPort[1])
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing port field [%s]: %v", ipPort[1], err)
|
|
}
|
|
sa.ip = ip
|
|
sa.port = port
|
|
return nil
|
|
}
|
|
|
|
func (ss *socketStatus) parseLine(line string) error {
|
|
fs := strings.Fields(line)
|
|
if len(fs) < 10 {
|
|
return errors.New("insufficient fields")
|
|
}
|
|
if err := ss.local.parse(fs[1]); err != nil {
|
|
return err
|
|
}
|
|
if err := ss.remote.parse(fs[2]); err != nil {
|
|
return err
|
|
}
|
|
uid, err := strconv.ParseUint(fs[7], 10, 32)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ss.uid = int(uid)
|
|
inode, err := strconv.ParseUint(fs[9], 10, 64)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ss.inode = inode
|
|
return nil
|
|
}
|
|
|
|
func getSocketForPacket(pkt *nfqueue.Packet) *socketStatus {
|
|
ss := findSocket(pkt)
|
|
if ss == nil {
|
|
return nil
|
|
}
|
|
pid := findPidForInode(ss.inode)
|
|
if pid > 0 {
|
|
ss.pid = pid
|
|
return ss
|
|
}
|
|
log.Info("Unable to find socket link socket:[%d] %s", ss.inode, printPacket(pkt, ""))
|
|
log.Info("Line was %s", ss.line)
|
|
return nil
|
|
}
|
|
|
|
func findSocket(pkt *nfqueue.Packet) *socketStatus {
|
|
var status socketStatus
|
|
for _, line := range getSocketLinesForPacket(pkt) {
|
|
if len(line) == 0 {
|
|
continue
|
|
}
|
|
if err := status.parseLine(line); err != nil {
|
|
log.Warning("Unable to parse line [%s]: %v", line, err)
|
|
} else if status.remote.ip.Equal(pkt.Dst) && status.remote.port == pkt.DstPort && status.local.ip.Equal(pkt.Src) && status.local.port == pkt.SrcPort {
|
|
status.line = line
|
|
return &status
|
|
}
|
|
}
|
|
log.Info("Failed to find socket for packet: %s", printPacket(pkt, ""))
|
|
return nil
|
|
}
|
|
|
|
func ParseIp(ip string) (net.IP, error) {
|
|
var result net.IP
|
|
dst, err := hex.DecodeString(ip)
|
|
if err != nil {
|
|
return result, fmt.Errorf("Error parsing IP: %s", err)
|
|
}
|
|
// Reverse byte order -- /proc/net/tcp etc. is little-endian
|
|
// TODO: Does this vary by architecture?
|
|
for i, j := 0, len(dst)-1; i < j; i, j = i+1, j-1 {
|
|
dst[i], dst[j] = dst[j], dst[i]
|
|
}
|
|
result = net.IP(dst)
|
|
return result, nil
|
|
}
|
|
|
|
func ParsePort(port string) (uint16, error) {
|
|
p64, err := strconv.ParseInt(port, 16, 32)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("Error parsing port: %s", err)
|
|
}
|
|
return uint16(p64), nil
|
|
}
|
|
|
|
func findPidForInode(inode uint64) int {
|
|
search := fmt.Sprintf("socket:[%d]", inode)
|
|
for _, pid := range getAllPids() {
|
|
if matchesSocketLink(pid, search) {
|
|
return pid
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func matchesSocketLink(pid int, search string) bool {
|
|
paths, _ := filepath.Glob(fmt.Sprintf("/proc/%d/fd/*", pid))
|
|
for _, p := range paths {
|
|
link, err := os.Readlink(p)
|
|
if err == nil && link == search {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getAllPids() []int {
|
|
var pids []int
|
|
d, err := os.Open("/proc")
|
|
if err != nil {
|
|
log.Warning("Error opening /proc: %v", err)
|
|
return nil
|
|
}
|
|
defer d.Close()
|
|
names, err := d.Readdirnames(0)
|
|
if err != nil {
|
|
log.Warning("Error reading directory names from /proc: %v", err)
|
|
return nil
|
|
}
|
|
for _, n := range names {
|
|
if pid, err := strconv.ParseUint(n, 10, 32); err == nil {
|
|
pids = append(pids, int(pid))
|
|
}
|
|
}
|
|
return pids
|
|
}
|
|
|
|
func getConnections() ([]*ConnectionInfo, error) {
|
|
conns,err := readConntrack()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
resolveProcinfo(conns)
|
|
return conns, nil
|
|
}
|
|
|
|
func resolveProcinfo(conns []*ConnectionInfo) {
|
|
var sockets []*socketStatus
|
|
for _,line := range getSocketLines("tcp") {
|
|
if len(strings.TrimSpace(line)) == 0 {
|
|
continue
|
|
}
|
|
ss := new(socketStatus)
|
|
if err := ss.parseLine(line); err != nil {
|
|
log.Warning("Unable to parse line [%s]: %v", line, err)
|
|
} else {
|
|
pid := findPidForInode(ss.inode)
|
|
if pid > 0 {
|
|
ss.pid = pid
|
|
fmt.Println("Socket", ss)
|
|
sockets = append(sockets, ss)
|
|
}
|
|
}
|
|
}
|
|
for _,ci := range conns {
|
|
ss := findContrackSocket(ci, sockets)
|
|
if ss == nil {
|
|
continue
|
|
}
|
|
proc := findProcessForSocket(ss)
|
|
if proc != nil {
|
|
ci.proc = proc
|
|
}
|
|
}
|
|
}
|
|
|
|
func findContrackSocket(ci *ConnectionInfo, sockets []*socketStatus) *socketStatus {
|
|
for _,ss := range sockets {
|
|
if ss.local.port == ci.local.port && ss.remote.ip.Equal(ci.remote.ip) && ss.remote.port == ci.remote.port {
|
|
return ss
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func readConntrack() ([]*ConnectionInfo, error) {
|
|
path := fmt.Sprintf("/proc/net/ip_conntrack")
|
|
data, err := ioutil.ReadFile(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var result []*ConnectionInfo
|
|
lines := strings.Split(string(data), "\n")
|
|
for _,line := range(lines) {
|
|
ci,err := parseConntrackLine(line)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if ci != nil {
|
|
result = append(result, ci)
|
|
}
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
func parseConntrackLine(line string) (*ConnectionInfo, error) {
|
|
parts := strings.Fields(line)
|
|
if len(parts) < 8 || parts[0] != "tcp" || parts[3] != "ESTABLISHED" {
|
|
return nil, nil
|
|
}
|
|
|
|
local,err := conntrackAddr(parts[4], parts[6])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
remote,err := conntrackAddr(parts[5], parts[7])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &ConnectionInfo{
|
|
local: local,
|
|
remote: remote,
|
|
},nil
|
|
}
|
|
|
|
func conntrackAddr(ip_str, port_str string) (*socketAddr, error) {
|
|
ip := net.ParseIP(stripLabel(ip_str))
|
|
if ip == nil {
|
|
return nil, errors.New("Could not parse IP: "+ip_str)
|
|
}
|
|
i64, err := strconv.Atoi(stripLabel(port_str))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &socketAddr{
|
|
ip: ip,
|
|
port: uint16(i64),
|
|
},nil
|
|
}
|
|
|
|
func stripLabel(s string) string {
|
|
idx := strings.Index(s, "=")
|
|
if idx == -1 {
|
|
return s
|
|
}
|
|
return s[idx+1:]
|
|
}
|