capture packages

This commit is contained in:
Martin/Geno 2018-06-02 01:00:54 +02:00
commit 5cea0a4ca2
No known key found for this signature in database
GPG Key ID: 9D7D3C6BFF600C6A
11 changed files with 527 additions and 0 deletions

26
.drone.yml Normal file
View File

@ -0,0 +1,26 @@
workspace:
base: /go
path: src/dev.sum7.eu/wifictld/analyzer
pipeline:
build:
image: golang:latest
commands:
- go get ./...
- go build
codestyle:
image: golang:latest
commands:
- go get github.com/client9/misspell/cmd/misspell
- misspell -error .
- if [ -n "$(gofmt -s -l .)" ]; then echo "Go code is not formatted, run 'gofmt -s -w .'" >&2; exit 1; fi
test:
image: golang:latest
commands:
- go get github.com/stretchr/testify/assert
- go test ./... -v -cover
test-race:
image: golang:latest
commands:
- go get github.com/stretchr/testify/assert
- go test ./... -v -race

161
capture/collector.go Normal file
View File

@ -0,0 +1,161 @@
package capture
import (
"net"
"dev.sum7.eu/wifictld/analyzer/data"
log "github.com/sirupsen/logrus"
)
//Collector for capture
type Collector struct {
connections map[string]*net.UDPConn
handler data.Handler
queue chan *Packet
stop chan interface{}
}
// NewCollector creates a Collector struct
func NewCollector(handler data.Handler, ifaces []IFaceConfig) *Collector {
coll := &Collector{
handler: handler,
queue: make(chan *Packet, 400),
stop: make(chan interface{}),
connections: make(map[string]*net.UDPConn),
}
for _, iface := range ifaces {
coll.listenUDP(iface)
}
go coll.parser()
return coll
}
// Close Collector
func (coll *Collector) Close() {
close(coll.stop)
for _, conn := range coll.connections {
conn.Close()
}
close(coll.queue)
}
func (coll *Collector) listenUDP(iface IFaceConfig) {
ip := net.ParseIP(iface.IPAddress)
var conn *net.UDPConn
var err error
if ip.IsMulticast() {
ifx, err := net.InterfaceByName(iface.InterfaceName)
if err != nil {
log.Panic(err)
}
// Open socket
conn, err = net.ListenMulticastUDP("udp", ifx, &net.UDPAddr{
IP: ip,
Port: iface.Port,
})
if err != nil {
log.Panic(err)
}
} else {
// Open socket
conn, err = net.ListenUDP("udp", &net.UDPAddr{
IP: ip,
Port: iface.Port,
Zone: iface.InterfaceName,
})
if err != nil {
log.Panic(err)
}
}
conn.SetReadBuffer(MaxDataGramSize)
coll.connections[iface.InterfaceName] = conn
// Start receiver
go coll.receiver(conn)
}
// Packet of the respond request
type Packet struct {
Address *net.UDPAddr
Raw []byte
}
func (coll *Collector) parser() {
for obj := range coll.queue {
msg, err := data.NewSocketMSG(obj.Raw)
if err != nil {
log.Warnf("unable to unmarshal request from %s: %s", obj.Address.String(), err)
continue
}
response, err := coll.handler(obj.Address, msg)
if err != nil {
log.Warnf("unable to handle request from %s: %s", obj.Address.String(), err)
continue
}
if response != nil {
coll.SendTo(obj.Address, response)
}
}
}
// SendTo a specifical address
func (coll *Collector) SendTo(addr *net.UDPAddr, msg *data.SocketMSG) {
data, err := msg.Marshal()
if err != nil {
log.Warnf("unable to marshal response for %s: %s", addr.String(), err)
return
}
conn, ok := coll.connections[addr.Zone]
if ok {
conn.WriteToUDP(data, addr)
return
}
for _, conn := range coll.connections {
conn.WriteToUDP(data, addr)
}
}
// Send to every connection to default address
func (coll *Collector) Send(msg *data.SocketMSG) {
data, err := msg.Marshal()
if err != nil {
log.Warnf("unable to marshal response: %s", err)
return
}
for ifname, conn := range coll.connections {
conn.WriteToUDP(data, &net.UDPAddr{
IP: net.ParseIP(MulticastAddressDefault),
Port: Port,
Zone: ifname,
})
}
}
func (coll *Collector) receiver(conn *net.UDPConn) {
buf := make([]byte, MaxDataGramSize)
for {
n, src, err := conn.ReadFromUDP(buf)
if err != nil {
log.Warnf("ReadFromUDP failed: %s", err)
return
}
raw := make([]byte, n)
copy(raw, buf)
coll.queue <- &Packet{
Address: src,
Raw: raw,
}
}
}

12
capture/const.go Normal file
View File

@ -0,0 +1,12 @@
package capture
const (
// MulticastAddressDefault default multicast group used by announced
MulticastAddressDefault = "ff02::31f1"
// Port default udp port used by announced
Port = 1000
// MaxDataGramSize maximum receivable size
MaxDataGramSize = 256
)

8
capture/iface.go Normal file
View File

@ -0,0 +1,8 @@
package capture
// IFaceConfig where to listen
type IFaceConfig struct {
InterfaceName string `toml:"ifname"`
Port int `toml:"port"`
IPAddress string `toml:"ip_address"`
}

62
cmd/dump.go Normal file
View File

@ -0,0 +1,62 @@
package cmd
import (
"net"
"os"
"os/signal"
"strings"
"syscall"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
"dev.sum7.eu/wifictld/analyzer/capture"
"dev.sum7.eu/wifictld/analyzer/data"
)
var (
port int
ipAddress string
)
// queryCmd represents the query command
var dumpCmd = &cobra.Command{
Use: "dump <interfaces>",
Short: "capture wifictld traffic and just display the values (like wireshark)",
Example: `analyzer dump "eth0,wlan0"`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
ifaces := strings.Split(args[0], ",")
log.Infof("listen on: %s", ifaces)
var ifacesConfigs []capture.IFaceConfig
for _, iface := range ifaces {
ifaceConfig := capture.IFaceConfig{
InterfaceName: iface,
Port: port,
IPAddress: ipAddress,
}
ifacesConfigs = append(ifacesConfigs, ifaceConfig)
}
coll := capture.NewCollector(func(addr *net.UDPAddr, msg *data.SocketMSG) (*data.SocketMSG, error) {
log.Infof("recv[%s]: %s", addr, msg.String())
return nil, nil
}, ifacesConfigs)
defer coll.Close()
// Wait for INT/TERM
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
sig := <-sigs
log.Println("received", sig)
},
}
func init() {
RootCmd.AddCommand(dumpCmd)
dumpCmd.Flags().IntVar(&port, "port", capture.Port, "define a port to listen (if not set or set to 0 the kernel will use a random free port at its own)")
dumpCmd.Flags().StringVar(&ipAddress, "listen", capture.MulticastAddressDefault, "")
}

33
cmd/root.go Normal file
View File

@ -0,0 +1,33 @@
package cmd
import (
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var debug bool
// RootCmd represents the base command when called without any subcommands
var RootCmd = &cobra.Command{
Use: "analyzer",
Short: "wifictld analyzer",
Long: `capture wifictld traffic and display thus`,
}
// Execute adds all child commands to the root command and sets flags appropriately.
// This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() {
if err := RootCmd.Execute(); err != nil {
log.Error(err)
}
}
func init() {
cobra.OnInitialize(func() {
if debug {
log.SetLevel(log.DebugLevel)
}
log.Debug("show debug")
})
RootCmd.PersistentFlags().BoolVar(&debug, "v", false, "show debug log")
}

5
data/handler.go Normal file
View File

@ -0,0 +1,5 @@
package data
import "net"
type Handler func(addr *net.UDPAddr, msg *SocketMSG) (*SocketMSG, error)

125
data/socket_msg.go Normal file
View File

@ -0,0 +1,125 @@
package data
import (
"encoding/binary"
"fmt"
"net"
"time"
log "github.com/sirupsen/logrus"
)
// SocketMSGType kind of packages
type SocketMSGType uint32
const (
SocketMSGTypeRequest SocketMSGType = (1 << 0)
SocketMSGTypeResponse SocketMSGType = (1 << 1)
SocketMSGTypeClient SocketMSGType = (1 << 2)
SocketMSGTypeStats SocketMSGType = (1 << 3)
)
func (a SocketMSGType) Is(b SocketMSGType) bool {
log.Debugf("SocketType: %x & %x = %x -> %b", a, b, (a & b), (a&b) > 0)
return (a & b) > 0
}
// SocketMSG package of wifictld format
type SocketMSG struct {
Types SocketMSGType
Client *WifiClient
}
func NewSocketMSG(obj []byte) (*SocketMSG, error) {
msg := &SocketMSG{}
if err := msg.Unmarshal(obj); err != nil {
return nil, err
}
return msg, nil
}
func (msg *SocketMSG) Marshal() ([]byte, error) {
obj := make([]byte, MaxDataGramSize)
pos := 0
binary.BigEndian.PutUint32(obj[pos:(pos+4)], uint32(msg.Types))
pos += 4
if msg.Types.Is(SocketMSGTypeClient) {
obj[pos] = msg.Client.Addr[0]
pos++
obj[pos] = msg.Client.Addr[1]
pos++
obj[pos] = msg.Client.Addr[2]
pos++
obj[pos] = msg.Client.Addr[3]
pos++
obj[pos] = msg.Client.Addr[4]
pos++
obj[pos] = msg.Client.Addr[5]
pos++
binary.BigEndian.PutUint32(obj[pos:(pos+4)], uint32(msg.Client.Time.Unix()))
pos += 4
binary.BigEndian.PutUint16(obj[pos:(pos+2)], msg.Client.TryProbe)
pos += 2
binary.BigEndian.PutUint16(obj[pos:(pos+2)], msg.Client.TryAuth)
pos += 2
if msg.Client.Connected {
obj[pos] = byte(1)
}
pos++
if msg.Client.Authed {
obj[pos] = byte(1)
}
pos++
binary.BigEndian.PutUint16(obj[pos:(pos+2)], msg.Client.FreqHighest)
pos += 2
binary.BigEndian.PutUint16(obj[pos:(pos+2)], uint16(msg.Client.SignalLowFreq))
pos += 2
binary.BigEndian.PutUint16(obj[pos:(pos+2)], uint16(msg.Client.SignalHighFreq))
//pos += 2
}
return obj, nil
}
func (msg *SocketMSG) Unmarshal(obj []byte) error {
log.Debugf("hex unmarshal: %x", obj)
pos := 0
msg.Types = SocketMSGType(binary.BigEndian.Uint32(obj[pos:(pos + 4)]))
pos += 4
if msg.Types.Is(SocketMSGTypeClient) {
msg.Client = &WifiClient{
Addr: net.HardwareAddr(obj[pos:(pos + 6)]),
Time: time.Unix(int64(binary.BigEndian.Uint32(obj[(pos+6):(pos+10)])), 0),
TryProbe: binary.BigEndian.Uint16(obj[(pos + 10):(pos + 12)]),
TryAuth: binary.BigEndian.Uint16(obj[(pos + 12):(pos + 14)]),
Connected: (obj[(pos+14)] == 1),
Authed: (obj[(pos+15)] == 1),
FreqHighest: binary.BigEndian.Uint16(obj[(pos + 16):(pos + 18)]),
SignalLowFreq: int16(binary.BigEndian.Uint16(obj[(pos + 18):(pos + 20)])),
SignalHighFreq: int16(binary.BigEndian.Uint16(obj[(pos + 20):(pos + 22)])),
}
//pos += 22
}
return nil
}
func (msg *SocketMSG) String() string {
answer := ""
if msg.Types.Is(SocketMSGTypeRequest) {
answer += "request "
}
if msg.Types.Is(SocketMSGTypeResponse) {
answer += "response "
}
if msg.Types.Is(SocketMSGTypeClient) {
answer += fmt.Sprintf("client(mac=%s freq=%d ssi_l=%d ssi_h=%d tprobe=%d tauth=%d authed=%v time=%s) ", msg.Client.Addr, msg.Client.FreqHighest, msg.Client.SignalLowFreq, msg.Client.SignalHighFreq, msg.Client.TryProbe, msg.Client.TryAuth, msg.Client.Authed, msg.Client.Time.String())
}
return answer
}

58
data/socket_msg_test.go Normal file
View File

@ -0,0 +1,58 @@
package data
import (
"encoding/hex"
"log"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestMsgIsTypes(t *testing.T) {
assert := assert.New(t)
types := SocketMSGType(5)
assert.True(types.Is(SocketMSGTypeRequest))
assert.False(types.Is(SocketMSGTypeResponse))
assert.True(types.Is(SocketMSGTypeClient))
assert.False(types.Is(SocketMSGTypeStats))
assert.Equal(SocketMSGType(5), SocketMSGType(SocketMSGTypeRequest|SocketMSGTypeClient))
}
func TestMsgString(t *testing.T) {
assert := assert.New(t)
msg := SocketMSG{
Types: SocketMSGTypeRequest | SocketMSGTypeResponse | SocketMSGTypeClient,
Client: &WifiClient{
FreqHighest: 2464,
},
}
assert.Equal("request response client(mac= freq=2464 ssi_l=0 ssi_h=0 tprobe=0 tauth=0 authed=false time=0001-01-01 00:00:00 +0000 UTC) ", msg.String())
}
func TestMsgMarshal(t *testing.T) {
assert := assert.New(t)
dst := make([]byte, MaxDataGramSize)
_, err := hex.Decode(dst, []byte("00000005f0cfa13375295b0f8b92000000000101099effa300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"))
if err != nil {
log.Fatal(err)
}
msg, _ := NewSocketMSG(dst)
assert.Equal(SocketMSGType(SocketMSGTypeRequest|SocketMSGTypeClient), msg.Types)
assert.Equal("f0:cf:a1:33:75:29", msg.Client.Addr.String())
assert.Equal("2018-05-31T05:43:46Z", msg.Client.Time.UTC().Format(time.RFC3339))
assert.Equal(0, int(msg.Client.TryProbe))
assert.Equal(0, int(msg.Client.TryAuth))
assert.True(msg.Client.Connected)
assert.True(msg.Client.Authed)
assert.Equal(2462, int(msg.Client.FreqHighest))
assert.Equal(-93, int(msg.Client.SignalLowFreq))
assert.Equal(0, int(msg.Client.SignalHighFreq))
result, _ := msg.Marshal()
assert.Equal(dst, result)
}

30
data/wifi_client.go Normal file
View File

@ -0,0 +1,30 @@
package data
import (
"net"
"time"
)
const (
// default multicast group used by announced
MulticastAddressDefault = "ff02::31f1"
// default udp port used by announced
Port = 1000
// maximum receivable size
MaxDataGramSize = 256
)
// WifiClient datatype of wifictld
type WifiClient struct {
Addr net.HardwareAddr
Time time.Time
TryProbe uint16
TryAuth uint16
Connected bool
Authed bool
FreqHighest uint16
SignalLowFreq int16
SignalHighFreq int16
}

7
main.go Normal file
View File

@ -0,0 +1,7 @@
package main
import "dev.sum7.eu/wifictld/analyzer/cmd"
func main() {
cmd.Execute()
}