mirror of
https://github.com/safing/portmaster
synced 2025-09-02 10:39:22 +00:00
370 lines
9.5 KiB
Go
370 lines
9.5 KiB
Go
// +build linux
|
|
|
|
package proc
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
|
|
"github.com/safing/portbase/log"
|
|
)
|
|
|
|
/*
|
|
|
|
1. find socket inode
|
|
- by incoming (listenting sockets) or outgoing (local port + external IP + port) - also local IP?
|
|
- /proc/net/{tcp|udp}[6]
|
|
|
|
2. get list of processes of uid
|
|
|
|
3. find socket inode in process fds
|
|
- if not found, refresh map of uid->pids
|
|
- if not found, check ALL pids: maybe euid != uid
|
|
|
|
4. gather process info
|
|
|
|
Cache every step!
|
|
|
|
*/
|
|
|
|
// Network Related Constants
|
|
const (
|
|
TCP4 uint8 = iota
|
|
UDP4
|
|
TCP6
|
|
UDP6
|
|
ICMP4
|
|
ICMP6
|
|
|
|
TCP4Data = "/proc/net/tcp"
|
|
UDP4Data = "/proc/net/udp"
|
|
TCP6Data = "/proc/net/tcp6"
|
|
UDP6Data = "/proc/net/udp6"
|
|
ICMP4Data = "/proc/net/icmp"
|
|
ICMP6Data = "/proc/net/icmp6"
|
|
)
|
|
|
|
var (
|
|
// connectionSocketsLock sync.Mutex
|
|
// connectionTCP4 = make(map[string][]int)
|
|
// connectionUDP4 = make(map[string][]int)
|
|
// connectionTCP6 = make(map[string][]int)
|
|
// connectionUDP6 = make(map[string][]int)
|
|
|
|
listeningSocketsLock sync.Mutex
|
|
addressListeningTCP4 = make(map[string][]int)
|
|
addressListeningUDP4 = make(map[string][]int)
|
|
addressListeningTCP6 = make(map[string][]int)
|
|
addressListeningUDP6 = make(map[string][]int)
|
|
globalListeningTCP4 = make(map[uint16][]int)
|
|
globalListeningUDP4 = make(map[uint16][]int)
|
|
globalListeningTCP6 = make(map[uint16][]int)
|
|
globalListeningUDP6 = make(map[uint16][]int)
|
|
)
|
|
|
|
func getConnectionSocket(localIP net.IP, localPort uint16, protocol uint8) (int, int, bool) {
|
|
// listeningSocketsLock.Lock()
|
|
// defer listeningSocketsLock.Unlock()
|
|
|
|
var procFile string
|
|
var localIPHex string
|
|
switch protocol {
|
|
case TCP4:
|
|
procFile = TCP4Data
|
|
localIPBytes := []byte(localIP.To4())
|
|
localIPHex = strings.ToUpper(hex.EncodeToString([]byte{localIPBytes[3], localIPBytes[2], localIPBytes[1], localIPBytes[0]}))
|
|
case UDP4:
|
|
procFile = UDP4Data
|
|
localIPBytes := []byte(localIP.To4())
|
|
localIPHex = strings.ToUpper(hex.EncodeToString([]byte{localIPBytes[3], localIPBytes[2], localIPBytes[1], localIPBytes[0]}))
|
|
case TCP6:
|
|
procFile = TCP6Data
|
|
localIPHex = hex.EncodeToString([]byte(localIP))
|
|
case UDP6:
|
|
procFile = UDP6Data
|
|
localIPHex = hex.EncodeToString([]byte(localIP))
|
|
}
|
|
|
|
localPortHex := fmt.Sprintf("%04X", localPort)
|
|
|
|
// log.Tracef("process/proc: searching for PID of: %s:%d (%s:%s)", localIP, localPort, localIPHex, localPortHex)
|
|
|
|
// open file
|
|
socketData, err := os.Open(procFile)
|
|
if err != nil {
|
|
log.Warningf("process/proc: could not read %s: %s", procFile, err)
|
|
return unidentifiedProcessID, unidentifiedProcessID, false
|
|
}
|
|
defer socketData.Close()
|
|
|
|
// file scanner
|
|
scanner := bufio.NewScanner(socketData)
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
// parse
|
|
scanner.Scan() // skip first line
|
|
for scanner.Scan() {
|
|
line := strings.FieldsFunc(scanner.Text(), procDelimiter)
|
|
// log.Tracef("line: %s", line)
|
|
if len(line) < 14 {
|
|
// log.Tracef("process: too short: %s", line)
|
|
continue
|
|
}
|
|
|
|
if line[1] != localIPHex {
|
|
continue
|
|
}
|
|
if line[2] != localPortHex {
|
|
continue
|
|
}
|
|
|
|
ok := true
|
|
|
|
uid, err := strconv.ParseInt(line[11], 10, 32)
|
|
if err != nil {
|
|
log.Warningf("process: could not parse uid %s: %s", line[11], err)
|
|
uid = -1
|
|
ok = false
|
|
}
|
|
|
|
inode, err := strconv.ParseInt(line[13], 10, 32)
|
|
if err != nil {
|
|
log.Warningf("process: could not parse inode %s: %s", line[13], err)
|
|
inode = -1
|
|
ok = false
|
|
}
|
|
|
|
// log.Tracef("process/proc: identified process of %s:%d: socket=%d uid=%d", localIP, localPort, int(inode), int(uid))
|
|
return int(uid), int(inode), ok
|
|
|
|
}
|
|
|
|
return unidentifiedProcessID, unidentifiedProcessID, false
|
|
|
|
}
|
|
|
|
func getListeningSocket(localIP net.IP, localPort uint16, protocol uint8) (uid, inode int, ok bool) {
|
|
listeningSocketsLock.Lock()
|
|
defer listeningSocketsLock.Unlock()
|
|
|
|
var addressListening map[string][]int
|
|
var globalListening map[uint16][]int
|
|
switch protocol {
|
|
case TCP4:
|
|
addressListening = addressListeningTCP4
|
|
globalListening = globalListeningTCP4
|
|
case UDP4:
|
|
addressListening = addressListeningUDP4
|
|
globalListening = globalListeningUDP4
|
|
case TCP6:
|
|
addressListening = addressListeningTCP6
|
|
globalListening = globalListeningTCP6
|
|
case UDP6:
|
|
addressListening = addressListeningUDP6
|
|
globalListening = globalListeningUDP6
|
|
}
|
|
|
|
data, ok := addressListening[fmt.Sprintf("%s:%d", localIP, localPort)]
|
|
if !ok {
|
|
data, ok = globalListening[localPort]
|
|
}
|
|
if ok {
|
|
return data[0], data[1], true
|
|
}
|
|
updateListeners(protocol)
|
|
data, ok = addressListening[fmt.Sprintf("%s:%d", localIP, localPort)]
|
|
if !ok {
|
|
data, ok = globalListening[localPort]
|
|
}
|
|
if ok {
|
|
return data[0], data[1], true
|
|
}
|
|
|
|
return unidentifiedProcessID, unidentifiedProcessID, false
|
|
}
|
|
|
|
func procDelimiter(c rune) bool {
|
|
return unicode.IsSpace(c) || c == ':'
|
|
}
|
|
|
|
func convertIPv4(data string) net.IP {
|
|
decoded, err := hex.DecodeString(data)
|
|
if err != nil {
|
|
log.Warningf("process: could not parse IPv4 %s: %s", data, err)
|
|
return nil
|
|
}
|
|
if len(decoded) != 4 {
|
|
log.Warningf("process: decoded IPv4 %s has wrong length", decoded)
|
|
return nil
|
|
}
|
|
ip := net.IPv4(decoded[3], decoded[2], decoded[1], decoded[0])
|
|
return ip
|
|
}
|
|
|
|
func convertIPv6(data string) net.IP {
|
|
decoded, err := hex.DecodeString(data)
|
|
if err != nil {
|
|
log.Warningf("process: could not parse IPv6 %s: %s", data, err)
|
|
return nil
|
|
}
|
|
if len(decoded) != 16 {
|
|
log.Warningf("process: decoded IPv6 %s has wrong length", decoded)
|
|
return nil
|
|
}
|
|
ip := net.IP(decoded)
|
|
return ip
|
|
}
|
|
|
|
func updateListeners(protocol uint8) {
|
|
switch protocol {
|
|
case TCP4:
|
|
addressListeningTCP4, globalListeningTCP4 = getListenerMaps(TCP4Data, "00000000", "0A", convertIPv4)
|
|
case UDP4:
|
|
addressListeningUDP4, globalListeningUDP4 = getListenerMaps(UDP4Data, "00000000", "07", convertIPv4)
|
|
case TCP6:
|
|
addressListeningTCP6, globalListeningTCP6 = getListenerMaps(TCP6Data, "00000000000000000000000000000000", "0A", convertIPv6)
|
|
case UDP6:
|
|
addressListeningUDP6, globalListeningUDP6 = getListenerMaps(UDP6Data, "00000000000000000000000000000000", "07", convertIPv6)
|
|
}
|
|
}
|
|
|
|
func getListenerMaps(procFile, zeroIP, socketStatusListening string, ipConverter func(string) net.IP) (map[string][]int, map[uint16][]int) {
|
|
addressListening := make(map[string][]int)
|
|
globalListening := make(map[uint16][]int)
|
|
|
|
// open file
|
|
socketData, err := os.Open(procFile)
|
|
if err != nil {
|
|
log.Warningf("process: could not read %s: %s", procFile, err)
|
|
return addressListening, globalListening
|
|
}
|
|
defer socketData.Close()
|
|
|
|
// file scanner
|
|
scanner := bufio.NewScanner(socketData)
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
// parse
|
|
scanner.Scan() // skip first line
|
|
for scanner.Scan() {
|
|
line := strings.FieldsFunc(scanner.Text(), procDelimiter)
|
|
if len(line) < 14 {
|
|
// log.Tracef("process: too short: %s", line)
|
|
continue
|
|
}
|
|
if line[5] != socketStatusListening {
|
|
// skip if not listening
|
|
// log.Tracef("process: not listening %s: %s", line, line[5])
|
|
continue
|
|
}
|
|
|
|
port, err := strconv.ParseUint(line[2], 16, 16)
|
|
// log.Tracef("port: %s", line[2])
|
|
if err != nil {
|
|
log.Warningf("process: could not parse port %s: %s", line[2], err)
|
|
continue
|
|
}
|
|
|
|
uid, err := strconv.ParseInt(line[11], 10, 32)
|
|
// log.Tracef("uid: %s", line[11])
|
|
if err != nil {
|
|
log.Warningf("process: could not parse uid %s: %s", line[11], err)
|
|
continue
|
|
}
|
|
|
|
inode, err := strconv.ParseInt(line[13], 10, 32)
|
|
// log.Tracef("inode: %s", line[13])
|
|
if err != nil {
|
|
log.Warningf("process: could not parse inode %s: %s", line[13], err)
|
|
continue
|
|
}
|
|
|
|
if line[1] == zeroIP {
|
|
globalListening[uint16(port)] = []int{int(uid), int(inode)}
|
|
} else {
|
|
address := ipConverter(line[1])
|
|
if address != nil {
|
|
addressListening[fmt.Sprintf("%s:%d", address, port)] = []int{int(uid), int(inode)}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
return addressListening, globalListening
|
|
}
|
|
|
|
// GetActiveConnectionIDs returns all connection IDs that are still marked as active by the OS.
|
|
func GetActiveConnectionIDs() []string {
|
|
var connections []string
|
|
|
|
connections = append(connections, getConnectionIDsFromSource(TCP4Data, 6, convertIPv4)...)
|
|
connections = append(connections, getConnectionIDsFromSource(UDP4Data, 17, convertIPv4)...)
|
|
connections = append(connections, getConnectionIDsFromSource(TCP6Data, 6, convertIPv6)...)
|
|
connections = append(connections, getConnectionIDsFromSource(UDP6Data, 17, convertIPv6)...)
|
|
|
|
return connections
|
|
}
|
|
|
|
func getConnectionIDsFromSource(source string, protocol uint16, ipConverter func(string) net.IP) []string {
|
|
var connections []string
|
|
|
|
// open file
|
|
socketData, err := os.Open(source)
|
|
if err != nil {
|
|
log.Warningf("process: could not read %s: %s", source, err)
|
|
return connections
|
|
}
|
|
defer socketData.Close()
|
|
|
|
// file scanner
|
|
scanner := bufio.NewScanner(socketData)
|
|
scanner.Split(bufio.ScanLines)
|
|
|
|
// parse
|
|
scanner.Scan() // skip first line
|
|
for scanner.Scan() {
|
|
line := strings.FieldsFunc(scanner.Text(), procDelimiter)
|
|
if len(line) < 14 {
|
|
// log.Tracef("process: too short: %s", line)
|
|
continue
|
|
}
|
|
|
|
// skip listeners and closed connections
|
|
if line[5] == "0A" || line[5] == "07" {
|
|
continue
|
|
}
|
|
|
|
localIP := ipConverter(line[1])
|
|
if localIP == nil {
|
|
continue
|
|
}
|
|
|
|
localPort, err := strconv.ParseUint(line[2], 16, 16)
|
|
if err != nil {
|
|
log.Warningf("process: could not parse port: %s", err)
|
|
continue
|
|
}
|
|
|
|
remoteIP := ipConverter(line[3])
|
|
if remoteIP == nil {
|
|
continue
|
|
}
|
|
|
|
remotePort, err := strconv.ParseUint(line[4], 16, 16)
|
|
if err != nil {
|
|
log.Warningf("process: could not parse port: %s", err)
|
|
continue
|
|
}
|
|
|
|
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", protocol, localIP, localPort, remoteIP, remotePort))
|
|
}
|
|
|
|
return connections
|
|
}
|