Compare commits

..

No commits in common. "master" and "t0.0.1" have entirely different histories.

48 changed files with 226 additions and 15435 deletions

View File

@ -1,8 +0,0 @@
#!/bin/bash
result="$(gofmt -s -l . | grep -v '^vendor/' )"
if [ -n "$result" ]; then
echo "Go code is not formatted, run 'gofmt -s -w .'" >&2
echo "$result"
exit 1
fi

View File

@ -1,25 +0,0 @@
#!/usr/bin/env python
# checks if every desired package has test files
import os
import re
import sys
source_re = re.compile(".*\.go")
test_re = re.compile(".*_test\.go")
missing = False
for root, dirs, files in os.walk("."):
# ignore some paths
if root == "." or root.startswith("./vendor") or root.startswith("./."):
continue
# source files but not test files?
if len(filter(source_re.match, files)) > 0 and len(filter(test_re.match, files)) == 0:
print("no test files for {}".format(root))
missing = True
if missing:
sys.exit(1)
else:
print("every package has test files")

34
.drone.yml Normal file
View File

@ -0,0 +1,34 @@
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
release:
image: plugins/gitea-release
base_url: https://dev.sum7.eu
secrets: [ gitea_token ]
files: /go/bin/analyzer
draft: true
when:
event: tag

View File

@ -1,53 +0,0 @@
image: golang:latest
stages:
- build
- test
- deploy
before_script:
- mkdir -p "/go/src/dev.sum7.eu/$CI_PROJECT_NAMESPACE/"
- cp -R "/builds/$CI_PROJECT_PATH" "/go/src/dev.sum7.eu/$CI_PROJECT_NAMESPACE/"
- cd "/go/src/dev.sum7.eu/$CI_PROJECT_PATH"
- go get -d -t ./...
build-my-project:
stage: build
script:
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
- mv "/go/bin/$CI_PROJECT_NAME" "/builds/$CI_PROJECT_PATH"
artifacts:
paths:
- config_example.conf
- "$CI_PROJECT_NAME"
test-my-project:
stage: test
script:
- go get github.com/client9/misspell/cmd/misspell
- find . -type f | grep -v webroot/assets | xargs misspell -error
- ./.ci/check-gofmt
- ./.ci/check-testfiles
- go test $(go list ./... | grep -v /vendor/) -v -coverprofile .testCoverage.txt
artifacts:
paths:
- .testCoverage.txt
test-race-my-project:
stage: test
script:
- go test -race ./...
deploy:
stage: deploy
only:
- master
script:
- go install "dev.sum7.eu/$CI_PROJECT_PATH"
- 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
- 'which rsync || ( apt-get update -y && apt-get install rsync -y )'
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- rsync -e "ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT" -a --delete "/builds/$CI_PROJECT_PATH/webroot/" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/webroot/
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl stop $CI_PROJECT_NAME
- scp -6 -o StrictHostKeyChecking=no -P $SSH_PORT "/go/bin/$CI_PROJECT_NAME" "$CI_PROJECT_NAME@$SSH_HOST":/opt/$CI_PROJECT_NAME/bin
- ssh -6 -o StrictHostKeyChecking=no -p $SSH_PORT "$CI_PROJECT_NAME@$SSH_HOST" sudo /usr/bin/systemctl start $CI_PROJECT_NAME

View File

@ -1,47 +0,0 @@
# wifictld-analyzer
[![Build Status](https://dev.sum7.eu/wifictld/wifictld-analyzer/badges/master/build.svg)](https://dev.sum7.eu/genofire/wifictld-analyzer/pipelines)
[![Go Report Card](https://goreportcard.com/badge/dev.sum7.eu/wifictld/wifictld-analyzer)](https://goreportcard.com/report/dev.sum7.eu/genofire/wifictld-analyzer)
[![GoDoc](https://godoc.org/dev.sum7.eu/wifictld/wifictld-analyzer?status.svg)](https://godoc.org/dev.sum7.eu/genofire/wifictld-analyzer)
## Get wifictld-analyzer
#### Download
Latest Build binary from ci here:
[Download All](https://dev.sum7.eu/wifictld/wifictld-analyzer/-/jobs/artifacts/master/download/?job=build-my-project) (with config example)
[Download Binary](https://dev.sum7.eu/wifictld/wifictld-analyzer/-/jobs/artifacts/master/raw/wifictld-analyzer?inline=false&job=build-my-project)
#### Build
```bash
go get -u dev.sum7.eu/wifictld/wifictld-analyzer
```
## Configure
see `config_example.conf`
## Start / Boot
_/lib/systemd/system/wifictld-analyzer.service_ :
```
[Unit]
Description=wifictld-analyzer
After=network.target
[Service]
Type=simple
# User=notRoot
ExecStart=/opt/go/bin/wifictld-analyzer controller /etc/wifictld-analyzer.conf
Restart=always
RestartSec=5sec
[Install]
WantedBy=multi-user.target
```
Start: `systemctl start wifictld-analyzer`
Autostart: `systemctl enable wifictld-analyzer`

View File

@ -3,19 +3,20 @@ package capture
import ( import (
"net" "net"
"github.com/bdlm/log" "dev.sum7.eu/wifictld/analyzer/data"
log "github.com/sirupsen/logrus"
) )
//Collector for capture //Collector for capture
type Collector struct { type Collector struct {
connections map[string]*net.UDPConn connections map[string]*net.UDPConn
handler Handler handler data.Handler
queue chan *Packet queue chan *Packet
stop chan interface{} stop chan interface{}
} }
// NewCollector creates a Collector struct // NewCollector creates a Collector struct
func NewCollector(handler Handler, ifaces []*IFaceConfig) *Collector { func NewCollector(handler data.Handler, ifaces []IFaceConfig) *Collector {
coll := &Collector{ coll := &Collector{
handler: handler, handler: handler,
@ -25,12 +26,6 @@ func NewCollector(handler Handler, ifaces []*IFaceConfig) *Collector {
} }
for _, iface := range ifaces { for _, iface := range ifaces {
if iface.Port == 0 {
iface.Port = Port
}
if iface.IPAddress == "" {
iface.IPAddress = MulticastAddressDefault
}
coll.listenUDP(iface) coll.listenUDP(iface)
} }
@ -48,7 +43,7 @@ func (coll *Collector) Close() {
close(coll.queue) close(coll.queue)
} }
func (coll *Collector) listenUDP(iface *IFaceConfig) { func (coll *Collector) listenUDP(iface IFaceConfig) {
ip := net.ParseIP(iface.IPAddress) ip := net.ParseIP(iface.IPAddress)
var conn *net.UDPConn var conn *net.UDPConn
var err error var err error
@ -95,7 +90,7 @@ type Packet struct {
func (coll *Collector) parser() { func (coll *Collector) parser() {
for obj := range coll.queue { for obj := range coll.queue {
msg, err := NewSocketMSG(obj.Raw) msg, err := data.NewSocketMSG(obj.Raw)
if err != nil { if err != nil {
log.Warnf("unable to unmarshal request from %s: %s", obj.Address.String(), err) log.Warnf("unable to unmarshal request from %s: %s", obj.Address.String(), err)
continue continue
@ -113,7 +108,7 @@ func (coll *Collector) parser() {
} }
// SendTo a specifical address // SendTo a specifical address
func (coll *Collector) SendTo(addr *net.UDPAddr, msg *SocketMSG) { func (coll *Collector) SendTo(addr *net.UDPAddr, msg *data.SocketMSG) {
log.Debugf("send[%s]: %s", addr, msg.String()) log.Debugf("send[%s]: %s", addr, msg.String())
data, err := msg.Marshal() data, err := msg.Marshal()
if err != nil { if err != nil {
@ -131,7 +126,7 @@ func (coll *Collector) SendTo(addr *net.UDPAddr, msg *SocketMSG) {
} }
// Send to every connection to default address // Send to every connection to default address
func (coll *Collector) Send(msg *SocketMSG) { func (coll *Collector) Send(msg *data.SocketMSG) {
log.Debugf("send: %s", msg.String()) log.Debugf("send: %s", msg.String())
data, err := msg.Marshal() data, err := msg.Marshal()
if err != nil { if err != nil {

View File

@ -1 +0,0 @@
package capture

View File

@ -1,20 +0,0 @@
package capture
import (
"time"
"dev.sum7.eu/wifictld/wifictld-analyzer/data"
)
// WifiClient datatype of wifictld
type WifiClient struct {
Addr data.HardwareAddr `json:"addr"`
Time time.Time `json:"time"`
TryProbe uint16 `json:"try_probe"`
TryAuth uint16 `json:"try_auth"`
Connected bool `json:"connected"`
Authed bool `json:"authed"`
FreqHighest uint16 `json:"freq_highest"`
SignalLowFreq int16 `json:"signal_low_freq"`
SignalHighFreq int16 `json:"signal_high_freq"`
}

View File

@ -1,67 +1,50 @@
package cmd package cmd
import ( import (
"net"
"os" "os"
"os/signal" "os/signal"
"strings"
"syscall" "syscall"
"dev.sum7.eu/genofire/golang-lib/database" log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/golang-lib/file"
"github.com/bdlm/log"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"dev.sum7.eu/wifictld/wifictld-analyzer/capture" "dev.sum7.eu/wifictld/analyzer/capture"
"dev.sum7.eu/wifictld/wifictld-analyzer/controller" "dev.sum7.eu/wifictld/analyzer/controller"
"dev.sum7.eu/wifictld/wifictld-analyzer/web" "dev.sum7.eu/wifictld/analyzer/database"
) )
type ControllerConfig struct { var (
Database database.Config `toml:"database"` central bool
Answer bool `toml:"answer"` )
Webserver *web.Config `toml:"webserver"`
Interfaces []*capture.IFaceConfig `toml:"interfaces"`
}
// queryCmd represents the query command // queryCmd represents the query command
var controllerCmd = &cobra.Command{ var controllerCmd = &cobra.Command{
Use: "controller <interfaces>", Use: "controller <interfaces>",
Short: "simulate a wifictld controller", Short: "simulate a wifictld controller",
Example: `analyzer controller "/etc/wifictld.conf"`, Example: `analyzer controller "eth0,wlan0"`,
Args: cobra.ExactArgs(1), Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
config := &ControllerConfig{} ifaces := strings.Split(args[0], ",")
file.ReadTOML(args[0], config) log.Infof("listen on: %s", ifaces)
if err := database.Open(config.Database); err != nil { var ifacesConfigs []capture.IFaceConfig
log.Panicf("no database connection: %s", err) for _, iface := range ifaces {
ifaceConfig := capture.IFaceConfig{
InterfaceName: iface,
Port: port,
IPAddress: ipAddress,
}
ifacesConfigs = append(ifacesConfigs, ifaceConfig)
} }
defer database.Close()
ctr := controller.NewController() db := database.NewDB()
ctr := controller.NewController(db, central)
defer ctr.Close() defer ctr.Close()
var handlers []capture.Handler coll := capture.NewCollector(ctr.Handler, ifacesConfigs)
if config.Webserver.Enable {
log.Infof("starting webserver on %s", config.Webserver.Bind)
srv := web.New(config.Webserver)
go srv.Start()
handlers = append(handlers, srv.Handler)
defer srv.Close()
}
coll := capture.NewCollector(func(addr *net.UDPAddr, msg *capture.SocketMSG) (*capture.SocketMSG, error) {
for _, a := range handlers {
a(addr, msg)
}
if !config.Answer {
ctr.Handler(addr, msg)
return nil, nil
}
return ctr.Handler(addr, msg)
}, config.Interfaces)
defer coll.Close() defer coll.Close()
ctr.Send = coll.Send ctr.Send = coll.Send
@ -77,5 +60,8 @@ var controllerCmd = &cobra.Command{
} }
func init() { func init() {
RootCMD.AddCommand(controllerCmd) RootCmd.AddCommand(controllerCmd)
controllerCmd.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)")
controllerCmd.Flags().StringVar(&ipAddress, "listen", capture.MulticastAddressDefault, "")
controllerCmd.Flags().BoolVar(&central, "central", false, "")
} }

View File

@ -7,10 +7,11 @@ import (
"strings" "strings"
"syscall" "syscall"
"github.com/bdlm/log" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"dev.sum7.eu/wifictld/wifictld-analyzer/capture" "dev.sum7.eu/wifictld/analyzer/capture"
"dev.sum7.eu/wifictld/analyzer/data"
) )
var ( var (
@ -29,9 +30,9 @@ var dumpCmd = &cobra.Command{
log.Infof("listen on: %s", ifaces) log.Infof("listen on: %s", ifaces)
var ifacesConfigs []*capture.IFaceConfig var ifacesConfigs []capture.IFaceConfig
for _, iface := range ifaces { for _, iface := range ifaces {
ifaceConfig := &capture.IFaceConfig{ ifaceConfig := capture.IFaceConfig{
InterfaceName: iface, InterfaceName: iface,
Port: port, Port: port,
IPAddress: ipAddress, IPAddress: ipAddress,
@ -39,9 +40,9 @@ var dumpCmd = &cobra.Command{
ifacesConfigs = append(ifacesConfigs, ifaceConfig) ifacesConfigs = append(ifacesConfigs, ifaceConfig)
} }
capture.DEBUG = debug data.DEBUG = debug
coll := capture.NewCollector(func(addr *net.UDPAddr, msg *capture.SocketMSG) (*capture.SocketMSG, error) { coll := capture.NewCollector(func(addr *net.UDPAddr, msg *data.SocketMSG) (*data.SocketMSG, error) {
log.Infof("recv[%s]: %s", addr, msg.String()) log.Infof("recv[%s]: %s", addr, msg.String())
return nil, nil return nil, nil
}, ifacesConfigs) }, ifacesConfigs)
@ -57,7 +58,7 @@ var dumpCmd = &cobra.Command{
} }
func init() { func init() {
RootCMD.AddCommand(dumpCmd) 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().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, "") dumpCmd.Flags().StringVar(&ipAddress, "listen", capture.MulticastAddressDefault, "")
} }

View File

@ -1,27 +1,24 @@
package cmd package cmd
import ( import (
"github.com/bdlm/log" log "github.com/sirupsen/logrus"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
var ( var debug bool
debug bool
timestamps bool
)
// RootCMD represents the base command when called without any subcommands // RootCmd represents the base command when called without any subcommands
var RootCMD = &cobra.Command{ var RootCmd = &cobra.Command{
Use: "analyzer", Use: "analyzer",
Short: "wifictld analyzer", Short: "wifictld analyzer",
Long: `capture wifictld traffic and display thus`, Long: `capture wifictld traffic and display thus`,
} }
// Execute adds all child commands to the root command and sets flags appropriately. // 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. // This is called by main.main(). It only needs to happen once to the rootCmd.
func Execute() { func Execute() {
if err := RootCMD.Execute(); err != nil { if err := RootCmd.Execute(); err != nil {
log.Error(err) log.Error(err)
} }
} }
@ -30,11 +27,7 @@ func init() {
if debug { if debug {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
} }
log.SetFormatter(&log.TextFormatter{
DisableTimestamp: timestamps,
})
log.Debug("show debug") log.Debug("show debug")
}) })
RootCMD.PersistentFlags().BoolVar(&debug, "v", false, "show debug log") RootCmd.PersistentFlags().BoolVar(&debug, "v", false, "show debug log")
RootCMD.PersistentFlags().BoolVar(&timestamps, "timestamps", false, "Enables timestamps for log output")
} }

View File

@ -1 +0,0 @@
package cmd

View File

@ -1,18 +0,0 @@
answer = false
[database]
type = "sqlite3"
logging = true
connection = "/tmp/wifictld.db"
# For Master-Slave cluster
# read_connection = ""
[webserver]
enable = true
bind = ":8080"
webroot = "./webroot/"
[[interfaces]]
ifname = "wlp4s0"
#port = 1000
#ip_address = "ff02::31f1"

View File

@ -2,41 +2,27 @@ package controller
import ( import (
"net" "net"
"time"
// "github.com/bdlm/log" // log "github.com/sirupsen/logrus"
"dev.sum7.eu/genofire/golang-lib/database"
"dev.sum7.eu/wifictld/wifictld-analyzer/capture" "dev.sum7.eu/wifictld/analyzer/data"
"dev.sum7.eu/wifictld/wifictld-analyzer/data"
) )
func (c *Controller) Handler(addr *net.UDPAddr, msg *capture.SocketMSG) (*capture.SocketMSG, error) { func (c *Controller) Handler(addr *net.UDPAddr, msg *data.SocketMSG) (*data.SocketMSG, error) {
ignore := false ignore := false
if msg.Types.Is(capture.SocketMSGTypeClient) && msg.Client != nil { if msg.Types.Is(data.SocketMSGTypeClient) && msg.Client != nil {
ignore = c.LearnClient(addr.IP, msg.Client) ignore = c.db.LearnClient(addr.IP, msg.Client)
} }
if !msg.Types.Is(capture.SocketMSGTypeRequest) { if !msg.Types.Is(data.SocketMSGTypeRequest) {
return nil, nil return nil, nil
} }
client := &data.Client{Addr: msg.Client.Addr} msg = &data.SocketMSG{
Types: (data.SocketMSGTypeResponse | data.SocketMSGTypeClient),
if result := database.Read.Select([]string{"try_probe", "try_auth"}).First(client); result.Error != nil { Client: c.db.GetClient(msg.Client.Addr),
return nil, result.Error
} }
msg = &capture.SocketMSG{ if c.central || !ignore {
Types: (capture.SocketMSGTypeResponse | capture.SocketMSGTypeClient),
Client: &capture.WifiClient{
Addr: msg.Client.Addr,
Time: time.Now(),
TryProbe: client.TryProbe,
TryAuth: client.TryAuth,
},
}
if !ignore {
return msg, nil return msg, nil
} }
return nil, nil return nil, nil

View File

@ -1,57 +0,0 @@
package controller
import (
"net"
"time"
// "github.com/bdlm/log"
"dev.sum7.eu/genofire/golang-lib/database"
"dev.sum7.eu/wifictld/wifictld-analyzer/capture"
"dev.sum7.eu/wifictld/wifictld-analyzer/data"
)
func (c *Controller) LearnClient(apIP net.IP, clientWifictl *capture.WifiClient) bool {
ret := false
// learn ap
ap := &data.AP{
IP: apIP,
Lastseen: time.Now(),
}
result := database.Read.First(ap)
if result.RowsAffected > 0 {
database.Write.Save(ap)
} else {
database.Write.Create(ap)
}
// learn client
client := &data.Client{
Addr: clientWifictl.Addr,
Lastseen: time.Now(),
APAddr: apIP,
Connected: clientWifictl.Connected,
SignalLowFreq: clientWifictl.SignalLowFreq,
SignalHighFreq: clientWifictl.SignalHighFreq,
}
database.Write.FirstOrCreate(client)
if clientWifictl.TryAuth > client.TryAuth {
client.TryAuth = clientWifictl.TryAuth
}
if clientWifictl.TryProbe > client.TryProbe {
client.TryProbe = clientWifictl.TryProbe
}
if client.FreqHighest < clientWifictl.FreqHighest {
ret = (client.FreqHighest != 0)
client.FreqHighest = clientWifictl.FreqHighest
}
if clientWifictl.Authed {
client.Authed = clientWifictl.Authed
}
database.Write.Save(client)
return ret
}

View File

@ -4,22 +4,25 @@ import (
"net" "net"
"time" "time"
"dev.sum7.eu/genofire/golang-lib/database" log "github.com/sirupsen/logrus"
"github.com/bdlm/log"
"dev.sum7.eu/wifictld/wifictld-analyzer/capture" "dev.sum7.eu/wifictld/analyzer/data"
"dev.sum7.eu/wifictld/wifictld-analyzer/data" "dev.sum7.eu/wifictld/analyzer/database"
) )
type Controller struct { type Controller struct {
SendTo func(addr *net.UDPAddr, msg *capture.SocketMSG) SendTo func(addr *net.UDPAddr, msg *data.SocketMSG)
Send func(msg *capture.SocketMSG) Send func(msg *data.SocketMSG)
ticker *time.Ticker db *database.DB
ticker *time.Ticker
central bool
} }
func NewController() *Controller { func NewController(db *database.DB, central bool) *Controller {
ctl := &Controller{ ctl := &Controller{
ticker: time.NewTicker(time.Minute), ticker: time.NewTicker(60 * time.Second),
db: db,
central: central,
} }
go ctl.Repeated() go ctl.Repeated()
return ctl return ctl
@ -30,12 +33,7 @@ func (c *Controller) Close() {
} }
func (c *Controller) Repeated() { func (c *Controller) Repeated() {
aps := 0
clients := 0
for range c.ticker.C { for range c.ticker.C {
database.Read.Model(&data.AP{}).Count(&aps) log.Infof("lerned: %d APs, %d Clients", len(c.db.APs), len(c.db.Clients))
database.Read.Model(&data.Client{}).Count(&clients)
log.Debugf("learned: %d APs, %d Clients", aps, clients)
} }
} }

View File

@ -1 +0,0 @@
package controller

View File

@ -1,20 +0,0 @@
package data
import (
"net"
"time"
"dev.sum7.eu/genofire/golang-lib/database"
)
type AP struct {
IP net.IP `json:"ip" gorm:"PRIMARY_KEY"`
Lastseen time.Time `json:"lastseen"`
Clients []Client `gorm:"foreignkey:APAddr" json:"-"`
}
// Function to initialize the database
func init() {
database.AddModel(&AP{})
}

View File

@ -1,26 +0,0 @@
package data
import (
"net"
"time"
"dev.sum7.eu/genofire/golang-lib/database"
)
type Client struct {
Addr HardwareAddr `gorm:"PRIMARY_KEY" json:"addr"`
APAddr net.IP `gorm:"column:ap" json:"ap"`
TryProbe uint16 `json:"try_probe"`
TryAuth uint16 `json:"try_auth"`
Connected bool `json:"connected"`
Authed bool `json:"authed"`
FreqHighest uint16 `json:"freq_highest"`
SignalLowFreq int16 `json:"signal_low_freq"`
SignalHighFreq int16 `json:"signal_high_freq"`
Lastseen time.Time `json:"lastseen"`
}
// Function to initialize the database
func init() {
database.AddModel(&Client{})
}

View File

@ -1,4 +1,4 @@
package capture package data
import "net" import "net"

View File

@ -1,25 +0,0 @@
package data
import "net"
type HardwareAddr net.HardwareAddr
//MarshalJSON to bytearray
func (a HardwareAddr) String() string {
return net.HardwareAddr(a).String()
}
//MarshalJSON to bytearray
func (a HardwareAddr) MarshalText() ([]byte, error) {
return []byte(a.String()), nil
}
// UnmarshalJSON from bytearray
func (a HardwareAddr) UnmarshalText(data []byte) error {
b, err := net.ParseMAC(string(data))
if err != nil {
return err
}
a = HardwareAddr(b)
return nil
}

View File

@ -1 +0,0 @@
package data

View File

@ -1,13 +1,12 @@
package capture package data
import ( import (
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"net"
"time" "time"
"github.com/bdlm/log" log "github.com/sirupsen/logrus"
"dev.sum7.eu/wifictld/wifictld-analyzer/data"
) )
var DEBUG = false var DEBUG = false
@ -24,15 +23,16 @@ const (
func (a SocketMSGType) Is(b SocketMSGType) bool { func (a SocketMSGType) Is(b SocketMSGType) bool {
if DEBUG { if DEBUG {
log.Debugf("SocketType: %x & %x = %x -> %t", a, b, (a & b), (a&b) > 0)
log.Debugf("SocketType: %x & %x = %x -> %b", a, b, (a & b), (a&b) > 0)
} }
return (a & b) > 0 return (a & b) > 0
} }
// SocketMSG package of wifictld format // SocketMSG package of wifictld format
type SocketMSG struct { type SocketMSG struct {
Types SocketMSGType `json:"types"` Types SocketMSGType
Client *WifiClient `json:"client,omitempty"` Client *WifiClient
} }
func NewSocketMSG(obj []byte) (*SocketMSG, error) { func NewSocketMSG(obj []byte) (*SocketMSG, error) {
@ -100,7 +100,7 @@ func (msg *SocketMSG) Unmarshal(obj []byte) error {
if msg.Types.Is(SocketMSGTypeClient) { if msg.Types.Is(SocketMSGTypeClient) {
msg.Client = &WifiClient{ msg.Client = &WifiClient{
Addr: data.HardwareAddr(obj[pos:(pos + 6)]), Addr: net.HardwareAddr(obj[pos:(pos + 6)]),
Time: time.Unix(int64(binary.BigEndian.Uint32(obj[(pos+6):(pos+10)])), 0), Time: time.Unix(int64(binary.BigEndian.Uint32(obj[(pos+6):(pos+10)])), 0),
TryProbe: binary.BigEndian.Uint16(obj[(pos + 10):(pos + 12)]), TryProbe: binary.BigEndian.Uint16(obj[(pos + 10):(pos + 12)]),
TryAuth: binary.BigEndian.Uint16(obj[(pos + 12):(pos + 14)]), TryAuth: binary.BigEndian.Uint16(obj[(pos + 12):(pos + 14)]),

View File

@ -1,21 +1,14 @@
package capture package data
import ( import (
"encoding/hex" "encoding/hex"
"log"
"testing" "testing"
"time" "time"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/bdlm/log"
"github.com/bdlm/std/logger"
) )
func init() {
DEBUG = true
log.SetLevel(logger.Debug)
}
func TestMsgIsTypes(t *testing.T) { func TestMsgIsTypes(t *testing.T) {
assert := assert.New(t) assert := assert.New(t)
types := SocketMSGType(5) types := SocketMSGType(5)

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
}

4
database/ap.go Normal file
View File

@ -0,0 +1,4 @@
package database
type AP struct {
}

60
database/client.go Normal file
View File

@ -0,0 +1,60 @@
package database
import (
"net"
"time"
// log "github.com/sirupsen/logrus"
"dev.sum7.eu/wifictld/analyzer/data"
)
type Client struct {
Addr net.HardwareAddr
Time time.Time
TryProbe uint16
TryAuth uint16
Connected bool
Authed bool
FreqHighest uint16
SignalLowFreq int16
SignalHighFreq int16
}
func (db *DB) LearnClient(apIP net.IP, clientWifictl *data.WifiClient) bool {
ret := false
apAddr := apIP.String()
ap, ok := db.APs[apAddr]
if !ok {
ap = &AP{}
db.APs[apAddr] = ap
}
clientAddr := clientWifictl.Addr.String()
client, ok := db.Clients[clientAddr]
if !ok {
client = &Client{
Addr: clientWifictl.Addr,
}
db.Clients[clientAddr] = client
}
client.Time = time.Now()
if client.FreqHighest < clientWifictl.FreqHighest {
ret = (client.FreqHighest != 0)
client.FreqHighest = clientWifictl.FreqHighest
}
return ret
}
func (db *DB) GetClient(addr net.HardwareAddr) *data.WifiClient {
client, ok := db.Clients[addr.String()]
wClient := &data.WifiClient{
Addr: addr,
Time: time.Now(),
}
if ok {
wClient.TryProbe = client.TryProbe
}
return wClient
}

13
database/main.go Normal file
View File

@ -0,0 +1,13 @@
package database
type DB struct {
Clients map[string]*Client `json:"client"`
APs map[string]*AP `json:"ap"`
}
func NewDB() *DB {
return &DB{
Clients: make(map[string]*Client),
APs: make(map[string]*AP),
}
}

View File

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

View File

@ -1,7 +0,0 @@
package web
type Config struct {
Enable bool `toml:"enable"`
Bind string `toml:"bind"`
Webroot string `toml:"webroot"`
}

View File

@ -1,77 +0,0 @@
package web
import (
"net"
"net/http"
"github.com/NYTimes/gziphandler"
"github.com/bdlm/log"
"dev.sum7.eu/genofire/golang-lib/database"
lib "dev.sum7.eu/genofire/golang-lib/http"
"dev.sum7.eu/genofire/golang-lib/websocket"
"dev.sum7.eu/wifictld/wifictld-analyzer/capture"
"dev.sum7.eu/wifictld/wifictld-analyzer/data"
)
type Server struct {
web *http.Server
ws *websocket.WebsocketHandlerService
}
// New creates a new webserver and starts it
func New(config *Config) *Server {
ws := websocket.NewWebsocketHandlerService()
ws.Listen("/ws")
http.HandleFunc("/data.json", func(w http.ResponseWriter, r *http.Request) {
type dataResponse struct {
APs []data.AP `json:"aps"`
Clients []data.Client `json:"clients"`
}
data := &dataResponse{}
if result := database.Read.Find(&data.APs); result.Error != nil {
log.WithField("error", result.Error.Error()).Warn("not possible to read APs")
http.Error(w, "not possible to read APs", http.StatusNotFound)
return
}
if result := database.Read.Find(&data.Clients); result.Error != nil {
log.WithField("error", result.Error.Error()).Warn("not possible to read Clients")
http.Error(w, "not possible to read Clients", http.StatusNotFound)
return
}
lib.Write(w, data)
log.Info("fetch data")
})
http.Handle("/", gziphandler.GzipHandler(http.FileServer(http.Dir(config.Webroot))))
return &Server{
web: &http.Server{
Addr: config.Bind,
},
ws: ws,
}
}
func (srv *Server) Handler(addr *net.UDPAddr, msg *capture.SocketMSG) (*capture.SocketMSG, error) {
srv.ws.SendAll(&websocket.Message{
Subject: "wifictld_pkg",
Body: map[string]interface{}{
"ip": addr.IP,
"msg": msg,
},
})
return msg, nil
}
func (srv *Server) Start() {
// service connections
if err := srv.web.ListenAndServe(); err != http.ErrServerClosed {
log.Panicf("webserver crashed: %s", err)
}
}
func (srv *Server) Close() {
srv.web.Close()
srv.ws.Close()
}

View File

@ -1,28 +0,0 @@
package web
import (
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestWebserver(t *testing.T) {
assert := assert.New(t)
srv := New(&Config{
Bind: ":12345",
Webroot: "/tmp",
})
assert.NotNil(srv)
go srv.Start()
time.Sleep(time.Millisecond * 200)
assert.Panics(func() {
srv.Start()
}, "not allowed to listen twice")
srv.Close()
}

View File

@ -1,6 +0,0 @@
wget -O vanilla-framework.css https://assets.ubuntu.com/v1/vanilla-framework-version-1.8.1.min.css
wget -O vue.js https://vuejs.org/js/vue.min.js
wget -O vue-route.js https://unpkg.com/vue-router/dist/vue-router.js
wget -O vuex.js https://unpkg.com/vuex@2.0.0/dist/vuex.min.js
wget -O vue-websocket.js https://raw.githubusercontent.com/nathantsoi/vue-native-websocket/master/dist/build.js
wget -O vue-websocket.js.map https://raw.githubusercontent.com/nathantsoi/vue-native-websocket/master/dist/build.js.map

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,10 +0,0 @@
.p-navigation__logo {
font-size: 1.5rem;
line-height: 3rem;
}
.p-navigation .p-navigation__logo .p-navigation__link {
color: red;
}
.p-navigation .p-navigation__logo.online .p-navigation__link {
color: black;
}

View File

@ -1,48 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Wifictld</title>
<link rel="stylesheet" href="assets/vanilla-framework.css" />
<link rel="stylesheet" href="css/main.css" />
<script src="assets/vue.js"></script>
<script src="assets/vue-route.js"></script>
<script src="assets/vuex.js"></script>
<script src="assets/vue-websocket.js"></script>
<script src="js/utils.js"></script>
</head>
<body role="document">
<div id="app">
<header id="navigation" class="p-navigation">
<div class="p-navigation__banner">
<navbar-logo></navbar-logo>
<a href="#navigation" class="p-navigation__toggle--open" title="menu">Menu</a>
<a href="#navigation-closed" class="p-navigation__toggle--close" title="close menu">Close menu</a>
</div>
<nav class="p-navigation__nav" role="menubar">
<span class="u-off-screen">
<a href="#main-content">Jump to main content</a>
</span>
<ul class="p-navigation__links" role="menu">
<router-link :to="{name: 'aps' }" tag="li" class="p-navigation__link" active-class="is-selected">
<a>Access Points</a>
</router-link>
<router-link :to="{name: 'clients' }" tag="li" class="p-navigation__link" active-class="is-selected">
<a>Clients</a>
</router-link>
</ul>
</nav>
</header>
<div class="content">
<router-view></router-view>
</div>
</div>
<script src="js/view/accesspoint.js"></script>
<script src="js/view/clients.js"></script>
<script src="js/store.js"></script>
<script src="js/view.js"></script>
<script src="js/data.js"></script>
<script src="js/main.js"></script>
</body>
</html>

View File

@ -1,32 +0,0 @@
store.commit('setEvent', {
subject:"wifictld_pkg",
callback: (state, msg) => {
// add to ap list
const ip = msg.body.ip;
if(state.controller._ap[ip] === undefined){
state.controller.ap.push(ip)
state.controller._ap[ip] = null;
}
// add clients
const client = msg.body.msg.client;
if (state.controller.clients[client.addr] === undefined) {
state.controller._clients.push(client.addr)
}
client.ap = ip;
state.controller.clients[client.addr] = client;
}
})
VueNativeSock.default.install(Vue, `//${location.host}${location.pathname}ws`, {
store: store,
reconnection: true,
reconnectionDelay: 5000,
format: 'json',
})
getJSON(`//${location.host}${location.pathname}data.json`).then(function(data){
store.commit('initData',data);
})

View File

@ -1,6 +0,0 @@
const app = new Vue({
el: '#app',
store,
router,
components: { NavbarLogo },
})

View File

@ -1,118 +0,0 @@
function newUUID () {
/* eslint-disable */
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = Math.random() * 16 | 0,
v = c === 'x' ? r : r & 0x3 | 0x8;
return v.toString(16);
});
/* eslint-enable */
}
const store = new Vuex.Store({
state: {
socket: {
_session: localStorage.getItem('session'),
isConnected: false,
reconnectError: false,
eventMSGID: {},
eventTo: {},
},
controller: {
_ap : {},
ap: [],
_clients: [],
clients: {},
},
},
mutations:{
SOCKET_ONOPEN (state, event) {
state.socket.isConnected = true
},
SOCKET_ONCLOSE (state, event) {
state.socket.isConnected = false
},
SOCKET_ONERROR (state, event) {
console.error(state, event)
},
// default handler called for all methods
SOCKET_ONMESSAGE (state, msg) {
if (msg.subject === 'session_init') {
if (state.socket._session === null) {
state.socket._session = newUUID();
localStorage.setItem('session', state.socket._session);
}
msg.id = state.socket._session;
Vue.prototype.$socket.sendObj(msg);
return;
}
const msgFunc = state.socket.eventMSGID[msg.id];
if (msgFunc) {
msgFunc(state, msg);
delete state.socket.eventMSGID[msg.id];
return;
}
const eventFuncs = state.socket.eventTo[msg.subject];
if (typeof eventFuncs === 'object' && eventFuncs.length > 0) {
for (const key in eventFuncs) {
const func = eventFuncs[key];
if (func) {
func(state, msg);
}
}
return;
}
console.log(`unable to identify message: ${msg.subject}`);
},
// mutations for reconnect methods
SOCKET_RECONNECT(state, count) {
console.info(state, "reconnect:", count)
},
SOCKET_RECONNECT_ERROR(state) {
state.socket.reconnectError = true;
},
setEvent (state, data) {
state.socket.eventTo[data.subject] = [data.callback];
},
addEvent (state, data) {
if (typeof state.socket.eventTo[data.subject] !== 'object') {
state.socket.eventTo[data.subject] = [];
}
state.socket.eventTo[data.subject].push(data.callback);
},
delEvent (state, data) {
if (typeof state.socket.eventTo[data.subject] === 'object' && state.socket.eventTo[data.subject].length > 1) {
state.socket.eventTo[data.subject].pop(data.callback);
} else {
state.socket.eventTo[data.subject] = [];
}
},
call (state, data) {
if (!data.msg.id) {
data.msg.id = newUUID();
}
const ret = Vue.prototype.$socket.sendObj(data.msg);
if (typeof data.callback === 'function') {
state.socket.eventMSGID[data.msg.id] = data.callback;
}
return ret;
},
initData (state, data) {
data.aps.forEach((ap) => {
if(state.controller._ap[ap.ip] === undefined){
state.controller.ap.push(ap.ip)
state.controller._ap[ap.ip] = null;
}
})
data.clients.forEach((client) => {
if (state.controller.clients[client.addr] === undefined) {
state.controller._clients.push(client.addr)
}
state.controller.clients[client.addr] = client;
})
}
}
})

View File

@ -1,23 +0,0 @@
function get(url) {
return new Promise(function (resolve, reject) {
var req = new XMLHttpRequest();
req.open('GET', url);
req.onload = function onload() {
if (req.status === 200) {
resolve(req.response);
} else {
reject(Error(req.statusText));
}
};
req.onerror = function onerror() {
reject(Error('Network Error'));
};
req.send();
});
}
function getJSON(url) {
return get(url).then(JSON.parse);
}

View File

@ -1,21 +0,0 @@
const NavbarLogo = {
template: `<div class="p-navigation__logo" v-bind:class="{ online: isOnline }">
<a class="p-navigation__link" href="/">Wifictld</a>
</div>`,
computed: {
isOnline () {
return this.$store.state.socket.isConnected
}
}
};
const router = new VueRouter({
store,
routes: [
{ path: '/ap', component: ViewAccessPoints, name: "aps" },
{ path: '/ap/clients/:ip', component: ViewClients, name: "ap.clients"},
{ path: '/clients', component: ViewClients, name: "clients" },
{ path: '/', redirect: '/ap' }
]
})

View File

@ -1,37 +0,0 @@
const ViewAccessPoints = {
template: `<div class="row">
<p><span class="p-heading--one">AccessPoints</span></p>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" aria-sort="none">IP</th>
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">Users</th>
</tr>
</thead>
<tbody>
<tr role="row" v-for="item in getAPs">
<td role="rowheader" aria-label="IP">{{ item.ip.substring(21) }}</td>
<td role="gridcell" aria-label="Users" class="u-align--right">
<router-link :to="{ name: 'ap.clients', params: { ip: item.ip }}">
{{ item.clients }}
</router-link>
</td>
</tr>
</tbody>
</table>
</div>`,
computed: {
getAPs () {
const state = this.$store.state;
return state.controller.ap.map(function (ip) {
return {
ip: ip,
clients: state.controller._clients.filter(function (hwaddr) {
const client = state.controller.clients[hwaddr];
return client.ap === ip;
}).length,
};
})
},
}
}

View File

@ -1,42 +0,0 @@
const ViewClients = {
template: `<div class="row">
<p>
<span class="p-heading--one">Clients</span>
<sub v-if="$route.params.ip">of Router {{ $route.params.ip.substring(21) }}</sub>
</p>
<table class="p-table--mobile-card" role="grid">
<thead>
<tr role="row">
<th scope="col" role="columnheader" aria-sort="none">Addr</th>
<th scope="col" role="columnheader" aria-sort="none">AP</th>
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">FreqHighs</th>
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">SignalLow</th>
<th scope="col" role="columnheader" aria-sort="none" class="u-align--right">SignalHigh</th>
</tr>
</thead>
<tbody>
<tr role="row" v-for="item in getClients">
<td role="rowheader" aria-label="Addr">{{ item.addr.substring(12) }}</td>
<td role="gridcell" aria-label="AP">
<router-link :to="{ name: 'ap.clients', params: { ip: item.ap }}">
{{ item.ap.substring(21) }}
</router-link>
</td>
<td role="gridcell" aria-label="Freq Highs" class="u-align--right">{{ item.freq_highest }}</td>
<td role="gridcell" aria-label="Signal LowF" class="u-align--right">{{ item.signal_low_freq }}</td>
<td role="gridcell" aria-label="Signal HighF" class="u-align--right">{{ item.signal_high_freq }}</td>
</tr>
</tbody>
</table>
</div>`,
computed: {
getClients () {
const state = this.$store.state,
apIP = this.$route.params.ip;
return state.controller._clients.map(function(addr) {
return state.controller.clients[addr];
}).filter((client)=> (apIP === undefined || client.ap === apIP));
},
}
}