mirror of
https://github.com/bettercap/bettercap
synced 2025-07-11 15:46:59 -07:00
Merge branch 'master' into wifi.flood
This commit is contained in:
commit
d6b9aa7b7f
24 changed files with 646 additions and 335 deletions
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/bettercap/bettercap/session"
|
||||
"github.com/bettercap/bettercap/tls"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
|
@ -146,10 +147,22 @@ func (api *RestAPI) Configure() error {
|
|||
|
||||
api.server.Addr = fmt.Sprintf("%s:%d", ip, port)
|
||||
|
||||
router := http.NewServeMux()
|
||||
router := mux.NewRouter()
|
||||
|
||||
router.HandleFunc("/api/session", api.sessionRoute)
|
||||
router.HandleFunc("/api/events", api.eventsRoute)
|
||||
router.HandleFunc("/api/session", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/ble", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/ble/{mac}", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/env", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/gateway", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/interface", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/lan", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/lan/{mac}", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/options", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/packets", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/started-at", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/wifi", api.sessionRoute)
|
||||
router.HandleFunc("/api/session/wifi/{mac}", api.sessionRoute)
|
||||
|
||||
api.server.Handler = router
|
||||
|
||||
|
|
|
@ -6,21 +6,11 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write an event to the client.
|
||||
writeWait = 10 * time.Second
|
||||
// Time allowed to read the next pong message from the client.
|
||||
pongWait = 60 * time.Second
|
||||
// Send pings to client with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
"github.com/gorilla/mux"
|
||||
)
|
||||
|
||||
type CommandRequest struct {
|
||||
|
@ -67,6 +57,71 @@ func (api *RestAPI) showSession(w http.ResponseWriter, r *http.Request) {
|
|||
toJSON(w, session.I)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showBle(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
mac := strings.ToLower(params["mac"])
|
||||
|
||||
if mac == "" {
|
||||
toJSON(w, session.I.BLE)
|
||||
} else if dev, found := session.I.BLE.Get(mac); found == true {
|
||||
toJSON(w, dev)
|
||||
} else {
|
||||
http.Error(w, "Not Found", 404)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) showEnv(w http.ResponseWriter, r *http.Request) {
|
||||
toJSON(w, session.I.Env)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showGateway(w http.ResponseWriter, r *http.Request) {
|
||||
toJSON(w, session.I.Gateway)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showInterface(w http.ResponseWriter, r *http.Request) {
|
||||
toJSON(w, session.I.Interface)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showLan(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
mac := strings.ToLower(params["mac"])
|
||||
|
||||
if mac == "" {
|
||||
toJSON(w, session.I.Lan)
|
||||
} else if host, found := session.I.Lan.Get(mac); found == true {
|
||||
toJSON(w, host)
|
||||
} else {
|
||||
http.Error(w, "Not Found", 404)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) showOptions(w http.ResponseWriter, r *http.Request) {
|
||||
toJSON(w, session.I.Options)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showPackets(w http.ResponseWriter, r *http.Request) {
|
||||
toJSON(w, session.I.Queue)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showStartedAt(w http.ResponseWriter, r *http.Request) {
|
||||
toJSON(w, session.I.StartedAt)
|
||||
}
|
||||
|
||||
func (api *RestAPI) showWiFi(w http.ResponseWriter, r *http.Request) {
|
||||
params := mux.Vars(r)
|
||||
mac := strings.ToLower(params["mac"])
|
||||
|
||||
if mac == "" {
|
||||
toJSON(w, session.I.WiFi)
|
||||
} else if station, found := session.I.WiFi.Get(mac); found == true {
|
||||
toJSON(w, station)
|
||||
} else if client, found := session.I.WiFi.GetClient(mac); found == true {
|
||||
toJSON(w, client)
|
||||
} else {
|
||||
http.Error(w, "Not Found", 404)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
var cmd CommandRequest
|
||||
|
@ -82,103 +137,12 @@ func (api *RestAPI) runSessionCommand(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) streamEvent(ws *websocket.Conn, event session.Event) error {
|
||||
msg, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
log.Error("Error while creating websocket message: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := ws.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
if !strings.Contains(err.Error(), "closed connection") {
|
||||
log.Error("Error while writing websocket message: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *RestAPI) sendPing(ws *websocket.Conn) error {
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||
log.Error("Error while writing websocket ping message: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *RestAPI) streamWriter(ws *websocket.Conn, w http.ResponseWriter, r *http.Request) {
|
||||
defer ws.Close()
|
||||
|
||||
// first we stream what we already have
|
||||
events := session.I.Events.Sorted()
|
||||
n := len(events)
|
||||
if n > 0 {
|
||||
log.Debug("Sending %d events.", n)
|
||||
for _, event := range events {
|
||||
if err := api.streamEvent(ws, event); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session.I.Events.Clear()
|
||||
|
||||
log.Debug("Listening for events and streaming to ws endpoint ...")
|
||||
|
||||
pingTicker := time.NewTicker(pingPeriod)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-pingTicker.C:
|
||||
if err := api.sendPing(ws); err != nil {
|
||||
return
|
||||
}
|
||||
case event := <-api.eventListener:
|
||||
if err := api.streamEvent(ws, event); err != nil {
|
||||
return
|
||||
}
|
||||
case <-api.quit:
|
||||
log.Info("Stopping websocket events streamer ...")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) streamReader(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
ws.SetReadLimit(512)
|
||||
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||
for {
|
||||
_, _, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
log.Debug("Closing websocket reader.")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) showEvents(w http.ResponseWriter, r *http.Request) {
|
||||
var err error
|
||||
|
||||
if api.useWebsocket {
|
||||
ws, err := api.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||
log.Error("Error while updating api.rest connection to websocket: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("Websocket streaming started for %s", r.RemoteAddr)
|
||||
|
||||
go api.streamWriter(ws, w, r)
|
||||
api.streamReader(ws)
|
||||
api.startStreamingEvents(w, r)
|
||||
} else {
|
||||
|
||||
events := session.I.Events.Sorted()
|
||||
nevents := len(events)
|
||||
nmax := nevents
|
||||
|
@ -210,12 +174,49 @@ func (api *RestAPI) sessionRoute(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if api.checkAuth(r) == false {
|
||||
setAuthFailed(w, r)
|
||||
} else if r.Method == "GET" {
|
||||
api.showSession(w, r)
|
||||
return
|
||||
} else if r.Method == "POST" {
|
||||
api.runSessionCommand(w, r)
|
||||
} else {
|
||||
return
|
||||
} else if r.Method != "GET" {
|
||||
http.Error(w, "Bad Request", 400)
|
||||
return
|
||||
}
|
||||
|
||||
path := r.URL.String()
|
||||
switch {
|
||||
case path == "/api/session":
|
||||
api.showSession(w, r)
|
||||
|
||||
case path == "/api/session/env":
|
||||
api.showEnv(w, r)
|
||||
|
||||
case path == "/api/session/gateway":
|
||||
api.showGateway(w, r)
|
||||
|
||||
case path == "/api/session/interface":
|
||||
api.showInterface(w, r)
|
||||
|
||||
case strings.HasPrefix(path, "/api/session/lan"):
|
||||
api.showLan(w, r)
|
||||
|
||||
case path == "/api/session/options":
|
||||
api.showOptions(w, r)
|
||||
|
||||
case path == "/api/session/packets":
|
||||
api.showPackets(w, r)
|
||||
|
||||
case path == "/api/session/started-at":
|
||||
api.showStartedAt(w, r)
|
||||
|
||||
case strings.HasPrefix(path, "/api/session/ble"):
|
||||
api.showBle(w, r)
|
||||
|
||||
case strings.HasPrefix(path, "/api/session/wifi"):
|
||||
api.showWiFi(w, r)
|
||||
|
||||
default:
|
||||
http.Error(w, "Not Found", 404)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,7 +225,10 @@ func (api *RestAPI) eventsRoute(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
if api.checkAuth(r) == false {
|
||||
setAuthFailed(w, r)
|
||||
} else if r.Method == "GET" {
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == "GET" {
|
||||
api.showEvents(w, r)
|
||||
} else if r.Method == "DELETE" {
|
||||
api.clearEvents(w, r)
|
||||
|
|
116
modules/api_rest_ws.go
Normal file
116
modules/api_rest_ws.go
Normal file
|
@ -0,0 +1,116 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/session"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
const (
|
||||
// Time allowed to write an event to the client.
|
||||
writeWait = 10 * time.Second
|
||||
// Time allowed to read the next pong message from the client.
|
||||
pongWait = 60 * time.Second
|
||||
// Send pings to client with this period. Must be less than pongWait.
|
||||
pingPeriod = (pongWait * 9) / 10
|
||||
)
|
||||
|
||||
func (api *RestAPI) streamEvent(ws *websocket.Conn, event session.Event) error {
|
||||
msg, err := json.Marshal(event)
|
||||
if err != nil {
|
||||
log.Error("Error while creating websocket message: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := ws.WriteMessage(websocket.TextMessage, msg); err != nil {
|
||||
if !strings.Contains(err.Error(), "closed connection") {
|
||||
log.Error("Error while writing websocket message: %s", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *RestAPI) sendPing(ws *websocket.Conn) error {
|
||||
ws.SetWriteDeadline(time.Now().Add(writeWait))
|
||||
if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
|
||||
log.Error("Error while writing websocket ping message: %s", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *RestAPI) streamWriter(ws *websocket.Conn, w http.ResponseWriter, r *http.Request) {
|
||||
defer ws.Close()
|
||||
|
||||
// first we stream what we already have
|
||||
events := session.I.Events.Sorted()
|
||||
n := len(events)
|
||||
if n > 0 {
|
||||
log.Debug("Sending %d events.", n)
|
||||
for _, event := range events {
|
||||
if err := api.streamEvent(ws, event); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
session.I.Events.Clear()
|
||||
|
||||
log.Debug("Listening for events and streaming to ws endpoint ...")
|
||||
|
||||
pingTicker := time.NewTicker(pingPeriod)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-pingTicker.C:
|
||||
if err := api.sendPing(ws); err != nil {
|
||||
return
|
||||
}
|
||||
case event := <-api.eventListener:
|
||||
if err := api.streamEvent(ws, event); err != nil {
|
||||
return
|
||||
}
|
||||
case <-api.quit:
|
||||
log.Info("Stopping websocket events streamer ...")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) streamReader(ws *websocket.Conn) {
|
||||
defer ws.Close()
|
||||
ws.SetReadLimit(512)
|
||||
ws.SetReadDeadline(time.Now().Add(pongWait))
|
||||
ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(pongWait)); return nil })
|
||||
for {
|
||||
_, _, err := ws.ReadMessage()
|
||||
if err != nil {
|
||||
log.Debug("Closing websocket reader.")
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (api *RestAPI) startStreamingEvents(w http.ResponseWriter, r *http.Request) {
|
||||
ws, err := api.upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
if _, ok := err.(websocket.HandshakeError); !ok {
|
||||
log.Error("Error while updating api.rest connection to websocket: %s", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
log.Debug("Websocket streaming started for %s", r.RemoteAddr)
|
||||
|
||||
go api.streamWriter(ws, w, r)
|
||||
api.streamReader(ws)
|
||||
}
|
|
@ -2,6 +2,7 @@ package modules
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
|
||||
type EventsStream struct {
|
||||
session.SessionModule
|
||||
output *os.File
|
||||
ignoreList *IgnoreList
|
||||
waitFor string
|
||||
waitChan chan *session.Event
|
||||
|
@ -22,6 +24,7 @@ type EventsStream struct {
|
|||
func NewEventsStream(s *session.Session) *EventsStream {
|
||||
stream := &EventsStream{
|
||||
SessionModule: session.NewSessionModule("events.stream", s),
|
||||
output: os.Stdout,
|
||||
quit: make(chan bool),
|
||||
waitChan: make(chan *session.Event),
|
||||
waitFor: "",
|
||||
|
@ -104,6 +107,11 @@ func NewEventsStream(s *session.Session) *EventsStream {
|
|||
return nil
|
||||
}))
|
||||
|
||||
stream.AddParam(session.NewStringParameter("events.stream.output",
|
||||
"",
|
||||
"",
|
||||
"If not empty, events will be written to this file instead of the standard output."))
|
||||
|
||||
return stream
|
||||
}
|
||||
|
||||
|
@ -119,11 +127,25 @@ func (s EventsStream) Author() string {
|
|||
return "Simone Margaritelli <evilsocket@protonmail.com>"
|
||||
}
|
||||
|
||||
func (s *EventsStream) Configure() error {
|
||||
return nil
|
||||
func (s *EventsStream) Configure() (err error) {
|
||||
var output string
|
||||
|
||||
if err, output = s.StringParam("events.stream.output"); err == nil {
|
||||
if output == "" {
|
||||
s.output = os.Stdout
|
||||
} else if output, err = core.ExpandPath(output); err == nil {
|
||||
s.output, err = os.OpenFile(output, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *EventsStream) Start() error {
|
||||
if err := s.Configure(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.SetRunning(true, func() {
|
||||
s.eventListener = s.Session.Events.Listen()
|
||||
for {
|
||||
|
@ -196,5 +218,8 @@ func (s *EventsStream) startWaitingFor(tag string, timeout int) error {
|
|||
func (s *EventsStream) Stop() error {
|
||||
return s.SetRunning(false, func() {
|
||||
s.quit <- true
|
||||
if s.output != os.Stdout {
|
||||
s.output.Close()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/bettercap/bettercap/core"
|
||||
|
@ -13,15 +14,15 @@ import (
|
|||
|
||||
const eventTimeFormat = "15:04:05"
|
||||
|
||||
func (s EventsStream) viewLogEvent(e session.Event) {
|
||||
fmt.Printf("[%s] [%s] [%s] %s\n",
|
||||
func (s *EventsStream) viewLogEvent(e session.Event) {
|
||||
fmt.Fprintf(s.output, "[%s] [%s] [%s] %s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
e.Label(),
|
||||
e.Data.(session.LogMessage).Message)
|
||||
}
|
||||
|
||||
func (s EventsStream) viewWiFiEvent(e session.Event) {
|
||||
func (s *EventsStream) viewWiFiEvent(e session.Event) {
|
||||
|
||||
if strings.HasPrefix(e.Tag, "wifi.ap.") {
|
||||
ap := e.Data.(*network.AccessPoint)
|
||||
|
@ -35,7 +36,7 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
|
|||
}
|
||||
|
||||
if e.Tag == "wifi.ap.new" {
|
||||
fmt.Printf("[%s] [%s] WiFi access point %s%s detected as %s%s.\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] WiFi access point %s%s detected as %s%s.\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
core.Bold(ap.ESSID()),
|
||||
|
@ -43,13 +44,13 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
|
|||
core.Green(ap.BSSID()),
|
||||
core.Dim(vend))
|
||||
} else if e.Tag == "wifi.ap.lost" {
|
||||
fmt.Printf("[%s] [%s] WiFi access point %s (%s) lost.\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] WiFi access point %s (%s) lost.\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
core.Red(ap.ESSID()),
|
||||
ap.BSSID())
|
||||
} else {
|
||||
fmt.Printf("[%s] [%s] %s\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] %s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
ap.String())
|
||||
|
@ -67,7 +68,7 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
|
|||
rssi = fmt.Sprintf(" (%d dBm)", probe.RSSI)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] [%s] Station %s%s is probing for SSID %s%s\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] Station %s%s is probing for SSID %s%s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
probe.FromAddr.String(),
|
||||
|
@ -77,7 +78,7 @@ func (s EventsStream) viewWiFiEvent(e session.Event) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s EventsStream) viewEndpointEvent(e session.Event) {
|
||||
func (s *EventsStream) viewEndpointEvent(e session.Event) {
|
||||
t := e.Data.(*network.Endpoint)
|
||||
vend := ""
|
||||
name := ""
|
||||
|
@ -93,7 +94,7 @@ func (s EventsStream) viewEndpointEvent(e session.Event) {
|
|||
}
|
||||
|
||||
if e.Tag == "endpoint.new" {
|
||||
fmt.Printf("[%s] [%s] Endpoint %s%s detected as %s%s.\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] Endpoint %s%s detected as %s%s.\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
core.Bold(t.IpAddress),
|
||||
|
@ -101,27 +102,27 @@ func (s EventsStream) viewEndpointEvent(e session.Event) {
|
|||
core.Green(t.HwAddress),
|
||||
core.Dim(vend))
|
||||
} else if e.Tag == "endpoint.lost" {
|
||||
fmt.Printf("[%s] [%s] Endpoint %s%s lost.\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] Endpoint %s%s lost.\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
core.Red(t.IpAddress),
|
||||
core.Dim(vend))
|
||||
} else {
|
||||
fmt.Printf("[%s] [%s] %s\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] %s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
t.String())
|
||||
}
|
||||
}
|
||||
|
||||
func (s EventsStream) viewModuleEvent(e session.Event) {
|
||||
fmt.Printf("[%s] [%s] %s\n",
|
||||
func (s *EventsStream) viewModuleEvent(e session.Event) {
|
||||
fmt.Fprintf(s.output, "[%s] [%s] %s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
e.Data)
|
||||
}
|
||||
|
||||
func (s EventsStream) viewSnifferEvent(e session.Event) {
|
||||
func (s *EventsStream) viewSnifferEvent(e session.Event) {
|
||||
se := e.Data.(SnifferEvent)
|
||||
misc := ""
|
||||
|
||||
|
@ -150,16 +151,16 @@ func (s EventsStream) viewSnifferEvent(e session.Event) {
|
|||
misc = fmt.Sprintf("%s", se.Data)
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] [%s] %s %s\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] %s %s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
se.Message,
|
||||
misc)
|
||||
}
|
||||
|
||||
func (s EventsStream) viewSynScanEvent(e session.Event) {
|
||||
func (s *EventsStream) viewSynScanEvent(e session.Event) {
|
||||
se := e.Data.(SynScanEvent)
|
||||
fmt.Printf("[%s] [%s] Found open port %d for %s\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] Found open port %d for %s\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
se.Port,
|
||||
|
@ -182,10 +183,10 @@ func (s *EventsStream) View(e session.Event, refresh bool) {
|
|||
} else if strings.HasPrefix(e.Tag, "syn.scan.") {
|
||||
s.viewSynScanEvent(e)
|
||||
} else {
|
||||
fmt.Printf("[%s] [%s] %v\n", e.Time.Format(eventTimeFormat), core.Green(e.Tag), e)
|
||||
fmt.Fprintf(s.output, "[%s] [%s] %v\n", e.Time.Format(eventTimeFormat), core.Green(e.Tag), e)
|
||||
}
|
||||
|
||||
if refresh {
|
||||
if refresh && s.output == os.Stdout {
|
||||
s.Session.Refresh()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ import (
|
|||
"github.com/bettercap/bettercap/session"
|
||||
)
|
||||
|
||||
func (s EventsStream) viewBLEEvent(e session.Event) {
|
||||
func (s *EventsStream) viewBLEEvent(e session.Event) {
|
||||
if e.Tag == "ble.device.new" {
|
||||
dev := e.Data.(*network.BLEDevice)
|
||||
name := dev.Device.Name()
|
||||
|
@ -23,7 +23,7 @@ func (s EventsStream) viewBLEEvent(e session.Event) {
|
|||
vend = fmt.Sprintf(" (%s)", core.Yellow(vend))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] [%s] New BLE device%s detected as %s%s %s.\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] New BLE device%s detected as %s%s %s.\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
name,
|
||||
|
@ -41,14 +41,14 @@ func (s EventsStream) viewBLEEvent(e session.Event) {
|
|||
vend = fmt.Sprintf(" (%s)", core.Yellow(vend))
|
||||
}
|
||||
|
||||
fmt.Printf("[%s] [%s] BLE device%s %s%s lost.\n",
|
||||
fmt.Fprintf(s.output, "[%s] [%s] BLE device%s %s%s lost.\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag),
|
||||
name,
|
||||
dev.Device.ID(),
|
||||
vend)
|
||||
} /* else {
|
||||
fmt.Printf("[%s] [%s]\n",
|
||||
fmt.Fprintf(s.output,"[%s] [%s]\n",
|
||||
e.Time.Format(eventTimeFormat),
|
||||
core.Green(e.Tag))
|
||||
} */
|
||||
|
|
|
@ -6,6 +6,6 @@ import (
|
|||
"github.com/bettercap/bettercap/session"
|
||||
)
|
||||
|
||||
func (s EventsStream) viewBLEEvent(e session.Event) {
|
||||
func (s *EventsStream) viewBLEEvent(e session.Event) {
|
||||
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
"github.com/bettercap/bettercap/packets"
|
||||
)
|
||||
|
||||
|
@ -62,7 +63,7 @@ func (w *WiFiModule) startDeauth(to net.HardwareAddr) error {
|
|||
if ap, found := w.Session.WiFi.Get(bssid); found == true {
|
||||
clients := ap.Clients()
|
||||
log.Info("Deauthing %d clients from AP %s ...", len(clients), ap.ESSID())
|
||||
w.onChannel(mhz2chan(ap.Frequency), func() {
|
||||
w.onChannel(network.Dot11Freq2Chan(ap.Frequency), func() {
|
||||
for _, c := range clients {
|
||||
if w.Running() == false {
|
||||
break
|
||||
|
@ -81,7 +82,7 @@ func (w *WiFiModule) startDeauth(to net.HardwareAddr) error {
|
|||
break
|
||||
} else if c, found := ap.Get(bssid); found == true {
|
||||
log.Info("Deauthing client %s from AP %s ...", c.HwAddress, ap.ESSID())
|
||||
w.onChannel(mhz2chan(ap.Frequency), func() {
|
||||
w.onChannel(network.Dot11Freq2Chan(ap.Frequency), func() {
|
||||
w.sendDeauthPacket(ap.HW, c.HW)
|
||||
})
|
||||
return nil
|
||||
|
|
58
modules/wifi_hopping.go
Normal file
58
modules/wifi_hopping.go
Normal file
|
@ -0,0 +1,58 @@
|
|||
package modules
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/bettercap/bettercap/log"
|
||||
"github.com/bettercap/bettercap/network"
|
||||
)
|
||||
|
||||
func (w *WiFiModule) onChannel(channel int, cb func()) {
|
||||
prev := w.stickChan
|
||||
w.stickChan = channel
|
||||
|
||||
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
|
||||
log.Warning("Error while hopping to channel %d: %s", channel, err)
|
||||
} else {
|
||||
log.Debug("Hopped on channel %d", channel)
|
||||
}
|
||||
|
||||
cb()
|
||||
|
||||
w.stickChan = prev
|
||||
}
|
||||
|
||||
func (w *WiFiModule) channelHopper() {
|
||||
w.reads.Add(1)
|
||||
defer w.reads.Done()
|
||||
|
||||
log.Info("Channel hopper started.")
|
||||
for w.Running() == true {
|
||||
delay := w.hopPeriod
|
||||
// if we have both 2.4 and 5ghz capabilities, we have
|
||||
// more channels, therefore we need to increase the time
|
||||
// we hop on each one otherwise me lose information
|
||||
if len(w.frequencies) > 14 {
|
||||
delay = delay * 2
|
||||
}
|
||||
|
||||
frequencies := w.frequencies
|
||||
for _, frequency := range frequencies {
|
||||
channel := network.Dot11Freq2Chan(frequency)
|
||||
// stick to the access point channel as long as it's selected
|
||||
// or as long as we're deauthing on it
|
||||
if w.stickChan != 0 {
|
||||
channel = w.stickChan
|
||||
}
|
||||
|
||||
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
|
||||
log.Warning("Error while hopping to channel %d: %s", channel, err)
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
if w.Running() == false {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -3,6 +3,8 @@ package modules
|
|||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -30,16 +32,18 @@ type WiFiProbe struct {
|
|||
type WiFiModule struct {
|
||||
session.SessionModule
|
||||
|
||||
handle *pcap.Handle
|
||||
channel int
|
||||
hopPeriod time.Duration
|
||||
frequencies []int
|
||||
ap *network.AccessPoint
|
||||
stickChan int
|
||||
skipBroken bool
|
||||
pktSourceChan chan gopacket.Packet
|
||||
writes *sync.WaitGroup
|
||||
reads *sync.WaitGroup
|
||||
handle *pcap.Handle
|
||||
source string
|
||||
channel int
|
||||
hopPeriod time.Duration
|
||||
frequencies []int
|
||||
ap *network.AccessPoint
|
||||
stickChan int
|
||||
skipBroken bool
|
||||
pktSourceChan chan gopacket.Packet
|
||||
pktSourceChanClosed bool
|
||||
writes *sync.WaitGroup
|
||||
reads *sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewWiFiModule(s *session.Session) *WiFiModule {
|
||||
|
@ -74,7 +78,7 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
|
|||
return err
|
||||
} else if ap, found := w.Session.WiFi.Get(bssid.String()); found == true {
|
||||
w.ap = ap
|
||||
w.stickChan = mhz2chan(ap.Frequency)
|
||||
w.stickChan = network.Dot11Freq2Chan(ap.Frequency)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Could not find station with BSSID %s", args[0])
|
||||
|
@ -85,6 +89,10 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
|
|||
func(args []string) error {
|
||||
w.ap = nil
|
||||
w.stickChan = 0
|
||||
var err error
|
||||
if w.frequencies, err = network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}))
|
||||
|
||||
|
@ -110,13 +118,43 @@ func NewWiFiModule(s *session.Session) *WiFiModule {
|
|||
return w.Show("rssi")
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewIntParameter("wifi.recon.channel",
|
||||
w.AddHandler(session.NewModuleHandler("wifi.recon.channel", `wifi\.recon\.channel[\s]+([0-9]+(?:[, ]+[0-9]+)*|clear)`,
|
||||
"WiFi channels (comma separated) or 'clear' for channel hopping.",
|
||||
func(args []string) error {
|
||||
newfrequencies := w.frequencies[:0]
|
||||
|
||||
if len(args) > 0 && args[0] != "clear" {
|
||||
channels := strings.Split(args[0], ",")
|
||||
for _, c := range channels {
|
||||
trimmed := strings.Trim(c, " ")
|
||||
channel, err := strconv.Atoi(trimmed)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newfrequencies = append(newfrequencies, network.Dot11Chan2Freq(channel))
|
||||
}
|
||||
} else {
|
||||
// No channels setted, retrieve frequencies supported by the card
|
||||
if frequencies, err := network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
newfrequencies = frequencies
|
||||
}
|
||||
}
|
||||
|
||||
w.frequencies = newfrequencies
|
||||
|
||||
return nil
|
||||
}))
|
||||
|
||||
w.AddParam(session.NewStringParameter("wifi.source.file",
|
||||
"",
|
||||
"WiFi channel or empty for channel hopping."))
|
||||
"",
|
||||
"If set, the wifi module will read from this pcap file instead of the hardware interface."))
|
||||
|
||||
w.AddParam(session.NewIntParameter("wifi.hop.period",
|
||||
"250",
|
||||
"If channel hopping is enabled (empty wifi.recon.channel), this is the time in millseconds the algorithm will hop on every channel (it'll be doubled if both 2.4 and 5.0 bands are available)."))
|
||||
"If channel hopping is enabled (empty wifi.recon.channel), this is the time in milliseconds the algorithm will hop on every channel (it'll be doubled if both 2.4 and 5.0 bands are available)."))
|
||||
|
||||
w.AddParam(session.NewBoolParameter("wifi.skip-broken",
|
||||
"true",
|
||||
|
@ -137,35 +175,34 @@ func (w WiFiModule) Author() string {
|
|||
return "Gianluca Braga <matrix86@protonmail.com> && Simone Margaritelli <evilsocket@protonmail.com>>"
|
||||
}
|
||||
|
||||
func mhz2chan(freq int) int {
|
||||
// ambo!
|
||||
if freq <= 2472 {
|
||||
return ((freq - 2412) / 5) + 1
|
||||
} else if freq == 2484 {
|
||||
return 14
|
||||
} else if freq >= 5035 && freq <= 5865 {
|
||||
return ((freq - 5035) / 5) + 7
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (w *WiFiModule) Configure() error {
|
||||
var hopPeriod int
|
||||
var err error
|
||||
|
||||
ihandle, err := pcap.NewInactiveHandle(w.Session.Interface.Name())
|
||||
if err != nil {
|
||||
if err, w.source = w.StringParam("wifi.source.file"); err != nil {
|
||||
return err
|
||||
}
|
||||
defer ihandle.CleanUp()
|
||||
|
||||
if err = ihandle.SetRFMon(true); err != nil {
|
||||
return fmt.Errorf("Error while setting interface %s in monitor mode: %s", core.Bold(w.Session.Interface.Name()), err)
|
||||
} else if err = ihandle.SetSnapLen(65536); err != nil {
|
||||
return err
|
||||
} else if err = ihandle.SetTimeout(pcap.BlockForever); err != nil {
|
||||
return err
|
||||
} else if w.handle, err = ihandle.Activate(); err != nil {
|
||||
return err
|
||||
if w.source != "" {
|
||||
if w.handle, err = pcap.OpenOffline(w.source); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
ihandle, err := pcap.NewInactiveHandle(w.Session.Interface.Name())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer ihandle.CleanUp()
|
||||
|
||||
if err = ihandle.SetRFMon(true); err != nil {
|
||||
return fmt.Errorf("Error while setting interface %s in monitor mode: %s", core.Bold(w.Session.Interface.Name()), err)
|
||||
} else if err = ihandle.SetSnapLen(65536); err != nil {
|
||||
return err
|
||||
} else if err = ihandle.SetTimeout(pcap.BlockForever); err != nil {
|
||||
return err
|
||||
} else if w.handle, err = ihandle.Activate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err, w.skipBroken = w.BoolParam("wifi.skip-broken"); err != nil {
|
||||
|
@ -176,69 +213,39 @@ func (w *WiFiModule) Configure() error {
|
|||
|
||||
w.hopPeriod = time.Duration(hopPeriod) * time.Millisecond
|
||||
|
||||
if err, w.channel = w.IntParam("wifi.recon.channel"); err == nil {
|
||||
if err = network.SetInterfaceChannel(w.Session.Interface.Name(), w.channel); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("WiFi recon active on channel %d.", w.channel)
|
||||
} else {
|
||||
w.channel = 0
|
||||
// we need to start somewhere, this is just to check if
|
||||
// this OS supports switching channel programmatically.
|
||||
if err = network.SetInterfaceChannel(w.Session.Interface.Name(), 1); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("WiFi recon active with channel hopping.")
|
||||
}
|
||||
if w.source == "" {
|
||||
// No channels setted, retrieve frequencies supported by the card
|
||||
if len(w.frequencies) == 0 {
|
||||
if w.frequencies, err = network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if frequencies, err := network.GetSupportedFrequencies(w.Session.Interface.Name()); err != nil {
|
||||
return err
|
||||
} else {
|
||||
w.frequencies = frequencies
|
||||
// we need to start somewhere, this is just to check if
|
||||
// this OS supports switching channel programmatically.
|
||||
if err = network.SetInterfaceChannel(w.Session.Interface.Name(), 1); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("WiFi recon active with channel hopping.")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *WiFiModule) onChannel(channel int, cb func()) {
|
||||
prev := w.stickChan
|
||||
w.stickChan = channel
|
||||
|
||||
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
|
||||
log.Warning("Error while hopping to channel %d: %s", channel, err)
|
||||
} else {
|
||||
log.Debug("Hopped on channel %d", channel)
|
||||
}
|
||||
|
||||
cb()
|
||||
|
||||
w.stickChan = prev
|
||||
}
|
||||
|
||||
func isZeroBSSID(bssid net.HardwareAddr) bool {
|
||||
for _, b := range bssid {
|
||||
if b != 0x00 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isBroadcastBSSID(bssid net.HardwareAddr) bool {
|
||||
for _, b := range bssid {
|
||||
if b != 0xff {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (w *WiFiModule) discoverAccessPoints(radiotap *layers.RadioTap, dot11 *layers.Dot11, packet gopacket.Packet) {
|
||||
// search for Dot11InformationElementIDSSID
|
||||
if ok, ssid := packets.Dot11ParseIDSSID(packet); ok == true {
|
||||
if isZeroBSSID(dot11.Address3) == false && isBroadcastBSSID(dot11.Address3) == false {
|
||||
bssid := dot11.Address3.String()
|
||||
frequency := int(radiotap.ChannelFrequency)
|
||||
from := dot11.Address3
|
||||
if network.IsZeroMac(from) == false && network.IsBroadcastMac(from) == false {
|
||||
var frequency int
|
||||
bssid := from.String()
|
||||
|
||||
if found, channel := packets.Dot11ParseDSSet(packet); found {
|
||||
frequency = network.Dot11Chan2Freq(channel)
|
||||
} else {
|
||||
frequency = int(radiotap.ChannelFrequency)
|
||||
}
|
||||
|
||||
w.Session.WiFi.AddIfNew(ssid, bssid, frequency, radiotap.DBMAntennaSignal)
|
||||
}
|
||||
}
|
||||
|
@ -317,40 +324,6 @@ func (w *WiFiModule) updateStats(dot11 *layers.Dot11, packet gopacket.Packet) {
|
|||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) channelHopper() {
|
||||
w.reads.Add(1)
|
||||
defer w.reads.Done()
|
||||
|
||||
log.Info("Channel hopper started.")
|
||||
for w.Running() == true {
|
||||
delay := w.hopPeriod
|
||||
// if we have both 2.4 and 5ghz capabilities, we have
|
||||
// more channels, therefore we need to increase the time
|
||||
// we hop on each one otherwise me lose information
|
||||
if len(w.frequencies) > 14 {
|
||||
delay = 500 * time.Millisecond
|
||||
}
|
||||
|
||||
for _, frequency := range w.frequencies {
|
||||
channel := mhz2chan(frequency)
|
||||
// stick to the access point channel as long as it's selected
|
||||
// or as long as we're deauthing on it
|
||||
if w.stickChan != 0 {
|
||||
channel = w.stickChan
|
||||
}
|
||||
|
||||
if err := network.SetInterfaceChannel(w.Session.Interface.Name(), channel); err != nil {
|
||||
log.Warning("Error while hopping to channel %d: %s", channel, err)
|
||||
}
|
||||
|
||||
time.Sleep(delay)
|
||||
if w.Running() == false {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WiFiModule) stationPruner() {
|
||||
w.reads.Add(1)
|
||||
defer w.reads.Done()
|
||||
|
@ -386,7 +359,7 @@ func (w *WiFiModule) Start() error {
|
|||
|
||||
w.SetRunning(true, func() {
|
||||
// start channel hopper if needed
|
||||
if w.channel == 0 {
|
||||
if w.channel == 0 && w.source == "" {
|
||||
go w.channelHopper()
|
||||
}
|
||||
|
||||
|
@ -423,6 +396,7 @@ func (w *WiFiModule) Start() error {
|
|||
w.updateStats(dot11, packet)
|
||||
}
|
||||
}
|
||||
w.pktSourceChanClosed = true
|
||||
})
|
||||
|
||||
return nil
|
||||
|
@ -433,7 +407,9 @@ func (w *WiFiModule) Stop() error {
|
|||
// wait any pending write operation
|
||||
w.writes.Wait()
|
||||
// signal the main for loop we want to exit
|
||||
w.pktSourceChan <- nil
|
||||
if w.pktSourceChanClosed == false {
|
||||
w.pktSourceChan <- nil
|
||||
}
|
||||
// close the pcap handle to make the main for exit
|
||||
w.handle.Close()
|
||||
// close the pcap handle to make the main for exit
|
||||
|
|
|
@ -17,7 +17,8 @@ func (w *WiFiModule) isApSelected() bool {
|
|||
return w.ap != nil
|
||||
}
|
||||
|
||||
func (w *WiFiModule) getRow(station *network.Station) []string {
|
||||
func (w *WiFiModule) getRow(station *network.Station) ([]string, bool) {
|
||||
include := false
|
||||
sinceStarted := time.Since(w.Session.StartedAt)
|
||||
sinceFirstSeen := time.Since(station.FirstSeen)
|
||||
|
||||
|
@ -61,16 +62,27 @@ func (w *WiFiModule) getRow(station *network.Station) []string {
|
|||
recvd = humanize.Bytes(station.Received)
|
||||
}
|
||||
|
||||
if w.source == "" {
|
||||
for _, frequencies := range w.frequencies {
|
||||
if frequencies == station.Frequency {
|
||||
include = true
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
include = true
|
||||
}
|
||||
|
||||
if w.isApSelected() {
|
||||
return []string{
|
||||
fmt.Sprintf("%d dBm", station.RSSI),
|
||||
bssid,
|
||||
/* station.Vendor, */
|
||||
strconv.Itoa(mhz2chan(station.Frequency)),
|
||||
strconv.Itoa(network.Dot11Freq2Chan(station.Frequency)),
|
||||
sent,
|
||||
recvd,
|
||||
seen,
|
||||
}
|
||||
}, include
|
||||
} else {
|
||||
// this is ugly, but necessary in order to have this
|
||||
// method handle both access point and clients
|
||||
|
@ -88,12 +100,12 @@ func (w *WiFiModule) getRow(station *network.Station) []string {
|
|||
ssid,
|
||||
/* station.Vendor, */
|
||||
encryption,
|
||||
strconv.Itoa(mhz2chan(station.Frequency)),
|
||||
strconv.Itoa(network.Dot11Freq2Chan(station.Frequency)),
|
||||
clients,
|
||||
sent,
|
||||
recvd,
|
||||
seen,
|
||||
}
|
||||
}, include
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -123,7 +135,9 @@ func (w *WiFiModule) Show(by string) error {
|
|||
|
||||
rows := make([][]string, 0)
|
||||
for _, s := range stations {
|
||||
rows = append(rows, w.getRow(s))
|
||||
if row, include := w.getRow(s); include == true {
|
||||
rows = append(rows, row)
|
||||
}
|
||||
}
|
||||
nrows := len(rows)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue