| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- // Package wifi provides WiFi packet capture and parsing
- package wifi
- import (
- "encoding/binary"
- "fmt"
- "net"
- "os/exec"
- "syscall"
- "time"
- )
- // 802.11 Frame Control field
- const (
- FrameTypeManagement = 0x00
- FrameSubtypeProbe = 0x04
- )
- // Radiotap constants
- const (
- RadiotapPresent_TSFT = 1 << 0
- RadiotapPresent_Flags = 1 << 1
- RadiotapPresent_Rate = 1 << 2
- RadiotapPresent_Channel = 1 << 3
- RadiotapPresent_FHSS = 1 << 4
- RadiotapPresent_DBM_Antsignal = 1 << 5
- )
- // CaptureSocket represents a raw packet capture socket
- type CaptureSocket struct {
- fd int
- iface string
- }
- // SetMonitorMode enables monitor mode on a WiFi interface
- func SetMonitorMode(iface string) error {
- // Bring interface down
- if err := exec.Command("ip", "link", "set", iface, "down").Run(); err != nil {
- return fmt.Errorf("ip link down: %w", err)
- }
- // Set monitor mode
- if err := exec.Command("iw", "dev", iface, "set", "type", "monitor").Run(); err != nil {
- return fmt.Errorf("iw set monitor: %w", err)
- }
- // Set monitor flags
- exec.Command("iw", "dev", iface, "set", "monitor", "control", "otherbss").Run()
- // Bring interface up
- if err := exec.Command("ip", "link", "set", iface, "up").Run(); err != nil {
- return fmt.Errorf("ip link up: %w", err)
- }
- return nil
- }
- // SetManagedMode restores managed mode on a WiFi interface
- func SetManagedMode(iface string) error {
- exec.Command("ip", "link", "set", iface, "down").Run()
- exec.Command("iw", "dev", iface, "set", "type", "managed").Run()
- exec.Command("ip", "link", "set", iface, "up").Run()
- return nil
- }
- // SetChannel sets the WiFi channel
- func SetChannel(iface string, channel int) error {
- return exec.Command("iw", "dev", iface, "set", "channel", fmt.Sprintf("%d", channel)).Run()
- }
- // NewCaptureSocket creates a raw packet capture socket
- func NewCaptureSocket(iface string) (*CaptureSocket, error) {
- // Get interface index
- ifi, err := net.InterfaceByName(iface)
- if err != nil {
- return nil, fmt.Errorf("interface %s: %w", iface, err)
- }
- // Create raw socket (ETH_P_ALL = 0x0003)
- fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(htons(0x0003)))
- if err != nil {
- return nil, fmt.Errorf("socket: %w", err)
- }
- // Bind to interface
- addr := syscall.SockaddrLinklayer{
- Protocol: htons(0x0003),
- Ifindex: ifi.Index,
- }
- if err := syscall.Bind(fd, &addr); err != nil {
- syscall.Close(fd)
- return nil, fmt.Errorf("bind: %w", err)
- }
- // Note: promiscuous mode not needed when interface is in monitor mode
- // Monitor mode already captures all packets
- return &CaptureSocket{fd: fd, iface: iface}, nil
- }
- // Read reads a packet from the socket
- func (c *CaptureSocket) Read(buf []byte) (int, error) {
- return syscall.Read(c.fd, buf)
- }
- // Close closes the capture socket
- func (c *CaptureSocket) Close() error {
- return syscall.Close(c.fd)
- }
- // Fd returns the file descriptor
- func (c *CaptureSocket) Fd() int {
- return c.fd
- }
- // htons converts a short (uint16) from host to network byte order
- func htons(i uint16) uint16 {
- return (i<<8)&0xff00 | i>>8
- }
- // ProbeRequest represents a parsed WiFi probe request
- type ProbeRequest struct {
- SourceMAC string
- RSSI int8
- SSID string
- Timestamp time.Time
- }
- // ParseRadiotapRSSI extracts RSSI from radiotap header
- func ParseRadiotapRSSI(data []byte) (headerLen int, rssi int8, ok bool) {
- if len(data) < 8 {
- return 0, 0, false
- }
- // Radiotap header: version(1) + pad(1) + length(2) + present_flags(4+)
- // version := data[0]
- headerLen = int(binary.LittleEndian.Uint16(data[2:4]))
- if headerLen > len(data) {
- return 0, 0, false
- }
- presentFlags := binary.LittleEndian.Uint32(data[4:8])
- offset := 8
- // Skip extended present flags if any
- for presentFlags&(1<<31) != 0 && offset+4 <= headerLen {
- presentFlags = binary.LittleEndian.Uint32(data[offset : offset+4])
- offset += 4
- }
- // Re-read first present flags
- presentFlags = binary.LittleEndian.Uint32(data[4:8])
- // Walk through present fields to find dBm antenna signal
- if presentFlags&RadiotapPresent_TSFT != 0 {
- offset = (offset + 7) &^ 7 // Align to 8 bytes
- offset += 8
- }
- if presentFlags&RadiotapPresent_Flags != 0 {
- offset += 1
- }
- if presentFlags&RadiotapPresent_Rate != 0 {
- offset += 1
- }
- if presentFlags&RadiotapPresent_Channel != 0 {
- offset = (offset + 1) &^ 1 // Align to 2 bytes
- offset += 4
- }
- if presentFlags&RadiotapPresent_FHSS != 0 {
- offset += 2
- }
- if presentFlags&RadiotapPresent_DBM_Antsignal != 0 {
- if offset < headerLen {
- rssi = int8(data[offset])
- return headerLen, rssi, true
- }
- }
- return headerLen, -100, true // Default RSSI if not found
- }
- // ParseProbeRequest parses a WiFi frame for probe requests
- func ParseProbeRequest(data []byte) (*ProbeRequest, bool) {
- // Parse radiotap header
- rtLen, rssi, ok := ParseRadiotapRSSI(data)
- if !ok || rtLen >= len(data) {
- return nil, false
- }
- frame := data[rtLen:]
- if len(frame) < 24 {
- return nil, false
- }
- // 802.11 header
- // Frame Control: type/subtype(2) + duration(2) + addr1(6) + addr2(6) + addr3(6) + seq(2)
- frameControl := binary.LittleEndian.Uint16(frame[0:2])
- // Check if it's a Probe Request (type=0, subtype=4)
- frameType := (frameControl >> 2) & 0x03
- frameSubtype := (frameControl >> 4) & 0x0F
- if frameType != FrameTypeManagement || frameSubtype != FrameSubtypeProbe {
- return nil, false
- }
- // Extract Source Address (addr2 at offset 10)
- srcMAC := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
- frame[10], frame[11], frame[12], frame[13], frame[14], frame[15])
- // Parse management frame body (after 24-byte header)
- body := frame[24:]
- // Parse information elements to find SSID
- ssid := ""
- offset := 0
- for offset+2 <= len(body) {
- elementID := body[offset]
- elementLen := int(body[offset+1])
- if offset+2+elementLen > len(body) {
- break
- }
- if elementID == 0 { // SSID
- ssidBytes := body[offset+2 : offset+2+elementLen]
- ssid = string(ssidBytes) // Best-effort UTF-8
- }
- offset += 2 + elementLen
- }
- return &ProbeRequest{
- SourceMAC: srcMAC,
- RSSI: rssi,
- SSID: ssid,
- Timestamp: time.Now(),
- }, true
- }
- // ChannelHopper cycles through WiFi channels
- type ChannelHopper struct {
- iface string
- channels []int
- dwell time.Duration
- stop chan struct{}
- }
- // NewChannelHopper creates a new channel hopper
- func NewChannelHopper(iface string, channels []int, dwell time.Duration) *ChannelHopper {
- return &ChannelHopper{
- iface: iface,
- channels: channels,
- dwell: dwell,
- stop: make(chan struct{}),
- }
- }
- // Start starts channel hopping
- func (h *ChannelHopper) Start() {
- go func() {
- i := 0
- for {
- select {
- case <-h.stop:
- return
- default:
- ch := h.channels[i%len(h.channels)]
- SetChannel(h.iface, ch)
- i++
- time.Sleep(h.dwell)
- }
- }
- }()
- }
- // Stop stops channel hopping
- func (h *ChannelHopper) Stop() {
- close(h.stop)
- }
|