2018-04-02 05:25:32 +02:00
package main
import (
2021-02-18 17:21:50 +03:00
"bytes"
2020-07-17 01:29:58 +02:00
"context"
2018-04-02 05:25:32 +02:00
"flag"
2018-04-16 17:51:54 +02:00
"fmt"
2018-04-02 05:25:32 +02:00
"io/ioutil"
golog "log"
"os"
"os/signal"
2018-04-16 17:51:54 +02:00
"runtime"
"runtime/pprof"
2018-04-02 05:25:32 +02:00
"syscall"
2021-02-18 17:21:50 +03:00
"time"
2018-04-02 05:25:32 +02:00
2020-12-09 18:18:42 +01:00
"github.com/evilsocket/opensnitch/daemon/conman"
"github.com/evilsocket/opensnitch/daemon/core"
"github.com/evilsocket/opensnitch/daemon/dns"
"github.com/evilsocket/opensnitch/daemon/firewall"
"github.com/evilsocket/opensnitch/daemon/log"
"github.com/evilsocket/opensnitch/daemon/netfilter"
2021-04-05 09:28:16 +00:00
"github.com/evilsocket/opensnitch/daemon/procmon/monitor"
2020-12-09 18:18:42 +01:00
"github.com/evilsocket/opensnitch/daemon/rule"
"github.com/evilsocket/opensnitch/daemon/statistics"
"github.com/evilsocket/opensnitch/daemon/ui"
2018-04-02 05:25:32 +02:00
)
var (
2021-03-22 17:51:31 +01:00
showVersion = false
2021-02-18 17:21:50 +03:00
procmonMethod = ""
logFile = ""
rulesPath = "rules"
noLiveReload = false
queueNum = 0
repeatQueueNum int //will be set later to queueNum + 1
workers = 16
debug = false
warning = false
important = false
errorlog = false
2018-04-02 05:25:32 +02:00
2020-10-26 23:16:27 +01:00
uiSocket = ""
2018-04-07 01:52:43 +02:00
uiClient = ( * ui . Client ) ( nil )
2018-04-02 05:25:32 +02:00
2018-04-16 17:51:54 +02:00
cpuProfile = ""
memProfile = ""
2021-02-18 17:21:50 +03:00
ctx = ( context . Context ) ( nil )
cancel = ( context . CancelFunc ) ( nil )
err = ( error ) ( nil )
rules = ( * rule . Loader ) ( nil )
stats = ( * statistics . Statistics ) ( nil )
queue = ( * netfilter . Queue ) ( nil )
repeatPktChan = ( <- chan netfilter . Packet ) ( nil )
pktChan = ( <- chan netfilter . Packet ) ( nil )
wrkChan = ( chan netfilter . Packet ) ( nil )
sigChan = ( chan os . Signal ) ( nil )
exitChan = ( chan bool ) ( nil )
2018-04-02 05:25:32 +02:00
)
func init ( ) {
2021-03-22 17:51:31 +01:00
flag . BoolVar ( & showVersion , "version" , debug , "Show daemon version of this executable and exit." )
2021-04-05 09:28:16 +00:00
flag . StringVar ( & procmonMethod , "process-monitor-method" , procmonMethod , "How to search for processes path. Options: ftrace, audit (experimental), ebpf (experimental), proc (default)" )
2018-04-07 01:52:43 +02:00
flag . StringVar ( & uiSocket , "ui-socket" , uiSocket , "Path the UI gRPC service listener (https://github.com/grpc/grpc/blob/master/doc/naming.md)." )
2018-04-02 05:25:32 +02:00
flag . StringVar ( & rulesPath , "rules-path" , rulesPath , "Path to load JSON rules from." )
flag . IntVar ( & queueNum , "queue-num" , queueNum , "Netfilter queue number." )
flag . IntVar ( & workers , "workers" , workers , "Number of concurrent workers." )
2018-04-08 16:36:12 +02:00
flag . BoolVar ( & noLiveReload , "no-live-reload" , debug , "Disable rules live reloading." )
2018-04-05 17:41:12 +02:00
flag . StringVar ( & logFile , "log-file" , logFile , "Write logs to this file instead of the standard output." )
2020-11-02 01:37:35 +01:00
flag . BoolVar ( & debug , "debug" , debug , "Enable debug level logs." )
flag . BoolVar ( & warning , "warning" , warning , "Enable warning level logs." )
2020-02-28 10:24:08 +01:00
flag . BoolVar ( & important , "important" , important , "Enable important level logs." )
flag . BoolVar ( & errorlog , "error" , errorlog , "Enable error level logs." )
2018-04-16 17:51:54 +02:00
flag . StringVar ( & cpuProfile , "cpu-profile" , cpuProfile , "Write CPU profile to this file." )
flag . StringVar ( & memProfile , "mem-profile" , memProfile , "Write memory profile to this file." )
2018-04-02 05:25:32 +02:00
}
2020-11-02 01:37:35 +01:00
func overwriteLogging ( ) bool {
return debug || warning || important || errorlog || logFile != ""
}
2018-04-08 16:36:12 +02:00
func setupLogging ( ) {
golog . SetOutput ( ioutil . Discard )
if debug {
2020-06-14 20:14:24 +02:00
log . SetLogLevel ( log . DEBUG )
2020-02-28 10:24:08 +01:00
} else if warning {
2020-06-14 20:14:24 +02:00
log . SetLogLevel ( log . WARNING )
2020-02-28 10:24:08 +01:00
} else if important {
2020-06-14 20:14:24 +02:00
log . SetLogLevel ( log . IMPORTANT )
2020-02-28 10:24:08 +01:00
} else if errorlog {
2020-06-14 20:14:24 +02:00
log . SetLogLevel ( log . ERROR )
2018-04-08 16:36:12 +02:00
} else {
2020-06-14 20:14:24 +02:00
log . SetLogLevel ( log . INFO )
2018-04-08 16:36:12 +02:00
}
2021-02-16 21:19:54 +03:00
var logFileToUse string
if logFile == "" {
logFileToUse = log . StdoutFile
} else {
logFileToUse = logFile
}
log . Close ( )
if err := log . OpenFile ( logFileToUse ) ; err != nil {
log . Error ( "Error opening user defined log: %s %s" , logFileToUse , err )
2018-04-08 16:36:12 +02:00
}
}
2018-04-02 05:25:32 +02:00
func setupSignals ( ) {
sigChan = make ( chan os . Signal , 1 )
2020-07-17 01:29:58 +02:00
exitChan = make ( chan bool , workers + 1 )
2018-04-02 05:25:32 +02:00
signal . Notify ( sigChan ,
syscall . SIGHUP ,
syscall . SIGINT ,
syscall . SIGTERM ,
syscall . SIGQUIT )
go func ( ) {
sig := <- sigChan
log . Raw ( "\n" )
log . Important ( "Got signal: %v" , sig )
2020-07-17 01:29:58 +02:00
cancel ( )
2018-04-02 05:25:32 +02:00
} ( )
}
func worker ( id int ) {
log . Debug ( "Worker #%d started." , id )
for true {
select {
2020-07-17 01:29:58 +02:00
case <- ctx . Done ( ) :
goto Exit
default :
pkt , ok := <- wrkChan
if ! ok {
2021-02-13 19:16:25 +03:00
log . Debug ( "worker channel closed %d" , id )
2020-07-17 01:29:58 +02:00
goto Exit
}
2018-04-02 05:25:32 +02:00
onPacket ( pkt )
}
}
2020-07-17 01:29:58 +02:00
Exit :
log . Debug ( "worker #%d exit" , id )
2018-04-02 05:25:32 +02:00
}
func setupWorkers ( ) {
2018-04-02 05:34:21 +02:00
log . Debug ( "Starting %d workers ..." , workers )
2018-04-02 05:25:32 +02:00
// setup the workers
2018-04-10 13:06:02 +02:00
wrkChan = make ( chan netfilter . Packet )
2018-04-02 05:25:32 +02:00
for i := 0 ; i < workers ; i ++ {
go worker ( i )
}
}
2021-02-18 17:21:50 +03:00
func doCleanup ( queue , repeatQueue * netfilter . Queue ) {
2018-04-02 05:25:32 +02:00
log . Info ( "Cleaning up ..." )
2021-06-07 01:32:05 +02:00
firewall . Stop ( )
2021-04-05 09:28:16 +00:00
monitor . End ( )
2020-07-17 01:29:58 +02:00
uiClient . Close ( )
queue . Close ( )
2021-02-18 17:21:50 +03:00
repeatQueue . Close ( )
2018-04-17 18:08:03 +02:00
2018-04-16 17:51:54 +02:00
if cpuProfile != "" {
pprof . StopCPUProfile ( )
}
if memProfile != "" {
f , err := os . Create ( memProfile )
if err != nil {
fmt . Printf ( "Could not create memory profile: %s\n" , err )
return
}
defer f . Close ( )
runtime . GC ( ) // get up-to-date statistics
if err := pprof . WriteHeapProfile ( f ) ; err != nil {
fmt . Printf ( "Could not write memory profile: %s\n" , err )
}
}
2018-04-02 05:25:32 +02:00
}
2018-04-10 13:06:02 +02:00
func onPacket ( packet netfilter . Packet ) {
2018-04-02 05:25:32 +02:00
// DNS response, just parse, track and accept.
if dns . TrackAnswers ( packet . Packet ) == true {
2021-01-25 11:33:24 +03:00
packet . SetVerdictAndMark ( netfilter . NF_ACCEPT , packet . Mark )
2018-04-05 23:56:07 +02:00
stats . OnDNSResponse ( )
2018-04-02 05:25:32 +02:00
return
}
// Parse the connection state
2019-11-01 01:00:10 +01:00
con := conman . Parse ( packet , uiClient . InterceptUnknown ( ) )
2018-04-02 05:25:32 +02:00
if con == nil {
2020-05-10 17:08:08 +02:00
applyDefaultAction ( & packet )
2018-04-02 05:25:32 +02:00
return
}
2020-04-19 20:13:31 +02:00
// accept our own connections
if con . Process . ID == os . Getpid ( ) {
packet . SetVerdict ( netfilter . NF_ACCEPT )
return
}
2018-04-02 05:25:32 +02:00
2018-04-02 18:26:04 +02:00
// search a match in preloaded rules
2020-03-03 23:51:25 +01:00
r := acceptOrDeny ( & packet , con )
stats . OnConnectionEvent ( con , r , r == nil )
}
2020-05-10 17:08:08 +02:00
func applyDefaultAction ( packet * netfilter . Packet ) {
if uiClient . DefaultAction ( ) == rule . Allow {
2021-01-25 11:33:24 +03:00
packet . SetVerdictAndMark ( netfilter . NF_ACCEPT , packet . Mark )
2020-05-10 17:08:08 +02:00
} else {
2021-02-19 09:25:08 +03:00
packet . SetVerdict ( netfilter . NF_DROP )
2020-05-10 17:08:08 +02:00
}
}
2020-03-03 23:51:25 +01:00
func acceptOrDeny ( packet * netfilter . Packet , con * conman . Connection ) * rule . Rule {
2018-04-02 05:25:32 +02:00
r := rules . FindFirstMatch ( con )
if r == nil {
2021-02-18 17:21:50 +03:00
// no rule matched
// Note that as soon as we set a verdict on a packet, the next packet in the netfilter queue
// will begin to be processed even if this function hasn't yet returned
// send a request to the UI client if
// 1) connected and running and 2) we are not already asking
if uiClient . Connected ( ) == false || uiClient . GetIsAsking ( ) == true {
applyDefaultAction ( packet )
2021-04-17 22:09:16 +02:00
log . Debug ( "UI is not running or busy, connected: %v, running: %v" , uiClient . Connected ( ) , uiClient . GetIsAsking ( ) )
2021-02-18 17:21:50 +03:00
return nil
}
uiClient . SetIsAsking ( true )
defer uiClient . SetIsAsking ( false )
// In order not to block packet processing, we send our packet to a different netfilter queue
// and then immediately pull it back out of that queue
packet . SetRequeueVerdict ( uint16 ( repeatQueueNum ) )
var o bool
var pkt netfilter . Packet
// don't wait for the packet longer than 1 sec
select {
case pkt , o = <- repeatPktChan :
if ! o {
log . Debug ( "error while receiving packet from repeatPktChan" )
return nil
}
case <- time . After ( 1 * time . Second ) :
log . Debug ( "timed out while receiving packet from repeatPktChan" )
return nil
}
//check if the pulled out packet is the same we put in
if res := bytes . Compare ( packet . Packet . Data ( ) , pkt . Packet . Data ( ) ) ; res != 0 {
2021-08-11 14:00:23 +02:00
log . Error ( "The packet which was requeued has changed abruptly. This should never happen. Please report this incident to the Opensnitch developers. %v %v " , packet , pkt )
2021-02-18 17:21:50 +03:00
return nil
}
packet = & pkt
r = uiClient . Ask ( con )
2020-02-14 23:15:14 +01:00
if r == nil {
2020-06-04 00:38:11 +02:00
log . Error ( "Invalid rule received, applying default action" )
applyDefaultAction ( packet )
2020-03-03 23:51:25 +01:00
return nil
2020-02-14 23:15:14 +01:00
}
2021-02-18 17:21:50 +03:00
ok := false
pers := ""
action := string ( r . Action )
if r . Action == rule . Allow {
action = log . Green ( action )
} else {
action = log . Red ( action )
}
// check if and how the rule needs to be saved
if r . Duration == rule . Always {
pers = "Saved"
// add to the loaded rules and persist on disk
if err := rules . Add ( r , true ) ; err != nil {
log . Error ( "Error while saving rule: %s" , err )
2018-04-05 18:15:36 +02:00
} else {
2021-02-18 17:21:50 +03:00
ok = true
2018-04-05 18:15:36 +02:00
}
2021-02-18 17:21:50 +03:00
} else {
pers = "Added"
// add to the rules but do not save to disk
if err := rules . Add ( r , false ) ; err != nil {
log . Error ( "Error while adding rule: %s" , err )
2019-11-09 01:35:13 +01:00
} else {
2021-02-18 17:21:50 +03:00
ok = true
2018-04-03 14:51:58 +02:00
}
2021-02-18 17:21:50 +03:00
}
2018-04-05 18:15:36 +02:00
2021-02-18 17:21:50 +03:00
if ok {
log . Important ( "%s new rule: %s if %s" , pers , action , r . Operator . String ( ) )
2018-04-03 14:51:58 +02:00
}
2021-02-18 17:21:50 +03:00
2018-04-02 05:25:32 +02:00
}
2020-05-10 17:08:08 +02:00
if r . Enabled == false {
applyDefaultAction ( packet )
ruleName := log . Green ( r . Name )
log . Info ( "DISABLED (%s) %s %s -> %s:%d (%s)" , uiClient . DefaultAction ( ) , log . Bold ( log . Green ( "✔" ) ) , log . Bold ( con . Process . Path ) , log . Bold ( con . To ( ) ) , con . DstPort , ruleName )
} else if r . Action == rule . Allow {
2020-03-03 23:51:25 +01:00
if packet != nil {
2021-01-25 11:33:24 +03:00
packet . SetVerdictAndMark ( netfilter . NF_ACCEPT , packet . Mark )
2020-03-03 23:51:25 +01:00
}
2018-04-02 05:25:32 +02:00
ruleName := log . Green ( r . Name )
2018-04-07 13:52:25 +02:00
if r . Operator . Operand == rule . OpTrue {
2018-04-02 05:25:32 +02:00
ruleName = log . Dim ( r . Name )
}
2018-04-03 14:51:58 +02:00
log . Debug ( "%s %s -> %s:%d (%s)" , log . Bold ( log . Green ( "✔" ) ) , log . Bold ( con . Process . Path ) , log . Bold ( con . To ( ) ) , con . DstPort , ruleName )
2018-04-08 20:13:35 +02:00
} else {
2020-03-03 23:51:25 +01:00
if packet != nil {
2021-02-19 09:25:08 +03:00
packet . SetVerdict ( netfilter . NF_DROP )
2020-03-03 23:51:25 +01:00
}
2018-04-02 05:25:32 +02:00
2020-04-20 01:00:16 +02:00
log . Debug ( "%s %s -> %s:%d (%s)" , log . Bold ( log . Red ( "✘" ) ) , log . Bold ( con . Process . Path ) , log . Bold ( con . To ( ) ) , con . DstPort , log . Red ( r . Name ) )
2018-04-08 20:13:35 +02:00
}
2020-03-03 23:51:25 +01:00
return r
2018-04-02 05:25:32 +02:00
}
func main ( ) {
2020-07-17 01:29:58 +02:00
ctx , cancel = context . WithCancel ( context . Background ( ) )
defer cancel ( )
2018-04-02 05:25:32 +02:00
flag . Parse ( )
2021-03-22 17:51:31 +01:00
if showVersion {
fmt . Println ( core . Version )
os . Exit ( 0 )
}
2018-04-08 16:36:12 +02:00
setupLogging ( )
2018-04-05 17:41:12 +02:00
2018-04-16 17:51:54 +02:00
if cpuProfile != "" {
if f , err := os . Create ( cpuProfile ) ; err != nil {
log . Fatal ( "%s" , err )
} else if err := pprof . StartCPUProfile ( f ) ; err != nil {
log . Fatal ( "%s" , err )
}
}
2018-04-02 05:34:21 +02:00
log . Important ( "Starting %s v%s" , core . Name , core . Version )
2018-04-02 05:25:32 +02:00
2018-04-02 05:34:21 +02:00
rulesPath , err := core . ExpandPath ( rulesPath )
if err != nil {
log . Fatal ( "%s" , err )
}
setupSignals ( )
2018-04-08 15:32:20 +02:00
log . Info ( "Loading rules from %s ..." , rulesPath )
2018-04-08 16:36:12 +02:00
if rules , err = rule . NewLoader ( ! noLiveReload ) ; err != nil {
log . Fatal ( "%s" , err )
} else if err = rules . Load ( rulesPath ) ; err != nil {
2018-04-08 15:32:20 +02:00
log . Fatal ( "%s" , err )
}
2018-04-08 17:20:37 +02:00
stats = statistics . New ( rules )
2018-04-02 05:25:32 +02:00
// prepare the queue
2018-04-08 15:32:20 +02:00
setupWorkers ( )
2018-04-10 13:15:30 +02:00
queue , err := netfilter . NewQueue ( uint16 ( queueNum ) )
2018-04-02 05:25:32 +02:00
if err != nil {
2021-02-18 17:21:50 +03:00
log . Warning ( "Is opensnitchd already running?" )
2018-04-02 05:25:32 +02:00
log . Fatal ( "Error while creating queue #%d: %s" , queueNum , err )
}
2018-04-10 12:52:01 +02:00
pktChan = queue . Packets ( )
2018-04-02 05:25:32 +02:00
2021-02-18 17:21:50 +03:00
repeatQueueNum = queueNum + 1
repeatQueue , rqerr := netfilter . NewQueue ( uint16 ( repeatQueueNum ) )
if rqerr != nil {
log . Warning ( "Is opensnitchd already running?" )
log . Fatal ( "Error while creating queue #%d: %s" , repeatQueueNum , rqerr )
}
repeatPktChan = repeatQueue . Packets ( )
2021-08-09 00:21:43 +02:00
uiClient = ui . NewClient ( uiSocket , stats , rules )
statistics: fixed missed connections
Previous behaviour:
1) Before version 1.0.0b the daemon kept a list of processes that had
established connections. The list was displayed on the GUI as is, so
the maximum number of connections displayed were 100 (hardcoded).
2) When the intercepted connections reached 100, the last entry of the
list was removed, and a new one was inserted on the top.
After v1.0.0 we started saving connections to a DB on the GUI side, to
get rid of the hardcoded connections limit. However, the point 2) was
still present that caused some problems:
- When the backlog was full we kept inserting and deleting connections
from it continuously, one by one.
- If there was a connections burst we could end up missing some
connections.
New behaviour:
- The statisics are deleted from the daemon everytime we send them to
the GUI, because we don't need them on the daemon anymore.
- If the GUI is not connected, the connections will be added to the
backlog as in the point 2).
- When the backlog reaches the limit, it'll keep deleting the last
one in order to insert a new one.
- The number of connections to keep on the backlog is configurable.
- If the statistics configuration is missing, default values will be
150 (maxEvents) and 25 (maxStats).
Notes:
If the GUI is saving the data to memory (default), there won't be
any noticeable side effect.
If the GUI is configured to save the connections to a DB on disk, and
the daemon sends all the backlog at once, the GUI may experience a
delay and a high CPU spike. This can occur on connecting to the daemon
(because the backlog will be full), or when an app sends too many
connections per second (like nmap).
2021-08-13 12:18:10 +02:00
stats . SetConfig ( uiClient . GetStatsConfig ( ) )
2021-08-09 00:21:43 +02:00
2021-04-17 22:09:16 +02:00
// queue is ready, run firewall rules
2021-08-09 00:21:43 +02:00
firewall . Init ( uiClient . GetFirewallType ( ) , & queueNum )
2021-04-17 22:09:16 +02:00
2020-11-02 01:37:35 +01:00
if overwriteLogging ( ) {
setupLogging ( )
}
2020-04-19 20:13:31 +02:00
// overwrite monitor method from configuration if the user has passed
// the option via command line.
2020-03-16 01:37:33 +01:00
if procmonMethod != "" {
2021-09-04 23:09:04 +02:00
if err := monitor . ReconfigureMonitorMethod ( procmonMethod ) ; err != nil {
log . Warning ( "Unable to set process monitor method via parameter: %v" , err )
}
2020-03-16 01:37:33 +01:00
}
2019-10-21 19:23:29 +02:00
2018-04-02 05:25:32 +02:00
log . Info ( "Running on netfilter queue #%d ..." , queueNum )
2020-07-17 01:29:58 +02:00
for {
2018-04-02 05:25:32 +02:00
select {
2020-07-17 01:29:58 +02:00
case <- ctx . Done ( ) :
goto Exit
case pkt , ok := <- pktChan :
if ! ok {
goto Exit
}
2018-04-02 05:25:32 +02:00
wrkChan <- pkt
}
}
2020-07-17 01:29:58 +02:00
Exit :
close ( wrkChan )
2021-02-18 17:21:50 +03:00
doCleanup ( queue , repeatQueue )
2020-07-17 01:29:58 +02:00
os . Exit ( 0 )
2018-04-02 05:25:32 +02:00
}