123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179 |
- package settings
- import (
- "bufio"
- "context"
- "fmt"
- "net"
- "os"
- "path/filepath"
- "strings"
- "time"
- "github.com/sagernet/sing-box/adapter"
- )
- type wpaSupplicantMonitor struct {
- socketPath string
- callback func(adapter.WIFIState)
- cancel context.CancelFunc
- }
- func newWpaSupplicantMonitor(callback func(adapter.WIFIState)) (WIFIMonitor, error) {
- socketDirs := []string{"/var/run/wpa_supplicant", "/run/wpa_supplicant"}
- for _, socketDir := range socketDirs {
- entries, err := os.ReadDir(socketDir)
- if err != nil {
- continue
- }
- for _, entry := range entries {
- if entry.IsDir() || entry.Name() == "." || entry.Name() == ".." {
- continue
- }
- socketPath := filepath.Join(socketDir, entry.Name())
- localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d", os.Getpid()), Net: "unixgram"}
- remoteAddr := &net.UnixAddr{Name: socketPath, Net: "unixgram"}
- conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
- if err != nil {
- continue
- }
- conn.Close()
- return &wpaSupplicantMonitor{socketPath: socketPath, callback: callback}, nil
- }
- }
- return nil, os.ErrNotExist
- }
- func (m *wpaSupplicantMonitor) ReadWIFIState() adapter.WIFIState {
- localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-%d", os.Getpid()), Net: "unixgram"}
- remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
- conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
- if err != nil {
- return adapter.WIFIState{}
- }
- defer conn.Close()
- conn.SetDeadline(time.Now().Add(3 * time.Second))
- status, err := m.sendCommand(conn, "STATUS")
- if err != nil {
- return adapter.WIFIState{}
- }
- var ssid, bssid string
- var connected bool
- scanner := bufio.NewScanner(strings.NewReader(status))
- for scanner.Scan() {
- line := scanner.Text()
- if strings.HasPrefix(line, "wpa_state=") {
- state := strings.TrimPrefix(line, "wpa_state=")
- connected = state == "COMPLETED"
- } else if strings.HasPrefix(line, "ssid=") {
- ssid = strings.TrimPrefix(line, "ssid=")
- } else if strings.HasPrefix(line, "bssid=") {
- bssid = strings.TrimPrefix(line, "bssid=")
- }
- }
- if !connected || ssid == "" {
- return adapter.WIFIState{}
- }
- return adapter.WIFIState{
- SSID: ssid,
- BSSID: strings.ToUpper(strings.ReplaceAll(bssid, ":", "")),
- }
- }
- func (m *wpaSupplicantMonitor) sendCommand(conn *net.UnixConn, command string) (string, error) {
- _, err := conn.Write([]byte(command + "\n"))
- if err != nil {
- return "", err
- }
- buf := make([]byte, 4096)
- n, err := conn.Read(buf)
- if err != nil {
- return "", err
- }
- response := string(buf[:n])
- if strings.HasPrefix(response, "FAIL") {
- return "", os.ErrInvalid
- }
- return strings.TrimSpace(response), nil
- }
- func (m *wpaSupplicantMonitor) Start() error {
- if m.callback == nil {
- return nil
- }
- ctx, cancel := context.WithCancel(context.Background())
- m.cancel = cancel
- state := m.ReadWIFIState()
- go m.monitorEvents(ctx, state)
- m.callback(state)
- return nil
- }
- func (m *wpaSupplicantMonitor) monitorEvents(ctx context.Context, lastState adapter.WIFIState) {
- var consecutiveErrors int
- localAddr := &net.UnixAddr{Name: fmt.Sprintf("@sing-box-wpa-mon-%d", os.Getpid()), Net: "unixgram"}
- remoteAddr := &net.UnixAddr{Name: m.socketPath, Net: "unixgram"}
- conn, err := net.DialUnix("unixgram", localAddr, remoteAddr)
- if err != nil {
- return
- }
- defer conn.Close()
- _, err = conn.Write([]byte("ATTACH\n"))
- if err != nil {
- return
- }
- buf := make([]byte, 4096)
- n, err := conn.Read(buf)
- if err != nil || !strings.HasPrefix(string(buf[:n]), "OK") {
- return
- }
- for {
- select {
- case <-ctx.Done():
- return
- default:
- }
- conn.SetReadDeadline(time.Now().Add(30 * time.Second))
- n, err := conn.Read(buf)
- if err != nil {
- consecutiveErrors++
- if consecutiveErrors > 10 {
- return
- }
- time.Sleep(time.Second)
- continue
- }
- consecutiveErrors = 0
- msg := string(buf[:n])
- if strings.Contains(msg, "CTRL-EVENT-CONNECTED") || strings.Contains(msg, "CTRL-EVENT-DISCONNECTED") {
- state := m.ReadWIFIState()
- if state != lastState {
- lastState = state
- m.callback(state)
- }
- }
- }
- }
- func (m *wpaSupplicantMonitor) Close() error {
- if m.cancel != nil {
- m.cancel()
- }
- return nil
- }
|