Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mutliple inputs support, fixes in metric count #41

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Best way to get started with `dnsmonster` is to download the binary from the rel
Since `dnsmonster` uses raw packet capture funcationality, Docker/Podman daemon must grant the capability to the container

```
sudo docker run --rm -it --net=host --cap-add NET_RAW --cap-add NET_ADMIN --name dnsmonster ghcr.io/mosajjal/dnsmonster:latest --devName lo --stdoutOutputType=1
sudo docker run --rm -it --net=host --cap-add NET_RAW --cap-add NET_ADMIN --name dnsmonster ghcr.io/mosajjal/dnsmonster:latest --input live::lo --stdoutOutputType=1
```


Expand Down Expand Up @@ -124,13 +124,13 @@ For more information on how the statically linked binary is created, take a look

Bulding on Windows is much the same as Linux. Just make sure that you have `npcap`. Clone the repository (`--history 1` works), and run `go get` and `go build .`

As mentioned, Windows release of the binary depends on [npcap](https://nmap.org/npcap/#download) to be installed. After installation, the binary should work out of the box. It's been tested in a Windows 10 environment and it executed without an issue. To find interface names to give `--devName` parameter and start sniffing, you'll need to do the following:
As mentioned, Windows release of the binary depends on [npcap](https://nmap.org/npcap/#download) to be installed. After installation, the binary should work out of the box. It's been tested in a Windows 10 environment and it executed without an issue. To find interface names to give `--input` parameter and start sniffing, you'll need to do the following:

- open cmd.exe as Administrator and run the following: `getmac.exe`, you'll see a table with your interfaces' MAC address and a Transport Name column with something like this: `\Device\Tcpip_{16000000-0000-0000-0000-145C4638064C}`
- run `dnsmonster.exe` in `cmd.exe` like this:

```sh
dnsmonster.exe --devName \Device\NPF_{16000000-0000-0000-0000-145C4638064C}
dnsmonster.exe --input="live::\Device\NPF_{16000000-0000-0000-0000-145C4638064C}"
```

Note that you must change `\Tcpip` from `getmac.exe` to `\NPF` and then pass it to `dnsmonster.exe`.
Expand Down Expand Up @@ -591,8 +591,7 @@ all the flags can also be set via env variables. Keep in mind that the name of e
Example:

```shell
$ export DNSMONSTER_PORT=53
$ export DNSMONSTER_DEVNAME=lo
$ export DNSMONSTER_INPUT=live::lo
$ sudo -E dnsmonster
```

Expand Down
2 changes: 1 addition & 1 deletion autobuild.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ dnsmonsteragent=$(cat <<EOF
- PGID=1000
command:
- "--serverName=HOSTNAME"
- "--devName=DEVNAME"
- "--input=live::DEVNAME"
- "--clickhouseAddress=127.0.0.1:9000"
- "--clickhouseOutputType=1"
- "--clickhouseBatchSize=BATCHSIZE"
Expand Down
11 changes: 8 additions & 3 deletions capture/afpacket_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package capture

import (
"net/url"
"os"
"syscall"
"time"
Expand All @@ -16,6 +17,7 @@ import (
)

type afpacketHandle struct {
name string
TPacket *afpacket.TPacket
}

Expand Down Expand Up @@ -49,6 +51,9 @@ func (h *afpacketHandle) SetBPFFilter(filter string, snaplen int) (err error) {
func (h *afpacketHandle) Close() {
h.TPacket.Close()
}
func (h *afpacketHandle) Name() string {
return url.QueryEscape(h.name)
}

func afpacketComputeSize(targetSizeMb uint, snaplen uint, pageSize uint) (
frameSize uint, blockSize uint, numBlocks uint, err error,
Expand All @@ -71,11 +76,11 @@ func afpacketComputeSize(targetSizeMb uint, snaplen uint, pageSize uint) (
return frameSize, blockSize, numBlocks, nil
}

func (config captureConfig) setPromiscuous() error {
func (config captureConfig) setPromiscuous(dev string) error {
var err error
if !config.NoPromiscuous {
// TODO: replace with x/net/bpf or pcap
err = syscall.SetLsfPromisc(config.DevName, !config.NoPromiscuous)
err = syscall.SetLsfPromisc(dev, !config.NoPromiscuous)
log.Infof("Promiscuous mode: %v", !config.NoPromiscuous)
}
return err
Expand Down Expand Up @@ -111,7 +116,7 @@ func (config captureConfig) initializeLiveAFpacket(devName, filter string) *afpa
// set up promisc mode. first we need to get the fd for the interface we just opened. using a hacky mode
// v := reflect.ValueOf(handle.TPacket)
// fd := v.FieldByName("fd").Int()
err = config.setPromiscuous()
err = config.setPromiscuous(devName)
if err != nil {
log.Fatal("Error setting the interface to promiscuous.. exiting")
}
Expand Down
4 changes: 3 additions & 1 deletion capture/afpacket_nonlinux.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ func (h *afpacketHandle) LinkType() layers.LinkType {

func (h *afpacketHandle) Close() {
}

func (h *afpacketHandle) Name() string {
return ""
}
func (afhandle *afpacketHandle) Stat() (uint, uint, error) {
return 0, 0, fmt.Errorf("Afpacket statistics are only available on Linux")
}
Expand Down
91 changes: 40 additions & 51 deletions capture/capture.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,23 @@ import (
)

type captureConfig struct {
DevName string `long:"devname" ini-name:"devname" env:"DNSMONSTER_DEVNAME" default:"" description:"Device used to capture"`
PcapFile string `long:"pcapfile" ini-name:"pcapfile" env:"DNSMONSTER_PCAPFILE" default:"" description:"Pcap filename to run"`
DnstapSocket string `long:"dnstapsocket" ini-name:"dnstapsocket" env:"DNSMONSTER_DNSTAPSOCKET" default:"" description:"dnstap socket path. Example: unix:///tmp/dnstap.sock, tcp://127.0.0.1:8080"`
Port uint `long:"port" ini-name:"port" env:"DNSMONSTER_PORT" default:"53" description:"Port selected to filter packets"`
SampleRatio string `long:"sampleratio" ini-name:"sampleratio" env:"DNSMONSTER_SAMPLERATIO" default:"1:1" description:"Capture Sampling by a:b. eg sampleRatio of 1:100 will process 1 percent of the incoming packets"`
DedupCleanupInterval time.Duration `long:"dedupcleanupinterval" ini-name:"dedupcleanupinterval" env:"DNSMONSTER_DEDUPCLEANUPINTERVAL" default:"60s" description:"Cleans up packet hash table used for deduplication"`
DnstapPermission string `long:"dnstappermission" ini-name:"dnstappermission" env:"DNSMONSTER_DNSTAPPERMISSION" default:"755" description:"Set the dnstap socket permission, only applicable when unix:// is used"`
PacketHandlerCount uint `long:"packethandlercount" ini-name:"packethandlercount" env:"DNSMONSTER_PACKETHANDLERCOUNT" default:"2" description:"Number of routines used to handle received packets"`
TCPAssemblyChannelSize uint `long:"tcpassemblychannelsize" ini-name:"tcpassemblychannelsize" env:"DNSMONSTER_TCPASSEMBLYCHANNELSIZE" default:"10000" description:"Size of the tcp assembler"`
TCPResultChannelSize uint `long:"tcpresultchannelsize" ini-name:"tcpresultchannelsize" env:"DNSMONSTER_TCPRESULTCHANNELSIZE" default:"10000" description:"Size of the tcp result channel"`
TCPHandlerCount uint `long:"tcphandlercount" ini-name:"tcphandlercount" env:"DNSMONSTER_TCPHANDLERCOUNT" default:"1" description:"Number of routines used to handle tcp packets"`
DefraggerChannelSize uint `long:"defraggerchannelsize" ini-name:"defraggerchannelsize" env:"DNSMONSTER_DEFRAGGERCHANNELSIZE" default:"10000" description:"Size of the channel to send packets to be defragged"`
DefraggerChannelReturnSize uint `long:"defraggerchannelreturnsize" ini-name:"defraggerchannelreturnsize" env:"DNSMONSTER_DEFRAGGERCHANNELRETURNSIZE" default:"10000" description:"Size of the channel where the defragged packets are returned"`
PacketChannelSize uint `long:"packetchannelsize" ini-name:"packetchannelsize" env:"DNSMONSTER_PACKETCHANNELSIZE" default:"1000" description:"Size of the packet handler channel"`
AfpacketBuffersizeMb uint `long:"afpacketbuffersizemb" ini-name:"afpacketbuffersizemb" env:"DNSMONSTER_AFPACKETBUFFERSIZEMB" default:"64" description:"Afpacket Buffersize in MB"`
Filter string `long:"filter" ini-name:"filter" env:"DNSMONSTER_FILTER" default:"((ip and (ip[9] == 6 or ip[9] == 17)) or (ip6 and (ip6[6] == 17 or ip6[6] == 6 or ip6[6] == 44)))" description:"BPF filter applied to the packet stream. If port is selected, the packets will not be defragged."`
UseAfpacket bool `long:"useafpacket" ini-name:"useafpacket" env:"DNSMONSTER_USEAFPACKET" description:"Use AFPacket for live captures. Supported on Linux 3.0+ only"`
NoEthernetframe bool `long:"noetherframe" ini-name:"noetherframe" env:"DNSMONSTER_NOETHERFRAME" description:"The PCAP capture does not contain ethernet frames"`
Dedup bool `long:"dedup" ini-name:"dedup" env:"DNSMONSTER_DEDUP" description:"Deduplicate incoming packets, Only supported with --devName and --pcapFile. Experimental "`
NoPromiscuous bool `long:"nopromiscuous" ini-name:"nopromiscuous" env:"DNSMONSTER_NOPROMISCUOUS" description:"Do not put the interface in promiscuous mode"`
Input []string `long:"input" ini-name:"input" env:"DNSMONSTER_INPUT" default:"" description:"capture input(s). example: live::eth0, pcap::/path/to/my/pcap dnstap::unix:///tmp/dnstap.sock, dnstap::tcp://127.0.0.1:8080"`
SampleRatio string `long:"sampleratio" ini-name:"sampleratio" env:"DNSMONSTER_SAMPLERATIO" default:"1:1" description:"Capture Sampling by a:b. eg sampleRatio of 1:100 will process 1 percent of the incoming packets"`
DedupCleanupInterval time.Duration `long:"dedupcleanupinterval" ini-name:"dedupcleanupinterval" env:"DNSMONSTER_DEDUPCLEANUPINTERVAL" default:"60s" description:"Cleans up packet hash table used for deduplication"`
DnstapPermission string `long:"dnstappermission" ini-name:"dnstappermission" env:"DNSMONSTER_DNSTAPPERMISSION" default:"755" description:"Set the dnstap socket permission, only applicable when unix:// is used"`
PacketHandlerCount uint `long:"packethandlercount" ini-name:"packethandlercount" env:"DNSMONSTER_PACKETHANDLERCOUNT" default:"2" description:"Number of routines used to handle received packets"`
TCPAssemblyChannelSize uint `long:"tcpassemblychannelsize" ini-name:"tcpassemblychannelsize" env:"DNSMONSTER_TCPASSEMBLYCHANNELSIZE" default:"10000" description:"Size of the tcp assembler"`
TCPResultChannelSize uint `long:"tcpresultchannelsize" ini-name:"tcpresultchannelsize" env:"DNSMONSTER_TCPRESULTCHANNELSIZE" default:"10000" description:"Size of the tcp result channel"`
TCPHandlerCount uint `long:"tcphandlercount" ini-name:"tcphandlercount" env:"DNSMONSTER_TCPHANDLERCOUNT" default:"1" description:"Number of routines used to handle tcp packets"`
DefraggerChannelSize uint `long:"defraggerchannelsize" ini-name:"defraggerchannelsize" env:"DNSMONSTER_DEFRAGGERCHANNELSIZE" default:"10000" description:"Size of the channel to send packets to be defragged"`
DefraggerChannelReturnSize uint `long:"defraggerchannelreturnsize" ini-name:"defraggerchannelreturnsize" env:"DNSMONSTER_DEFRAGGERCHANNELRETURNSIZE" default:"10000" description:"Size of the channel where the defragged packets are returned"`
PacketChannelSize uint `long:"packetchannelsize" ini-name:"packetchannelsize" env:"DNSMONSTER_PACKETCHANNELSIZE" default:"1000" description:"Size of the packet handler channel"`
AfpacketBuffersizeMb uint `long:"afpacketbuffersizemb" ini-name:"afpacketbuffersizemb" env:"DNSMONSTER_AFPACKETBUFFERSIZEMB" default:"64" description:"Afpacket Buffersize in MB"`
Filter string `long:"filter" ini-name:"filter" env:"DNSMONSTER_FILTER" default:"(ip or ip6) and port 53" description:"BPF filter applied to the packet stream. If you're using a non-libpcap version, you'll need to provide a base64 version of tcdpump -d output. refer to the docs for more info."`
UseAfpacket bool `long:"useafpacket" ini-name:"useafpacket" env:"DNSMONSTER_USEAFPACKET" description:"Use AFPacket for live captures. Supported on Linux 3.0+ only"`
NoEthernetframe bool `long:"noetherframe" ini-name:"noetherframe" env:"DNSMONSTER_NOETHERFRAME" description:"The PCAP capture does not contain ethernet frames"`
Dedup bool `long:"dedup" ini-name:"dedup" env:"DNSMONSTER_DEDUP" description:"Deduplicate incoming live or pcap packets. Experimental "`
NoPromiscuous bool `long:"nopromiscuous" ini-name:"nopromiscuous" env:"DNSMONSTER_NOPROMISCUOUS" description:"Do not put the interface in promiscuous mode"`
processingChannel chan *rawPacketBytes
ip4Defrgger chan ipv4ToDefrag
ip6Defrgger chan ipv6FragmentInfo
Expand All @@ -57,16 +54,13 @@ type captureConfig struct {
}

// GlobalCaptureConfig is accessible globally
var GlobalCaptureConfig *captureConfig
var GlobalCaptureConfig = new(captureConfig)

// this function will run at import time, before parsing the flags
func init() {
config := captureConfig{}
if _, err := util.GlobalParser.AddGroup("capture", "Options specific to capture side", &config); err != nil {
if _, err := util.GlobalParser.AddGroup("capture", "Options specific to capture side", GlobalCaptureConfig); err != nil {
log.Fatalf("error adding capture Module")
}
GlobalCaptureConfig = &config

}

func (config captureConfig) GetResultChannel() chan util.DNSResult {
Expand All @@ -79,28 +73,10 @@ func (config captureConfig) cleanExit(ctx context.Context) {
}

func (config *captureConfig) CheckFlagsAndStart(ctx context.Context) {
if config.Port > 65535 {
log.Fatal("--port must be between 1 and 65535")
}
if config.DevName == "" && config.PcapFile == "" && config.DnstapSocket == "" {
log.Fatal("one of --devName, --pcapFile or --dnstapSocket is required")
}

if config.DevName != "" {
if config.PcapFile != "" || config.DnstapSocket != "" {
log.Fatal("You must set only --devName, --pcapFile or --dnstapSocket")
}
} else {
if config.PcapFile != "" && config.DnstapSocket != "" {
log.Fatal("You must set only --devName, --pcapFile or --dnstapSocket")
}
if len(config.Input) == 0 {
log.Fatal("input can not be empty.")
}

if config.DnstapSocket != "" {
if !strings.HasPrefix(config.DnstapSocket, "unix://") && !strings.HasPrefix(config.DnstapSocket, "tcp://") {
log.Fatal("You must provide a unix:// or tcp:// socket for dnstap")
}
}
ratioNumbers := strings.Split(config.SampleRatio, ":")
if len(ratioNumbers) != 2 {
log.Fatal("wrong --sampleRatio syntax")
Expand Down Expand Up @@ -160,13 +136,25 @@ func (config *captureConfig) CheckFlagsAndStart(ctx context.Context) {
// start the packet decoder goroutines
g.Go(func() error { return config.StartPacketDecoder(gCtx) })

// Start listening if we're not using DNSTap
if config.DnstapSocket == "" {
g.Go(func() error { return config.StartNonDNSTap(gCtx) })
} else {
// dnstap is totally different, hence only the result channel is being pushed to it
g.Go(func() error { return config.StartDNSTap(gCtx) })
// parse inputs and start a goroutine per input type
for _, in := range config.Input {
tmp := strings.Split(in, "::")
if len(tmp) != 2 {
log.Fatalf("failed to parse input: %s", in)
}
inType, inPath := tmp[0], tmp[1]
if inType == "pcap" {
h := config.NewPcapfileHandler(gCtx, inPath)
g.Go(func() error { return config.StartNonDNSTap(gCtx, h) })
} else if inType == "live" {
h := config.NewLiveHander(gCtx, inPath)
g.Go(func() error { return config.StartNonDNSTap(gCtx, h) })
} else {
// dnstap is totally different, hence only the result channel is being pushed to it
g.Go(func() error { return config.StartDNSTap(gCtx, inPath) })
}
}

<-gCtx.Done()

}
Expand Down Expand Up @@ -259,6 +247,7 @@ type detectIP struct {
// right now, most functionality of afpacket, pcap file and libpcap
// are captured in this interface
type genericPacketHandler interface {
Name() string
ReadPacketData() ([]byte, gopacket.CaptureInfo, error)
ZeroCopyReadPacketData() ([]byte, gopacket.CaptureInfo, error)
Close()
Expand Down
Loading