Initial commit after restructure
This commit is contained in:
21
process/doc.go
Normal file
21
process/doc.go
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
/*
|
||||
|
||||
|
||||
Profiles
|
||||
|
||||
Profiles describe the network behaviour
|
||||
|
||||
|
||||
Profiles are found in 3 different paths:
|
||||
- /Me/Profiles/: Profiles used for this system
|
||||
- /Data/Profiles/: Profiles supplied by Safing
|
||||
- /Company/Profiles/: Profiles supplied by the company
|
||||
|
||||
When a program wants to use the network for the first time, Safing first searches for a Profile in the Company namespace, then in the Data namespace. If neither is found, it searches for a default profile in the same order.
|
||||
|
||||
Default profiles are profiles with a path ending with a "/". The default profile with the longest matching path is chosen.
|
||||
|
||||
*/
|
||||
package process
|
||||
74
process/fileinfo.go
Normal file
74
process/fileinfo.go
Normal file
@@ -0,0 +1,74 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"github.com/Safing/safing-core/database"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
)
|
||||
|
||||
// ExecutableSignature stores a signature of an executable.
|
||||
type ExecutableSignature []byte
|
||||
|
||||
// FileInfo stores (security) information about a file.
|
||||
type FileInfo struct {
|
||||
database.Base
|
||||
HumanName string
|
||||
Owners []string
|
||||
ApproxLastSeen int64
|
||||
Signature *ExecutableSignature
|
||||
}
|
||||
|
||||
var fileInfoModel *FileInfo // only use this as parameter for database.EnsureModel-like functions
|
||||
|
||||
func init() {
|
||||
database.RegisterModel(fileInfoModel, func() database.Model { return new(FileInfo) })
|
||||
}
|
||||
|
||||
// Create saves FileInfo with the provided name in the default namespace.
|
||||
func (m *FileInfo) Create(name string) error {
|
||||
return m.CreateObject(&database.FileInfoCache, name, m)
|
||||
}
|
||||
|
||||
// CreateInNamespace saves FileInfo with the provided name in the provided namespace.
|
||||
func (m *FileInfo) CreateInNamespace(namespace *datastore.Key, name string) error {
|
||||
return m.CreateObject(namespace, name, m)
|
||||
}
|
||||
|
||||
// Save saves FileInfo.
|
||||
func (m *FileInfo) Save() error {
|
||||
return m.SaveObject(m)
|
||||
}
|
||||
|
||||
// getFileInfo fetches FileInfo with the provided name from the default namespace.
|
||||
func getFileInfo(name string) (*FileInfo, error) {
|
||||
return getFileInfoFromNamespace(&database.FileInfoCache, name)
|
||||
}
|
||||
|
||||
// getFileInfoFromNamespace fetches FileInfo with the provided name from the provided namespace.
|
||||
func getFileInfoFromNamespace(namespace *datastore.Key, name string) (*FileInfo, error) {
|
||||
object, err := database.GetAndEnsureModel(namespace, name, fileInfoModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model, ok := object.(*FileInfo)
|
||||
if !ok {
|
||||
return nil, database.NewMismatchError(object, fileInfoModel)
|
||||
}
|
||||
return model, nil
|
||||
}
|
||||
|
||||
// GetFileInfo gathers information about a file and returns *FileInfo
|
||||
func GetFileInfo(path string) *FileInfo {
|
||||
// TODO: actually get file information
|
||||
// TODO: try to load from DB
|
||||
// TODO: save to DB (key: hash of some sorts)
|
||||
splittedPath := strings.Split("/", path)
|
||||
return &FileInfo{
|
||||
HumanName: splittedPath[len(splittedPath)-1],
|
||||
ApproxLastSeen: time.Now().Unix(),
|
||||
}
|
||||
}
|
||||
148
process/find.go
Normal file
148
process/find.go
Normal file
@@ -0,0 +1,148 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
|
||||
"github.com/Safing/safing-core/network/packet"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrConnectionNotFound = errors.New("could not find connection")
|
||||
ErrProcessNotFound = errors.New("could not find process")
|
||||
)
|
||||
|
||||
func GetPidByPacket(pkt packet.Packet) (pid int, direction bool, err error) {
|
||||
|
||||
var localIP net.IP
|
||||
var localPort uint16
|
||||
var remoteIP net.IP
|
||||
var remotePort uint16
|
||||
if pkt.IsInbound() {
|
||||
localIP = pkt.GetIPHeader().Dst
|
||||
remoteIP = pkt.GetIPHeader().Src
|
||||
} else {
|
||||
localIP = pkt.GetIPHeader().Src
|
||||
remoteIP = pkt.GetIPHeader().Dst
|
||||
}
|
||||
if pkt.GetIPHeader().Protocol == packet.TCP || pkt.GetIPHeader().Protocol == packet.UDP {
|
||||
if pkt.IsInbound() {
|
||||
localPort = pkt.GetTCPUDPHeader().DstPort
|
||||
remotePort = pkt.GetTCPUDPHeader().SrcPort
|
||||
} else {
|
||||
localPort = pkt.GetTCPUDPHeader().SrcPort
|
||||
remotePort = pkt.GetTCPUDPHeader().DstPort
|
||||
}
|
||||
}
|
||||
|
||||
switch {
|
||||
case pkt.GetIPHeader().Protocol == packet.TCP && pkt.IPVersion() == packet.IPv4:
|
||||
return getTCP4PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
|
||||
case pkt.GetIPHeader().Protocol == packet.UDP && pkt.IPVersion() == packet.IPv4:
|
||||
return getUDP4PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
|
||||
case pkt.GetIPHeader().Protocol == packet.TCP && pkt.IPVersion() == packet.IPv6:
|
||||
return getTCP6PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
|
||||
case pkt.GetIPHeader().Protocol == packet.UDP && pkt.IPVersion() == packet.IPv6:
|
||||
return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, pkt.IsInbound())
|
||||
default:
|
||||
return -1, false, errors.New("unsupported protocol for finding process")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, err error) {
|
||||
|
||||
var pid int
|
||||
pid, direction, err = GetPidByPacket(pkt)
|
||||
if pid < 0 {
|
||||
return nil, direction, ErrConnectionNotFound
|
||||
}
|
||||
if err != nil {
|
||||
return nil, direction, err
|
||||
}
|
||||
|
||||
process, err = GetOrFindProcess(pid)
|
||||
if err != nil {
|
||||
return nil, direction, err
|
||||
}
|
||||
|
||||
return process, direction, nil
|
||||
|
||||
}
|
||||
|
||||
func GetPidByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (pid int, direction bool, err error) {
|
||||
|
||||
ipVersion := packet.IPv4
|
||||
if v4 := localIP.To4(); v4 == nil {
|
||||
ipVersion = packet.IPv6
|
||||
}
|
||||
|
||||
switch {
|
||||
case protocol == packet.TCP && ipVersion == packet.IPv4:
|
||||
return getTCP4PacketInfo(localIP, localPort, remoteIP, remotePort, false)
|
||||
case protocol == packet.UDP && ipVersion == packet.IPv4:
|
||||
return getUDP4PacketInfo(localIP, localPort, remoteIP, remotePort, false)
|
||||
case protocol == packet.TCP && ipVersion == packet.IPv6:
|
||||
return getTCP6PacketInfo(localIP, localPort, remoteIP, remotePort, false)
|
||||
case protocol == packet.UDP && ipVersion == packet.IPv6:
|
||||
return getUDP6PacketInfo(localIP, localPort, remoteIP, remotePort, false)
|
||||
default:
|
||||
return -1, false, errors.New("unsupported protocol for finding process")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func GetProcessByEndpoints(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, protocol packet.IPProtocol) (process *Process, err error) {
|
||||
|
||||
var pid int
|
||||
pid, _, err = GetPidByEndpoints(localIP, localPort, remoteIP, remotePort, protocol)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if pid < 0 {
|
||||
return nil, ErrConnectionNotFound
|
||||
}
|
||||
|
||||
process, err = GetOrFindProcess(pid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return process, nil
|
||||
|
||||
}
|
||||
|
||||
func GetActiveConnectionIDs() []string {
|
||||
return getActiveConnectionIDs()
|
||||
}
|
||||
|
||||
// func GetProcessByPid(pid int) *Process {
|
||||
// process, err := GetOrFindProcess(pid)
|
||||
// if err != nil {
|
||||
// log.Warningf("process: failed to get process %d: %s", pid, err)
|
||||
// return nil
|
||||
// }
|
||||
// return process
|
||||
// }
|
||||
|
||||
// func GetProcessOfConnection(localIP *net.IP, localPort uint16, protocol uint8) (process *Process, status uint8) {
|
||||
// pid, status := GetPidOfConnection(localIP, localPort, protocol)
|
||||
// if status == Success {
|
||||
// process = GetProcessByPid(pid)
|
||||
// if process == nil {
|
||||
// return nil, NoProcessInfo
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
|
||||
// func GetProcessByPacket(pkt packet.Packet) (process *Process, direction bool, status uint8) {
|
||||
// pid, direction, status := GetPidByPacket(pkt)
|
||||
// if status == Success {
|
||||
// process = GetProcessByPid(pid)
|
||||
// if process == nil {
|
||||
// return nil, direction, NoProcessInfo
|
||||
// }
|
||||
// }
|
||||
// return
|
||||
// }
|
||||
11
process/getpid_linux.go
Normal file
11
process/getpid_linux.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package process
|
||||
|
||||
import "github.com/Safing/safing-core/process/proc"
|
||||
|
||||
var (
|
||||
getTCP4PacketInfo = proc.GetTCP4PacketInfo
|
||||
getTCP6PacketInfo = proc.GetTCP6PacketInfo
|
||||
getUDP4PacketInfo = proc.GetUDP4PacketInfo
|
||||
getUDP6PacketInfo = proc.GetUDP6PacketInfo
|
||||
getActiveConnectionIDs = proc.GetActiveConnectionIDs
|
||||
)
|
||||
13
process/getpid_windows.go
Normal file
13
process/getpid_windows.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package process
|
||||
|
||||
import (
|
||||
"github.com/Safing/safing-core/process/iphelper"
|
||||
)
|
||||
|
||||
var (
|
||||
getTCP4PacketInfo = iphelper.GetTCP4PacketInfo
|
||||
getTCP6PacketInfo = iphelper.GetTCP6PacketInfo
|
||||
getUDP4PacketInfo = iphelper.GetUDP4PacketInfo
|
||||
getUDP6PacketInfo = iphelper.GetUDP6PacketInfo
|
||||
getActiveConnectionIDs = iphelper.GetActiveConnectionIDs
|
||||
)
|
||||
220
process/iphelper/get.go
Normal file
220
process/iphelper/get.go
Normal file
@@ -0,0 +1,220 @@
|
||||
// +build windows
|
||||
|
||||
package iphelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
tcp4Connections []*connectionEntry
|
||||
tcp4Listeners []*connectionEntry
|
||||
tcp6Connections []*connectionEntry
|
||||
tcp6Listeners []*connectionEntry
|
||||
|
||||
udp4Connections []*connectionEntry
|
||||
udp4Listeners []*connectionEntry
|
||||
udp6Connections []*connectionEntry
|
||||
udp6Listeners []*connectionEntry
|
||||
|
||||
ipHelper *IPHelper
|
||||
lock sync.RWMutex
|
||||
)
|
||||
|
||||
func checkIPHelper() (err error) {
|
||||
if ipHelper == nil {
|
||||
ipHelper, err = New()
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
|
||||
// search
|
||||
pid, direction = search(tcp4Connections, tcp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// if unable to find, refresh
|
||||
lock.Lock()
|
||||
err = checkIPHelper()
|
||||
if err == nil {
|
||||
tcp4Connections, tcp4Listeners, err = ipHelper.GetTables(TCP, IPv4)
|
||||
}
|
||||
lock.Unlock()
|
||||
if err != nil {
|
||||
return -1, direction, err
|
||||
}
|
||||
|
||||
// search
|
||||
pid, direction = search(tcp4Connections, tcp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return -1, direction, nil
|
||||
}
|
||||
|
||||
func GetTCP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
|
||||
// search
|
||||
pid, direction = search(tcp6Connections, tcp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// if unable to find, refresh
|
||||
lock.Lock()
|
||||
err = checkIPHelper()
|
||||
if err == nil {
|
||||
tcp6Connections, tcp6Listeners, err = ipHelper.GetTables(TCP, IPv6)
|
||||
}
|
||||
lock.Unlock()
|
||||
if err != nil {
|
||||
return -1, direction, err
|
||||
}
|
||||
|
||||
// search
|
||||
pid, direction = search(tcp6Connections, tcp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return
|
||||
}
|
||||
|
||||
return -1, direction, nil
|
||||
}
|
||||
|
||||
func GetUDP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
|
||||
// search
|
||||
pid, _ = search(udp4Connections, udp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return pid, pktDirection, nil
|
||||
}
|
||||
|
||||
// if unable to find, refresh
|
||||
lock.Lock()
|
||||
err = checkIPHelper()
|
||||
if err == nil {
|
||||
udp4Connections, udp4Listeners, err = ipHelper.GetTables(UDP, IPv4)
|
||||
}
|
||||
lock.Unlock()
|
||||
if err != nil {
|
||||
return -1, pktDirection, err
|
||||
}
|
||||
|
||||
// search
|
||||
pid, _ = search(udp4Connections, udp4Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return pid, pktDirection, nil
|
||||
}
|
||||
|
||||
return -1, pktDirection, nil
|
||||
}
|
||||
|
||||
func GetUDP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
|
||||
// search
|
||||
pid, _ = search(udp6Connections, udp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return pid, pktDirection, nil
|
||||
}
|
||||
|
||||
// if unable to find, refresh
|
||||
lock.Lock()
|
||||
err = checkIPHelper()
|
||||
if err == nil {
|
||||
udp6Connections, udp6Listeners, err = ipHelper.GetTables(UDP, IPv6)
|
||||
}
|
||||
lock.Unlock()
|
||||
if err != nil {
|
||||
return -1, pktDirection, err
|
||||
}
|
||||
|
||||
// search
|
||||
pid, _ = search(udp6Connections, udp6Listeners, localIP, remoteIP, localPort, remotePort, pktDirection)
|
||||
if pid >= 0 {
|
||||
return pid, pktDirection, nil
|
||||
}
|
||||
|
||||
return -1, pktDirection, nil
|
||||
}
|
||||
|
||||
func search(connections, listeners []*connectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16, pktDirection bool) (pid int, direction bool) {
|
||||
lock.RLock()
|
||||
defer lock.RUnlock()
|
||||
|
||||
if pktDirection {
|
||||
// inbound
|
||||
pid = searchListeners(listeners, localIP, localPort)
|
||||
if pid >= 0 {
|
||||
return pid, true
|
||||
}
|
||||
pid = searchConnections(connections, localIP, remoteIP, localPort, remotePort)
|
||||
if pid >= 0 {
|
||||
return pid, false
|
||||
}
|
||||
} else {
|
||||
// outbound
|
||||
pid = searchConnections(connections, localIP, remoteIP, localPort, remotePort)
|
||||
if pid >= 0 {
|
||||
return pid, false
|
||||
}
|
||||
pid = searchListeners(listeners, localIP, localPort)
|
||||
if pid >= 0 {
|
||||
return pid, true
|
||||
}
|
||||
}
|
||||
|
||||
return -1, pktDirection
|
||||
}
|
||||
|
||||
func searchConnections(list []*connectionEntry, localIP, remoteIP net.IP, localPort, remotePort uint16) (pid int) {
|
||||
|
||||
for _, entry := range list {
|
||||
if localPort == entry.localPort &&
|
||||
remotePort == entry.remotePort &&
|
||||
remoteIP.Equal(entry.remoteIP) &&
|
||||
localIP.Equal(entry.localIP) {
|
||||
return entry.pid
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func searchListeners(list []*connectionEntry, localIP net.IP, localPort uint16) (pid int) {
|
||||
|
||||
for _, entry := range list {
|
||||
if localPort == entry.localPort &&
|
||||
entry.localIP == nil || // nil IP means zero IP, see tables.go
|
||||
localIP.Equal(entry.localIP) {
|
||||
return entry.pid
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func GetActiveConnectionIDs() (connections []string) {
|
||||
lock.Lock()
|
||||
defer lock.Unlock()
|
||||
|
||||
for _, entry := range tcp4Connections {
|
||||
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", TCP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
|
||||
}
|
||||
for _, entry := range tcp6Connections {
|
||||
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", TCP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
|
||||
}
|
||||
for _, entry := range udp4Connections {
|
||||
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", UDP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
|
||||
}
|
||||
for _, entry := range udp6Connections {
|
||||
connections = append(connections, fmt.Sprintf("%d-%s-%d-%s-%d", UDP, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort))
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
77
process/iphelper/iphelper.go
Normal file
77
process/iphelper/iphelper.go
Normal file
@@ -0,0 +1,77 @@
|
||||
// +build windows
|
||||
|
||||
package iphelper
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/tevino/abool"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var (
|
||||
errInvalid = errors.New("IPHelper not initialzed or broken")
|
||||
)
|
||||
|
||||
type IPHelper struct {
|
||||
dll *windows.LazyDLL
|
||||
|
||||
getExtendedTcpTable *windows.LazyProc
|
||||
getExtendedUdpTable *windows.LazyProc
|
||||
// getOwnerModuleFromTcpEntry *windows.LazyProc
|
||||
// getOwnerModuleFromTcp6Entry *windows.LazyProc
|
||||
// getOwnerModuleFromUdpEntry *windows.LazyProc
|
||||
// getOwnerModuleFromUdp6Entry *windows.LazyProc
|
||||
|
||||
valid *abool.AtomicBool
|
||||
}
|
||||
|
||||
func New() (*IPHelper, error) {
|
||||
|
||||
new := &IPHelper{}
|
||||
new.valid = abool.NewBool(false)
|
||||
var err error
|
||||
|
||||
// load dll
|
||||
new.dll = windows.NewLazySystemDLL("iphlpapi.dll")
|
||||
new.dll.Load()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// load functions
|
||||
new.getExtendedTcpTable = new.dll.NewProc("GetExtendedTcpTable")
|
||||
err = new.getExtendedTcpTable.Find()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could find proc GetExtendedTcpTable: %s", err)
|
||||
}
|
||||
new.getExtendedUdpTable = new.dll.NewProc("GetExtendedUdpTable")
|
||||
err = new.getExtendedUdpTable.Find()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could find proc GetExtendedUdpTable: %s", err)
|
||||
}
|
||||
// new.getOwnerModuleFromTcpEntry = new.dll.NewProc("GetOwnerModuleFromTcpEntry")
|
||||
// err = new.getOwnerModuleFromTcpEntry.Find()
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromTcpEntry: %s", err)
|
||||
// }
|
||||
// new.getOwnerModuleFromTcp6Entry = new.dll.NewProc("GetOwnerModuleFromTcp6Entry")
|
||||
// err = new.getOwnerModuleFromTcp6Entry.Find()
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromTcp6Entry: %s", err)
|
||||
// }
|
||||
// new.getOwnerModuleFromUdpEntry = new.dll.NewProc("GetOwnerModuleFromUdpEntry")
|
||||
// err = new.getOwnerModuleFromUdpEntry.Find()
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromUdpEntry: %s", err)
|
||||
// }
|
||||
// new.getOwnerModuleFromUdp6Entry = new.dll.NewProc("GetOwnerModuleFromUdp6Entry")
|
||||
// err = new.getOwnerModuleFromUdp6Entry.Find()
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("could find proc GetOwnerModuleFromUdp6Entry: %s", err)
|
||||
// }
|
||||
|
||||
new.valid.Set()
|
||||
return new, nil
|
||||
}
|
||||
263
process/iphelper/tables.go
Normal file
263
process/iphelper/tables.go
Normal file
@@ -0,0 +1,263 @@
|
||||
// +build windows
|
||||
|
||||
package iphelper
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
const (
|
||||
iphelper_TCP_TABLE_OWNER_PID_ALL uintptr = 5
|
||||
iphelper_UDP_TABLE_OWNER_PID uintptr = 1
|
||||
iphelper_TCP_STATE_LISTEN uint32 = 2
|
||||
)
|
||||
|
||||
type connectionEntry struct {
|
||||
localIP net.IP
|
||||
remoteIP net.IP
|
||||
localPort uint16
|
||||
remotePort uint16
|
||||
pid int
|
||||
}
|
||||
|
||||
func (entry *connectionEntry) String() string {
|
||||
return fmt.Sprintf("PID=%d %s:%d <> %s:%d", entry.pid, entry.localIP, entry.localPort, entry.remoteIP, entry.remotePort)
|
||||
}
|
||||
|
||||
type iphelperTcpTable struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366921(v=vs.85).aspx
|
||||
numEntries uint32
|
||||
table [4096]iphelperTcpRow
|
||||
}
|
||||
|
||||
type iphelperTcpRow struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366913(v=vs.85).aspx
|
||||
state uint32
|
||||
localAddr uint32
|
||||
localPort uint32
|
||||
remoteAddr uint32
|
||||
remotePort uint32
|
||||
owningPid uint32
|
||||
}
|
||||
|
||||
type iphelperTcp6Table struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366905(v=vs.85).aspx
|
||||
numEntries uint32
|
||||
table [4096]iphelperTcp6Row
|
||||
}
|
||||
|
||||
type iphelperTcp6Row struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366896(v=vs.85).aspx
|
||||
localAddr [16]byte
|
||||
localScopeId uint32
|
||||
localPort uint32
|
||||
remoteAddr [16]byte
|
||||
remoteScopeId uint32
|
||||
remotePort uint32
|
||||
state uint32
|
||||
owningPid uint32
|
||||
}
|
||||
|
||||
type iphelperUdpTable struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366932(v=vs.85).aspx
|
||||
numEntries uint32
|
||||
table [4096]iphelperUdpRow
|
||||
}
|
||||
|
||||
type iphelperUdpRow struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366928(v=vs.85).aspx
|
||||
localAddr uint32
|
||||
localPort uint32
|
||||
owningPid uint32
|
||||
}
|
||||
|
||||
type iphelperUdp6Table struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366925(v=vs.85).aspx
|
||||
numEntries uint32
|
||||
table [4096]iphelperUdp6Row
|
||||
}
|
||||
|
||||
type iphelperUdp6Row struct {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa366923(v=vs.85).aspx
|
||||
localAddr [16]byte
|
||||
localScopeId uint32
|
||||
localPort uint32
|
||||
owningPid uint32
|
||||
}
|
||||
|
||||
const (
|
||||
IPv4 uint8 = 4
|
||||
IPv6 uint8 = 6
|
||||
|
||||
TCP uint8 = 6
|
||||
UDP uint8 = 17
|
||||
)
|
||||
|
||||
func (ipHelper *IPHelper) GetTables(protocol uint8, ipVersion uint8) (connections []*connectionEntry, listeners []*connectionEntry, err error) {
|
||||
// docs: https://msdn.microsoft.com/en-us/library/windows/desktop/aa365928(v=vs.85).aspx
|
||||
|
||||
if !ipHelper.valid.IsSet() {
|
||||
return nil, nil, errInvalid
|
||||
}
|
||||
|
||||
var afClass int
|
||||
switch ipVersion {
|
||||
case IPv4:
|
||||
afClass = windows.AF_INET
|
||||
case IPv6:
|
||||
afClass = windows.AF_INET6
|
||||
default:
|
||||
return nil, nil, errors.New("invalid protocol")
|
||||
}
|
||||
|
||||
bufSize := 4096
|
||||
buf := make([]byte, bufSize)
|
||||
var r1 uintptr
|
||||
|
||||
switch protocol {
|
||||
case TCP:
|
||||
r1, _, err = ipHelper.getExtendedTcpTable.Call(
|
||||
uintptr(unsafe.Pointer(&buf[0])), // _Out_ PVOID pTcpTable
|
||||
uintptr(unsafe.Pointer(&bufSize)), // _Inout_ PDWORD pdwSize
|
||||
0, // _In_ BOOL bOrder
|
||||
uintptr(afClass), // _In_ ULONG ulAf
|
||||
iphelper_TCP_TABLE_OWNER_PID_ALL, // _In_ TCP_TABLE_CLASS TableClass
|
||||
0, // _In_ ULONG Reserved
|
||||
)
|
||||
case UDP:
|
||||
r1, _, err = ipHelper.getExtendedUdpTable.Call(
|
||||
uintptr(unsafe.Pointer(&buf[0])), // _Out_ PVOID pUdpTable,
|
||||
uintptr(unsafe.Pointer(&bufSize)), // _Inout_ PDWORD pdwSize,
|
||||
0, // _In_ BOOL bOrder,
|
||||
uintptr(afClass), // _In_ ULONG ulAf,
|
||||
iphelper_UDP_TABLE_OWNER_PID, // _In_ UDP_TABLE_CLASS TableClass,
|
||||
0, // _In_ ULONG Reserved
|
||||
)
|
||||
}
|
||||
|
||||
switch r1 {
|
||||
// case windows.ERROR_INSUFFICIENT_BUFFER:
|
||||
// return nil, fmt.Errorf("insufficient buffer error: %s", err)
|
||||
// case windows.ERROR_INVALID_PARAMETER:
|
||||
// return nil, fmt.Errorf("invalid parameter: %s", err)
|
||||
case windows.NO_ERROR:
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unexpected error: %s", err)
|
||||
}
|
||||
|
||||
// parse output
|
||||
switch {
|
||||
case protocol == TCP && ipVersion == IPv4:
|
||||
|
||||
tcpTable := (*iphelperTcpTable)(unsafe.Pointer(&buf[0]))
|
||||
table := tcpTable.table[:tcpTable.numEntries]
|
||||
|
||||
for _, row := range table {
|
||||
new := &connectionEntry{}
|
||||
|
||||
// PID
|
||||
new.pid = int(row.owningPid)
|
||||
|
||||
// local
|
||||
new.localIP = make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(new.localIP, row.localAddr)
|
||||
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
|
||||
|
||||
// remote
|
||||
if row.state == iphelper_TCP_STATE_LISTEN {
|
||||
if new.localIP.Equal(net.IPv4zero) {
|
||||
new.localIP = nil
|
||||
}
|
||||
listeners = append(listeners, new)
|
||||
} else {
|
||||
new.remoteIP = make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(new.remoteIP, row.remoteAddr)
|
||||
new.remotePort = uint16(row.remotePort>>8 | row.remotePort<<8)
|
||||
connections = append(connections, new)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case protocol == TCP && ipVersion == IPv6:
|
||||
|
||||
tcpTable := (*iphelperTcp6Table)(unsafe.Pointer(&buf[0]))
|
||||
table := tcpTable.table[:tcpTable.numEntries]
|
||||
|
||||
for _, row := range table {
|
||||
new := &connectionEntry{}
|
||||
|
||||
// PID
|
||||
new.pid = int(row.owningPid)
|
||||
|
||||
// local
|
||||
new.localIP = net.IP(row.localAddr[:])
|
||||
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
|
||||
|
||||
// remote
|
||||
if row.state == iphelper_TCP_STATE_LISTEN {
|
||||
if new.localIP.Equal(net.IPv6zero) {
|
||||
new.localIP = nil
|
||||
}
|
||||
listeners = append(listeners, new)
|
||||
} else {
|
||||
new.remoteIP = net.IP(row.remoteAddr[:])
|
||||
new.remotePort = uint16(row.remotePort>>8 | row.remotePort<<8)
|
||||
connections = append(connections, new)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case protocol == UDP && ipVersion == IPv4:
|
||||
|
||||
udpTable := (*iphelperUdpTable)(unsafe.Pointer(&buf[0]))
|
||||
table := udpTable.table[:udpTable.numEntries]
|
||||
|
||||
for _, row := range table {
|
||||
new := &connectionEntry{}
|
||||
|
||||
// PID
|
||||
new.pid = int(row.owningPid)
|
||||
|
||||
// local
|
||||
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
|
||||
if row.localAddr == 0 {
|
||||
listeners = append(listeners, new)
|
||||
} else {
|
||||
new.localIP = make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(new.localIP, row.localAddr)
|
||||
connections = append(connections, new)
|
||||
}
|
||||
}
|
||||
|
||||
case protocol == UDP && ipVersion == IPv6:
|
||||
|
||||
udpTable := (*iphelperUdp6Table)(unsafe.Pointer(&buf[0]))
|
||||
table := udpTable.table[:udpTable.numEntries]
|
||||
|
||||
for _, row := range table {
|
||||
new := &connectionEntry{}
|
||||
|
||||
// PID
|
||||
new.pid = int(row.owningPid)
|
||||
|
||||
// local
|
||||
new.localIP = net.IP(row.localAddr[:])
|
||||
new.localPort = uint16(row.localPort>>8 | row.localPort<<8)
|
||||
if new.localIP.Equal(net.IPv6zero) {
|
||||
new.localIP = nil
|
||||
listeners = append(listeners, new)
|
||||
} else {
|
||||
connections = append(connections, new)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return connections, listeners, nil
|
||||
}
|
||||
62
process/iphelper/test/main.go
Normal file
62
process/iphelper/test/main.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// +build windows
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/Safing/safing-core/process/iphelper"
|
||||
)
|
||||
|
||||
func main() {
|
||||
iph, err := iphelper.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("TCP4\n")
|
||||
conns, lConns, err := iph.GetTables(iphelper.TCP, iphelper.IPv4)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Connections:\n")
|
||||
for _, conn := range conns {
|
||||
fmt.Printf("%s\n", conn)
|
||||
}
|
||||
fmt.Printf("Listeners:\n")
|
||||
for _, conn := range lConns {
|
||||
fmt.Printf("%s\n", conn)
|
||||
}
|
||||
|
||||
fmt.Printf("\nTCP6\n")
|
||||
conns, lConns, err = iph.GetTables(iphelper.TCP, iphelper.IPv6)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Printf("Connections:\n")
|
||||
for _, conn := range conns {
|
||||
fmt.Printf("%s\n", conn)
|
||||
}
|
||||
fmt.Printf("Listeners:\n")
|
||||
for _, conn := range lConns {
|
||||
fmt.Printf("%s\n", conn)
|
||||
}
|
||||
|
||||
fmt.Printf("\nUDP4\n")
|
||||
_, lConns, err = iph.GetTables(iphelper.UDP, iphelper.IPv4)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, conn := range lConns {
|
||||
fmt.Printf("%s\n", conn)
|
||||
}
|
||||
|
||||
fmt.Printf("\nUDP6\n")
|
||||
_, lConns, err = iph.GetTables(iphelper.UDP, iphelper.IPv6)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
for _, conn := range lConns {
|
||||
fmt.Printf("%s\n", conn)
|
||||
}
|
||||
}
|
||||
BIN
process/iphelper/test/test.exe
Normal file
BIN
process/iphelper/test/test.exe
Normal file
Binary file not shown.
56
process/proc/gather.go
Normal file
56
process/proc/gather.go
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Success uint8 = iota
|
||||
NoSocket
|
||||
NoProcess
|
||||
)
|
||||
|
||||
func GetPidOfConnection(localIP *net.IP, localPort uint16, protocol uint8) (pid int, status uint8) {
|
||||
uid, inode, ok := getConnectionSocket(localIP, localPort, protocol)
|
||||
if !ok {
|
||||
uid, inode, ok = getListeningSocket(localIP, localPort, protocol)
|
||||
for i := 0; i < 3 && !ok; i++ {
|
||||
// give kernel some time, then try again
|
||||
// log.Tracef("process: giving kernel some time to think")
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
uid, inode, ok = getConnectionSocket(localIP, localPort, protocol)
|
||||
if !ok {
|
||||
uid, inode, ok = getListeningSocket(localIP, localPort, protocol)
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return -1, NoSocket
|
||||
}
|
||||
}
|
||||
pid, ok = GetPidOfInode(uid, inode)
|
||||
for i := 0; i < 3 && !ok; i++ {
|
||||
// give kernel some time, then try again
|
||||
// log.Tracef("process: giving kernel some time to think")
|
||||
time.Sleep(15 * time.Millisecond)
|
||||
pid, ok = GetPidOfInode(uid, inode)
|
||||
}
|
||||
if !ok {
|
||||
return -1, NoProcess
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func GetPidOfIncomingConnection(localIP *net.IP, localPort uint16, protocol uint8) (pid int, status uint8) {
|
||||
uid, inode, ok := getListeningSocket(localIP, localPort, protocol)
|
||||
if !ok {
|
||||
return -1, NoSocket
|
||||
}
|
||||
pid, ok = GetPidOfInode(uid, inode)
|
||||
if !ok {
|
||||
return -1, NoProcess
|
||||
}
|
||||
return
|
||||
}
|
||||
56
process/proc/get.go
Normal file
56
process/proc/get.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package proc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
)
|
||||
|
||||
func GetTCP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
return search(TCP4, localIP, localPort, direction)
|
||||
}
|
||||
|
||||
func GetTCP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
return search(TCP6, localIP, localPort, direction)
|
||||
}
|
||||
|
||||
func GetUDP4PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
return search(UDP4, localIP, localPort, direction)
|
||||
}
|
||||
|
||||
func GetUDP6PacketInfo(localIP net.IP, localPort uint16, remoteIP net.IP, remotePort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
return search(UDP6, localIP, localPort, direction)
|
||||
}
|
||||
|
||||
func search(protocol uint8, localIP net.IP, localPort uint16, pktDirection bool) (pid int, direction bool, err error) {
|
||||
|
||||
var status uint8
|
||||
if pktDirection {
|
||||
pid, status = GetPidOfIncomingConnection(&localIP, localPort, protocol)
|
||||
if pid >= 0 {
|
||||
return pid, true, nil
|
||||
}
|
||||
// pid, status = GetPidOfConnection(&localIP, localPort, protocol)
|
||||
// if pid >= 0 {
|
||||
// return pid, false, nil
|
||||
// }
|
||||
} else {
|
||||
pid, status = GetPidOfConnection(&localIP, localPort, protocol)
|
||||
if pid >= 0 {
|
||||
return pid, false, nil
|
||||
}
|
||||
// pid, status = GetPidOfIncomingConnection(&localIP, localPort, protocol)
|
||||
// if pid >= 0 {
|
||||
// return pid, true, nil
|
||||
// }
|
||||
}
|
||||
|
||||
switch status {
|
||||
case NoSocket:
|
||||
return -1, direction, errors.New("could not find socket")
|
||||
case NoProcess:
|
||||
return -1, direction, errors.New("could not find PID")
|
||||
default:
|
||||
return -1, direction, nil
|
||||
}
|
||||
|
||||
}
|
||||
163
process/proc/processfinder.go
Normal file
163
process/proc/processfinder.go
Normal file
@@ -0,0 +1,163 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Safing/safing-core/log"
|
||||
)
|
||||
|
||||
var (
|
||||
pidsByUserLock sync.Mutex
|
||||
pidsByUser = make(map[int][]int)
|
||||
)
|
||||
|
||||
func GetPidOfInode(uid, inode int) (int, bool) {
|
||||
pidsByUserLock.Lock()
|
||||
defer pidsByUserLock.Unlock()
|
||||
|
||||
pidsUpdated := false
|
||||
|
||||
// get pids of user, update if missing
|
||||
pids, ok := pidsByUser[uid]
|
||||
if !ok {
|
||||
// log.Trace("process: no processes of user, updating table")
|
||||
updatePids()
|
||||
pidsUpdated = true
|
||||
pids, ok = pidsByUser[uid]
|
||||
}
|
||||
if ok {
|
||||
// if user has pids, start checking them first
|
||||
var checkedUserPids []int
|
||||
for _, possiblePID := range pids {
|
||||
if findSocketFromPid(possiblePID, inode) {
|
||||
return possiblePID, true
|
||||
}
|
||||
checkedUserPids = append(checkedUserPids, possiblePID)
|
||||
}
|
||||
// if we fail on the first run and have not updated, update and check the ones we haven't tried so far.
|
||||
if !pidsUpdated {
|
||||
// log.Trace("process: socket not found in any process of user, updating table")
|
||||
// update
|
||||
updatePids()
|
||||
// sort for faster search
|
||||
for i, j := 0, len(checkedUserPids)-1; i < j; i, j = i+1, j-1 {
|
||||
checkedUserPids[i], checkedUserPids[j] = checkedUserPids[j], checkedUserPids[i]
|
||||
}
|
||||
len := len(checkedUserPids)
|
||||
// check unchecked pids
|
||||
for _, possiblePID := range pids {
|
||||
// only check if not already checked
|
||||
if sort.SearchInts(checkedUserPids, possiblePID) == len {
|
||||
if findSocketFromPid(possiblePID, inode) {
|
||||
return possiblePID, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// check all other pids
|
||||
// log.Trace("process: socket not found in any process of user, checking all pids")
|
||||
// TODO: find best order for pidsByUser for best performance
|
||||
for possibleUID, pids := range pidsByUser {
|
||||
if possibleUID != uid {
|
||||
for _, possiblePID := range pids {
|
||||
if findSocketFromPid(possiblePID, inode) {
|
||||
return possiblePID, true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return -1, false
|
||||
}
|
||||
|
||||
func findSocketFromPid(pid, inode int) bool {
|
||||
socketName := fmt.Sprintf("socket:[%d]", inode)
|
||||
entries := readDirNames(fmt.Sprintf("/proc/%d/fd", pid))
|
||||
if len(entries) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
link, err := os.Readlink(fmt.Sprintf("/proc/%d/fd/%s", pid, entry))
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Warningf("process: failed to read link /proc/%d/fd/%s: %s", pid, entry, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if link == socketName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func updatePids() {
|
||||
pidsByUser = make(map[int][]int)
|
||||
|
||||
entries := readDirNames("/proc")
|
||||
if len(entries) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
entryLoop:
|
||||
for _, entry := range entries {
|
||||
pid, err := strconv.ParseInt(entry, 10, 32)
|
||||
if err != nil {
|
||||
continue entryLoop
|
||||
}
|
||||
|
||||
statData, err := os.Stat(fmt.Sprintf("/proc/%d", pid))
|
||||
if err != nil {
|
||||
log.Warningf("process: could not stat /proc/%d: %s", pid, err)
|
||||
continue entryLoop
|
||||
}
|
||||
sys, ok := statData.Sys().(*syscall.Stat_t)
|
||||
if !ok {
|
||||
log.Warningf("process: unable to parse /proc/%d: wrong type", pid)
|
||||
continue entryLoop
|
||||
}
|
||||
|
||||
pids, ok := pidsByUser[int(sys.Uid)]
|
||||
if ok {
|
||||
pidsByUser[int(sys.Uid)] = append(pids, int(pid))
|
||||
} else {
|
||||
pidsByUser[int(sys.Uid)] = []int{int(pid)}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for _, slice := range pidsByUser {
|
||||
for i, j := 0, len(slice)-1; i < j; i, j = i+1, j-1 {
|
||||
slice[i], slice[j] = slice[j], slice[i]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func readDirNames(dir string) (names []string) {
|
||||
file, err := os.Open(dir)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
log.Warningf("process: could not open directory %s: %s", dir, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
names, err = file.Readdirnames(0)
|
||||
if err != nil {
|
||||
log.Warningf("process: could not get entries from direcotry %s: %s", dir, err)
|
||||
return []string{}
|
||||
}
|
||||
return
|
||||
}
|
||||
18
process/proc/processfinder_test.go
Normal file
18
process/proc/processfinder_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestProcessFinder(t *testing.T) {
|
||||
|
||||
updatePids()
|
||||
log.Printf("pidsByUser: %v", pidsByUser)
|
||||
|
||||
pid, _ := GetPidOfInode(1000, 112588)
|
||||
log.Printf("pid: %d", pid)
|
||||
|
||||
}
|
||||
381
process/proc/sockets.go
Normal file
381
process/proc/sockets.go
Normal file
@@ -0,0 +1,381 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"unicode"
|
||||
|
||||
"github.com/Safing/safing-core/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!
|
||||
|
||||
*/
|
||||
|
||||
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"
|
||||
|
||||
TCP_ESTABLISHED = iota + 1
|
||||
TCP_SYN_SENT
|
||||
TCP_SYN_RECV
|
||||
TCP_FIN_WAIT1
|
||||
TCP_FIN_WAIT2
|
||||
TCP_TIME_WAIT
|
||||
TCP_CLOSE
|
||||
TCP_CLOSE_WAIT
|
||||
TCP_LAST_ACK
|
||||
TCP_LISTEN
|
||||
TCP_CLOSING
|
||||
TCP_NEW_SYN_RECV
|
||||
)
|
||||
|
||||
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 -1, -1, 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 -1, -1, 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 0, 0, 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")
|
||||
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")
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
40
process/proc/sockets_test.go
Normal file
40
process/proc/sockets_test.go
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package proc
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestSockets(t *testing.T) {
|
||||
|
||||
updateListeners(TCP4)
|
||||
updateListeners(UDP4)
|
||||
updateListeners(TCP6)
|
||||
updateListeners(UDP6)
|
||||
t.Logf("addressListeningTCP4: %v", addressListeningTCP4)
|
||||
t.Logf("globalListeningTCP4: %v", globalListeningTCP4)
|
||||
t.Logf("addressListeningUDP4: %v", addressListeningUDP4)
|
||||
t.Logf("globalListeningUDP4: %v", globalListeningUDP4)
|
||||
t.Logf("addressListeningTCP6: %v", addressListeningTCP6)
|
||||
t.Logf("globalListeningTCP6: %v", globalListeningTCP6)
|
||||
t.Logf("addressListeningUDP6: %v", addressListeningUDP6)
|
||||
t.Logf("globalListeningUDP6: %v", globalListeningUDP6)
|
||||
|
||||
getListeningSocket(&net.IPv4zero, 53, TCP4)
|
||||
getListeningSocket(&net.IPv4zero, 53, UDP4)
|
||||
getListeningSocket(&net.IPv6zero, 53, TCP6)
|
||||
getListeningSocket(&net.IPv6zero, 53, UDP6)
|
||||
|
||||
// spotify: 192.168.0.102:5353 192.121.140.65:80
|
||||
localIP := net.IPv4(192, 168, 127, 10)
|
||||
uid, inode, ok := getConnectionSocket(&localIP, 46634, TCP4)
|
||||
t.Logf("getConnectionSocket: %d %d %v", uid, inode, ok)
|
||||
|
||||
activeConnectionIDs := GetActiveConnectionIDs()
|
||||
for _, connID := range activeConnectionIDs {
|
||||
t.Logf("active: %s", connID)
|
||||
}
|
||||
|
||||
}
|
||||
251
process/process.go
Normal file
251
process/process.go
Normal file
@@ -0,0 +1,251 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
datastore "github.com/ipfs/go-datastore"
|
||||
processInfo "github.com/shirou/gopsutil/process"
|
||||
|
||||
"github.com/Safing/safing-core/database"
|
||||
"github.com/Safing/safing-core/log"
|
||||
"github.com/Safing/safing-core/profiles"
|
||||
)
|
||||
|
||||
// A Process represents a process running on the operating system
|
||||
type Process struct {
|
||||
database.Base
|
||||
UserID int
|
||||
UserName string
|
||||
UserHome string
|
||||
Pid int
|
||||
ParentPid int
|
||||
Path string
|
||||
Cwd string
|
||||
FileInfo *FileInfo
|
||||
CmdLine string
|
||||
FirstArg string
|
||||
ProfileKey string
|
||||
Profile *profiles.Profile
|
||||
Name string
|
||||
Icon string
|
||||
// Icon is a path to the icon and is either prefixed "f:" for filepath, "d:" for database cache path or "c:"/"a:" for a the icon key to fetch it from a company / authoritative node and cache it in its own cache.
|
||||
}
|
||||
|
||||
var processModel *Process // only use this as parameter for database.EnsureModel-like functions
|
||||
|
||||
func init() {
|
||||
database.RegisterModel(processModel, func() database.Model { return new(Process) })
|
||||
}
|
||||
|
||||
// Create saves Process with the provided name in the default namespace.
|
||||
func (m *Process) Create(name string) error {
|
||||
return m.CreateObject(&database.Processes, name, m)
|
||||
}
|
||||
|
||||
// CreateInNamespace saves Process with the provided name in the provided namespace.
|
||||
func (m *Process) CreateInNamespace(namespace *datastore.Key, name string) error {
|
||||
return m.CreateObject(namespace, name, m)
|
||||
}
|
||||
|
||||
// Save saves Process.
|
||||
func (m *Process) Save() error {
|
||||
return m.SaveObject(m)
|
||||
}
|
||||
|
||||
// GetProcess fetches Process with the provided name from the default namespace.
|
||||
func GetProcess(name string) (*Process, error) {
|
||||
return GetProcessFromNamespace(&database.Processes, name)
|
||||
}
|
||||
|
||||
// GetProcessFromNamespace fetches Process with the provided name from the provided namespace.
|
||||
func GetProcessFromNamespace(namespace *datastore.Key, name string) (*Process, error) {
|
||||
object, err := database.GetAndEnsureModel(namespace, name, processModel)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
model, ok := object.(*Process)
|
||||
if !ok {
|
||||
return nil, database.NewMismatchError(object, processModel)
|
||||
}
|
||||
return model, nil
|
||||
}
|
||||
|
||||
func (m *Process) String() string {
|
||||
if m == nil {
|
||||
return "?"
|
||||
}
|
||||
if m.Profile != nil && !m.Profile.Default {
|
||||
return fmt.Sprintf("%s:%s:%d", m.UserName, m.Profile, m.Pid)
|
||||
}
|
||||
return fmt.Sprintf("%s:%s:%d", m.UserName, m.Path, m.Pid)
|
||||
}
|
||||
|
||||
func GetOrFindProcess(pid int) (*Process, error) {
|
||||
process, err := GetProcess(strconv.Itoa(pid))
|
||||
if err == nil {
|
||||
return process, nil
|
||||
}
|
||||
|
||||
new := &Process{
|
||||
Pid: pid,
|
||||
}
|
||||
|
||||
switch {
|
||||
case (pid == 0 && runtime.GOOS == "linux") || (pid == 4 && runtime.GOOS == "windows"):
|
||||
new.UserName = "Kernel"
|
||||
new.Name = "Operating System"
|
||||
new.Profile = &profiles.Profile{
|
||||
Name: "OS",
|
||||
Flags: []int8{profiles.Internet, profiles.LocalNet, profiles.Directconnect, profiles.Service},
|
||||
}
|
||||
default:
|
||||
|
||||
pInfo, err := processInfo.NewProcess(int32(pid))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// UID
|
||||
// net yet implemented for windows
|
||||
if runtime.GOOS == "linux" {
|
||||
uids, err := pInfo.Uids()
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get UID: %s", err)
|
||||
} else {
|
||||
new.UserID = int(uids[0])
|
||||
}
|
||||
}
|
||||
|
||||
// Username
|
||||
new.UserName, err = pInfo.Username()
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get Username: %s", err)
|
||||
}
|
||||
|
||||
// TODO: User Home
|
||||
// new.UserHome, err =
|
||||
|
||||
// PPID
|
||||
ppid, err := pInfo.Ppid()
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get PPID: %s", err)
|
||||
} else {
|
||||
new.ParentPid = int(ppid)
|
||||
}
|
||||
|
||||
// Path
|
||||
new.Path, err = pInfo.Exe()
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get Path: %s", err)
|
||||
}
|
||||
|
||||
// Current working directory
|
||||
// net yet implemented for windows
|
||||
// new.Cwd, err = pInfo.Cwd()
|
||||
// if err != nil {
|
||||
// log.Warningf("process: failed to get Cwd: %s", err)
|
||||
// }
|
||||
|
||||
// Command line arguments
|
||||
new.CmdLine, err = pInfo.Cmdline()
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get Cmdline: %s", err)
|
||||
}
|
||||
|
||||
// Name
|
||||
new.Name, err = pInfo.Name()
|
||||
if err != nil {
|
||||
log.Warningf("process: failed to get Name: %s", err)
|
||||
}
|
||||
|
||||
// TODO: App Icon
|
||||
// new.Icon, err =
|
||||
|
||||
// get Profile
|
||||
processPath := new.Path
|
||||
var applyProfile *profiles.Profile
|
||||
iterations := 0
|
||||
for applyProfile == nil {
|
||||
|
||||
iterations++
|
||||
if iterations > 10 {
|
||||
log.Warningf("process: got into loop while getting profile for %s", new)
|
||||
break
|
||||
}
|
||||
|
||||
applyProfile, err = profiles.GetActiveProfileByPath(processPath)
|
||||
if err == database.ErrNotFound {
|
||||
applyProfile, err = profiles.FindProfileByPath(processPath, new.UserHome)
|
||||
}
|
||||
if err != nil {
|
||||
log.Warningf("process: could not get profile for %s: %s", new, err)
|
||||
} else if applyProfile == nil {
|
||||
log.Warningf("process: no default profile found for %s", new)
|
||||
} else {
|
||||
|
||||
// TODO: there is a lot of undefined behaviour if chaining framework profiles
|
||||
|
||||
// process framework
|
||||
if applyProfile.Framework != nil {
|
||||
if applyProfile.Framework.FindParent > 0 {
|
||||
var ppid int32
|
||||
for i := uint8(1); i < applyProfile.Framework.FindParent; i++ {
|
||||
parent, err := pInfo.Parent()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ppid = parent.Pid
|
||||
}
|
||||
if applyProfile.Framework.MergeWithParent {
|
||||
return GetOrFindProcess(int(ppid))
|
||||
}
|
||||
// processPath, err = os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
|
||||
// if err != nil {
|
||||
// return nil, fmt.Errorf("could not read /proc/%d/exe: %s", pid, err)
|
||||
// }
|
||||
continue
|
||||
}
|
||||
|
||||
newCommand, err := applyProfile.Framework.GetNewPath(new.CmdLine, new.Cwd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// assign
|
||||
new.CmdLine = newCommand
|
||||
new.Path = strings.SplitN(newCommand, " ", 2)[0]
|
||||
processPath = new.Path
|
||||
|
||||
// make sure we loop
|
||||
applyProfile = nil
|
||||
continue
|
||||
}
|
||||
|
||||
// apply profile to process
|
||||
log.Debugf("process: applied profile to %s: %s", new, applyProfile)
|
||||
new.Profile = applyProfile
|
||||
new.ProfileKey = applyProfile.GetKey().String()
|
||||
|
||||
// update Profile with Process icon if Profile does not have one
|
||||
if !new.Profile.Default && new.Icon != "" && new.Profile.Icon == "" {
|
||||
new.Profile.Icon = new.Icon
|
||||
new.Profile.Save()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get FileInfo
|
||||
new.FileInfo = GetFileInfo(new.Path)
|
||||
|
||||
}
|
||||
|
||||
// save to DB
|
||||
new.Create(strconv.Itoa(new.Pid))
|
||||
|
||||
return new, nil
|
||||
}
|
||||
13
process/process_linux.go
Normal file
13
process/process_linux.go
Normal file
@@ -0,0 +1,13 @@
|
||||
package process
|
||||
|
||||
func (m *Process) IsUser() bool {
|
||||
return m.UserID >= 1000
|
||||
}
|
||||
|
||||
func (m *Process) IsAdmin() bool {
|
||||
return m.UserID >= 0
|
||||
}
|
||||
|
||||
func (m *Process) IsSystem() bool {
|
||||
return m.UserID == 0
|
||||
}
|
||||
16
process/process_windows.go
Normal file
16
process/process_windows.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package process
|
||||
|
||||
import "strings"
|
||||
|
||||
func (m *Process) IsUser() bool {
|
||||
return m.Pid != 4 && // Kernel
|
||||
!strings.HasPrefix(m.UserName, "NT-") // NT-Authority (localized!)
|
||||
}
|
||||
|
||||
func (m *Process) IsAdmin() bool {
|
||||
return strings.HasPrefix(m.UserName, "NT-") // NT-Authority (localized!)
|
||||
}
|
||||
|
||||
func (m *Process) IsSystem() bool {
|
||||
return m.Pid == 4
|
||||
}
|
||||
219
process/xdgicons/humanInfo.go
Normal file
219
process/xdgicons/humanInfo.go
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/Safing/safing-core/log"
|
||||
)
|
||||
|
||||
var (
|
||||
humanInfoCache map[string]*humanInfo
|
||||
humanInfoCacheNamesOnly map[string]*humanInfo
|
||||
nextHumanInfoCacheUpdate time.Time
|
||||
|
||||
searchPathCache map[string]string
|
||||
nextSearchPathCacheUpdate time.Time
|
||||
|
||||
humanInfoCacheLock sync.Mutex
|
||||
)
|
||||
|
||||
type humanInfo struct {
|
||||
Name string
|
||||
IconName string
|
||||
IconPath string
|
||||
ExecCmd string
|
||||
}
|
||||
|
||||
func (m *Process) GetHumanInfo() {
|
||||
humanInfoCacheLock.Lock()
|
||||
defer humanInfoCacheLock.Unlock()
|
||||
|
||||
// TODO: do some proper fuzzy matching to get these icons!
|
||||
// take special care of:
|
||||
// - flatpak
|
||||
// - snapd
|
||||
|
||||
fuzzyMatch := false
|
||||
info, ok := humanInfoCache[m.Path]
|
||||
if !ok {
|
||||
splitted := strings.Split(m.Path, "/")
|
||||
nameOnly := splitted[len(splitted)-1]
|
||||
info, ok = humanInfoCacheNamesOnly[nameOnly]
|
||||
if ok {
|
||||
fuzzyMatch = true
|
||||
} else {
|
||||
updateHumanInfoCache()
|
||||
info, ok = humanInfoCache[m.Path]
|
||||
if !ok {
|
||||
fuzzyMatch = true
|
||||
info, ok = humanInfoCacheNamesOnly[nameOnly]
|
||||
if !ok {
|
||||
if len(splitted) > 3 {
|
||||
info, ok = humanInfoCacheNamesOnly[splitted[len(splitted)-2]]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if info.IconPath == "" && info.IconName != "" {
|
||||
info.IconPath = getIconPathFromName(info.IconName)
|
||||
}
|
||||
|
||||
if !fuzzyMatch {
|
||||
m.Name = info.Name
|
||||
}
|
||||
if info.IconPath != "" {
|
||||
m.Icon = "f:" + info.IconPath
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func updateHumanInfoCache() {
|
||||
if time.Now().Before(nextHumanInfoCacheUpdate) {
|
||||
return
|
||||
}
|
||||
humanInfoCache = make(map[string]*humanInfo)
|
||||
humanInfoCacheNamesOnly = make(map[string]*humanInfo)
|
||||
log.Tracef("process: updating human info cache")
|
||||
for _, starterDir := range starterLocations {
|
||||
for _, dirEntry := range readDirNames(starterDir) {
|
||||
|
||||
// only look at .desktop files
|
||||
if !strings.HasSuffix(dirEntry, ".desktop") {
|
||||
continue
|
||||
}
|
||||
|
||||
execCmd, name, iconPath := getStarterInfo(filepath.Join(starterDir, dirEntry))
|
||||
|
||||
if execCmd == "" {
|
||||
continue
|
||||
}
|
||||
execPath := strings.SplitN(execCmd, " ", 2)[0]
|
||||
|
||||
var ok bool
|
||||
if !strings.HasPrefix(execPath, "/") {
|
||||
execPath, ok = searchPathCache[execPath]
|
||||
if !ok {
|
||||
updateSearchPathCache()
|
||||
execPath, ok = searchPathCache[execPath]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new := humanInfo{
|
||||
Name: name,
|
||||
}
|
||||
if strings.HasPrefix(iconPath, "/") {
|
||||
new.IconPath = iconPath
|
||||
} else {
|
||||
new.IconName = iconPath
|
||||
}
|
||||
humanInfoCache[execPath] = &new
|
||||
splitted := strings.Split(execPath, "/")
|
||||
humanInfoCacheNamesOnly[splitted[len(splitted)-1]] = &new
|
||||
// log.Tracef("process: new cache entry: %s - %s - %s - %s", execPath, name, new.IconName, new.IconPath)
|
||||
|
||||
}
|
||||
}
|
||||
nextHumanInfoCacheUpdate = time.Now().Add(10 * time.Second)
|
||||
}
|
||||
|
||||
func getStarterInfo(filePath string) (execCmd, name, iconPath string) {
|
||||
// open file
|
||||
file, err := os.Open(filePath)
|
||||
if err != nil {
|
||||
log.Warningf("process: could not read %s: %s", filePath, err)
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// file scanner
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
// parse
|
||||
seen := 0
|
||||
scanner.Scan() // skip first line
|
||||
for scanner.Scan() {
|
||||
line := strings.SplitN(scanner.Text(), "=", 2)
|
||||
if len(line) < 2 {
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case line[0] == "Name":
|
||||
if name == "" {
|
||||
name = line[1]
|
||||
seen += 1
|
||||
}
|
||||
case line[0] == "Exec":
|
||||
if execCmd == "" {
|
||||
execCmd = line[1]
|
||||
seen += 1
|
||||
}
|
||||
case line[0] == "Icon":
|
||||
if iconPath == "" {
|
||||
iconPath = line[1]
|
||||
seen += 1
|
||||
}
|
||||
}
|
||||
if seen >= 3 {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func updateSearchPathCache() {
|
||||
if time.Now().Before(nextSearchPathCacheUpdate) {
|
||||
return
|
||||
}
|
||||
searchPathCache = make(map[string]string)
|
||||
for _, searchEntry := range strings.Split(os.Getenv("PATH"), ":") {
|
||||
names := readDirNames(searchEntry)
|
||||
for _, name := range names {
|
||||
// only save first occurence
|
||||
if _, ok := searchPathCache[name]; !ok {
|
||||
searchPathCache[name] = filepath.Join(searchEntry, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
nextSearchPathCacheUpdate = time.Now().Add(10 * time.Second)
|
||||
}
|
||||
|
||||
func getIconPathFromName(iconName string) string {
|
||||
for _, location := range xdgIconLocations {
|
||||
// skip everything that does not exist or we do not have access to.
|
||||
if _, err := os.Stat(location); err != nil {
|
||||
continue
|
||||
}
|
||||
// check for icon
|
||||
for _, path := range xdgIconPaths {
|
||||
possibleIcon := filepath.Join(location, path, iconName+".png")
|
||||
if _, err := os.Stat(possibleIcon); err == nil {
|
||||
return possibleIcon
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, location := range iconLocations {
|
||||
possibleIcon := filepath.Join(location, iconName+".png")
|
||||
if _, err := os.Stat(possibleIcon); err == nil {
|
||||
return possibleIcon
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
32
process/xdgicons/humanInfo_test.go
Normal file
32
process/xdgicons/humanInfo_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package process
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetHumanInfo(t *testing.T) {
|
||||
// gather processes for testing
|
||||
pids := readDirNames("/proc")
|
||||
var allProcs []*Process
|
||||
for _, pidString := range pids {
|
||||
pid, err := strconv.ParseInt(pidString, 10, 32)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
next, err := GetOrFindProcess(int(pid))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
allProcs = append(allProcs, next)
|
||||
}
|
||||
|
||||
// test
|
||||
for _, process := range allProcs {
|
||||
process.GetHumanInfo()
|
||||
fmt.Printf("%d - %s - %s - %s\n", process.Pid, process.Path, process.Name, process.Icon)
|
||||
}
|
||||
}
|
||||
39
process/xdgicons/locations.go
Normal file
39
process/xdgicons/locations.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright Safing ICS Technologies GmbH. Use of this source code is governed by the AGPL license that can be found in the LICENSE file.
|
||||
|
||||
package process
|
||||
|
||||
// spec: https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html
|
||||
// alternatives:
|
||||
// gtk3 lib: https://developer.gnome.org/gtk3/stable/GtkIconTheme.html
|
||||
// qt lib: ?
|
||||
|
||||
var (
|
||||
starterLocations = []string{
|
||||
"/usr/share/applications",
|
||||
"/var/lib/flatpak/exports/share/applications/",
|
||||
}
|
||||
|
||||
iconLocations = []string{
|
||||
"/usr/share/pixmaps",
|
||||
}
|
||||
|
||||
xdgIconLocations = []string{
|
||||
"/usr/share/default",
|
||||
"/usr/share/gnome",
|
||||
"/var/lib/flatpak/exports/share",
|
||||
"/usr/local/share",
|
||||
"/usr/share",
|
||||
"/var/lib/snapd/desktop",
|
||||
}
|
||||
|
||||
xdgIconPaths = []string{
|
||||
"icons/hicolor/512x512/apps",
|
||||
"icons/hicolor/256x256/apps",
|
||||
"icons/hicolor/192x192/apps",
|
||||
"icons/hicolor/128x128/apps",
|
||||
"icons/hicolor/96x96/apps",
|
||||
"icons/hicolor/72x72/apps",
|
||||
"icons/hicolor/64x64/apps",
|
||||
"icons/hicolor/48x48/apps",
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user