capture packages
This commit is contained in:
commit
5cea0a4ca2
|
@ -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
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
)
|
|
@ -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"`
|
||||||
|
}
|
|
@ -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, "")
|
||||||
|
}
|
|
@ -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")
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import "net"
|
||||||
|
|
||||||
|
type Handler func(addr *net.UDPAddr, msg *SocketMSG) (*SocketMSG, error)
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
Loading…
Reference in New Issue