capture.go 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. // Package wifi provides WiFi packet capture and parsing
  2. package wifi
  3. import (
  4. "encoding/binary"
  5. "fmt"
  6. "net"
  7. "os/exec"
  8. "syscall"
  9. "time"
  10. )
  11. // 802.11 Frame Control field
  12. const (
  13. FrameTypeManagement = 0x00
  14. FrameSubtypeProbe = 0x04
  15. )
  16. // Radiotap constants
  17. const (
  18. RadiotapPresent_TSFT = 1 << 0
  19. RadiotapPresent_Flags = 1 << 1
  20. RadiotapPresent_Rate = 1 << 2
  21. RadiotapPresent_Channel = 1 << 3
  22. RadiotapPresent_FHSS = 1 << 4
  23. RadiotapPresent_DBM_Antsignal = 1 << 5
  24. )
  25. // CaptureSocket represents a raw packet capture socket
  26. type CaptureSocket struct {
  27. fd int
  28. iface string
  29. }
  30. // SetMonitorMode enables monitor mode on a WiFi interface
  31. func SetMonitorMode(iface string) error {
  32. // Bring interface down
  33. if err := exec.Command("ip", "link", "set", iface, "down").Run(); err != nil {
  34. return fmt.Errorf("ip link down: %w", err)
  35. }
  36. // Set monitor mode
  37. if err := exec.Command("iw", "dev", iface, "set", "type", "monitor").Run(); err != nil {
  38. return fmt.Errorf("iw set monitor: %w", err)
  39. }
  40. // Set monitor flags
  41. exec.Command("iw", "dev", iface, "set", "monitor", "control", "otherbss").Run()
  42. // Bring interface up
  43. if err := exec.Command("ip", "link", "set", iface, "up").Run(); err != nil {
  44. return fmt.Errorf("ip link up: %w", err)
  45. }
  46. return nil
  47. }
  48. // SetManagedMode restores managed mode on a WiFi interface
  49. func SetManagedMode(iface string) error {
  50. exec.Command("ip", "link", "set", iface, "down").Run()
  51. exec.Command("iw", "dev", iface, "set", "type", "managed").Run()
  52. exec.Command("ip", "link", "set", iface, "up").Run()
  53. return nil
  54. }
  55. // SetChannel sets the WiFi channel
  56. func SetChannel(iface string, channel int) error {
  57. return exec.Command("iw", "dev", iface, "set", "channel", fmt.Sprintf("%d", channel)).Run()
  58. }
  59. // NewCaptureSocket creates a raw packet capture socket
  60. func NewCaptureSocket(iface string) (*CaptureSocket, error) {
  61. // Get interface index
  62. ifi, err := net.InterfaceByName(iface)
  63. if err != nil {
  64. return nil, fmt.Errorf("interface %s: %w", iface, err)
  65. }
  66. // Create raw socket (ETH_P_ALL = 0x0003)
  67. fd, err := syscall.Socket(syscall.AF_PACKET, syscall.SOCK_RAW, int(htons(0x0003)))
  68. if err != nil {
  69. return nil, fmt.Errorf("socket: %w", err)
  70. }
  71. // Bind to interface
  72. addr := syscall.SockaddrLinklayer{
  73. Protocol: htons(0x0003),
  74. Ifindex: ifi.Index,
  75. }
  76. if err := syscall.Bind(fd, &addr); err != nil {
  77. syscall.Close(fd)
  78. return nil, fmt.Errorf("bind: %w", err)
  79. }
  80. // Note: promiscuous mode not needed when interface is in monitor mode
  81. // Monitor mode already captures all packets
  82. return &CaptureSocket{fd: fd, iface: iface}, nil
  83. }
  84. // Read reads a packet from the socket
  85. func (c *CaptureSocket) Read(buf []byte) (int, error) {
  86. return syscall.Read(c.fd, buf)
  87. }
  88. // Close closes the capture socket
  89. func (c *CaptureSocket) Close() error {
  90. return syscall.Close(c.fd)
  91. }
  92. // Fd returns the file descriptor
  93. func (c *CaptureSocket) Fd() int {
  94. return c.fd
  95. }
  96. // htons converts a short (uint16) from host to network byte order
  97. func htons(i uint16) uint16 {
  98. return (i<<8)&0xff00 | i>>8
  99. }
  100. // ProbeRequest represents a parsed WiFi probe request
  101. type ProbeRequest struct {
  102. SourceMAC string
  103. RSSI int8
  104. SSID string
  105. Timestamp time.Time
  106. }
  107. // ParseRadiotapRSSI extracts RSSI from radiotap header
  108. func ParseRadiotapRSSI(data []byte) (headerLen int, rssi int8, ok bool) {
  109. if len(data) < 8 {
  110. return 0, 0, false
  111. }
  112. // Radiotap header: version(1) + pad(1) + length(2) + present_flags(4+)
  113. // version := data[0]
  114. headerLen = int(binary.LittleEndian.Uint16(data[2:4]))
  115. if headerLen > len(data) {
  116. return 0, 0, false
  117. }
  118. presentFlags := binary.LittleEndian.Uint32(data[4:8])
  119. offset := 8
  120. // Skip extended present flags if any
  121. for presentFlags&(1<<31) != 0 && offset+4 <= headerLen {
  122. presentFlags = binary.LittleEndian.Uint32(data[offset : offset+4])
  123. offset += 4
  124. }
  125. // Re-read first present flags
  126. presentFlags = binary.LittleEndian.Uint32(data[4:8])
  127. // Walk through present fields to find dBm antenna signal
  128. if presentFlags&RadiotapPresent_TSFT != 0 {
  129. offset = (offset + 7) &^ 7 // Align to 8 bytes
  130. offset += 8
  131. }
  132. if presentFlags&RadiotapPresent_Flags != 0 {
  133. offset += 1
  134. }
  135. if presentFlags&RadiotapPresent_Rate != 0 {
  136. offset += 1
  137. }
  138. if presentFlags&RadiotapPresent_Channel != 0 {
  139. offset = (offset + 1) &^ 1 // Align to 2 bytes
  140. offset += 4
  141. }
  142. if presentFlags&RadiotapPresent_FHSS != 0 {
  143. offset += 2
  144. }
  145. if presentFlags&RadiotapPresent_DBM_Antsignal != 0 {
  146. if offset < headerLen {
  147. rssi = int8(data[offset])
  148. return headerLen, rssi, true
  149. }
  150. }
  151. return headerLen, -100, true // Default RSSI if not found
  152. }
  153. // ParseProbeRequest parses a WiFi frame for probe requests
  154. func ParseProbeRequest(data []byte) (*ProbeRequest, bool) {
  155. // Parse radiotap header
  156. rtLen, rssi, ok := ParseRadiotapRSSI(data)
  157. if !ok || rtLen >= len(data) {
  158. return nil, false
  159. }
  160. frame := data[rtLen:]
  161. if len(frame) < 24 {
  162. return nil, false
  163. }
  164. // 802.11 header
  165. // Frame Control: type/subtype(2) + duration(2) + addr1(6) + addr2(6) + addr3(6) + seq(2)
  166. frameControl := binary.LittleEndian.Uint16(frame[0:2])
  167. // Check if it's a Probe Request (type=0, subtype=4)
  168. frameType := (frameControl >> 2) & 0x03
  169. frameSubtype := (frameControl >> 4) & 0x0F
  170. if frameType != FrameTypeManagement || frameSubtype != FrameSubtypeProbe {
  171. return nil, false
  172. }
  173. // Extract Source Address (addr2 at offset 10)
  174. srcMAC := fmt.Sprintf("%02x:%02x:%02x:%02x:%02x:%02x",
  175. frame[10], frame[11], frame[12], frame[13], frame[14], frame[15])
  176. // Parse management frame body (after 24-byte header)
  177. body := frame[24:]
  178. // Parse information elements to find SSID
  179. ssid := ""
  180. offset := 0
  181. for offset+2 <= len(body) {
  182. elementID := body[offset]
  183. elementLen := int(body[offset+1])
  184. if offset+2+elementLen > len(body) {
  185. break
  186. }
  187. if elementID == 0 { // SSID
  188. ssidBytes := body[offset+2 : offset+2+elementLen]
  189. ssid = string(ssidBytes) // Best-effort UTF-8
  190. }
  191. offset += 2 + elementLen
  192. }
  193. return &ProbeRequest{
  194. SourceMAC: srcMAC,
  195. RSSI: rssi,
  196. SSID: ssid,
  197. Timestamp: time.Now(),
  198. }, true
  199. }
  200. // ChannelHopper cycles through WiFi channels
  201. type ChannelHopper struct {
  202. iface string
  203. channels []int
  204. dwell time.Duration
  205. stop chan struct{}
  206. }
  207. // NewChannelHopper creates a new channel hopper
  208. func NewChannelHopper(iface string, channels []int, dwell time.Duration) *ChannelHopper {
  209. return &ChannelHopper{
  210. iface: iface,
  211. channels: channels,
  212. dwell: dwell,
  213. stop: make(chan struct{}),
  214. }
  215. }
  216. // Start starts channel hopping
  217. func (h *ChannelHopper) Start() {
  218. go func() {
  219. i := 0
  220. for {
  221. select {
  222. case <-h.stop:
  223. return
  224. default:
  225. ch := h.channels[i%len(h.channels)]
  226. SetChannel(h.iface, ch)
  227. i++
  228. time.Sleep(h.dwell)
  229. }
  230. }
  231. }()
  232. }
  233. // Stop stops channel hopping
  234. func (h *ChannelHopper) Stop() {
  235. close(h.stop)
  236. }