以编程方式使用Ruby和ioctl获取wifi BSSID

问题描述:

使用Getting essid via ioctl in ruby作为模板我想获取BSSID而不是ESSID。但是,不是C开发人员,有一些我不明白的东西。以编程方式使用Ruby和ioctl获取wifi BSSID

我有什么到目前为止这确实工作:(...

注意我有点困惑,因为我的一部分认为,根据wireless.h一些意见,认为BSSID只能是设置通过ioctl。然而,从ioctl到得到存在。随着我几乎完全不了解更多的中间C型(结构,工会和东西;)),我只是不知道不知道。

def _get_bssid(interface) 
    # Copied from wireless.h 
    # supposing a 16 byte address and 32 byte buffer but I'm totally 
    # guessing here. 
    iwreq = [interface, '' * 48,0].pack('a*pI') 
    sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) 

    # from wireless.h 
    # SIOCGIWAP 0x8B15  /* get access point MAC addresses */ 
    sock.ioctl('0x8B15', iwreq) # always get an error: Can't convert string to Integer 

    puts iwreq.inspect 
end 

所以,在此之前,我使用的是wpa_cli方法抓住BSSID,但我宁愿使用IOCTL:

def _wpa_status(interface) 
    wpa_data = nil 

    unless interface.nil? 
     # need to write a method to get the src_sock_path 
     # programmatically. Fortunately, for me 
     # this is going to be the correct sock path 99% of the time. 
     # Ideas to get programmatically would be: 
     # parse wpa_supplicant.conf 
     # check process table | grep wpa_suppl | parse arguments 
     src_sock_path = '/var/run/wpa_supplicant/' + interface 
    else 
     return nil 
    end 

    client_sock_path = '/var/run/hwinfo_wpa' 

    # open Domain socket 
    socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM, 0) 

    begin 
     # bind client domain socket 
     socket.bind(Socket.pack_sockaddr_un(client_sock_path)) 

     # connect to server with our client socket 
     socket.connect(Socket.pack_sockaddr_un(src_sock_path)) 

     # send STATUS command 
     socket.send('STATUS', 0) 

     # receive 1024 bytes (totally arbitrary value) 
     # split lines by \n 
     # store in variable wpa_data. 
     wpa_data = socket.recv(1024) 
    rescue => e 
     $stderr.puts 'WARN: unable to gather wpa data: ' + e.inspect 
    end 
    # close or next time we attempt to read it will fail. 
    socket.close 

    begin 
     # remove the domain socket file for the client 
     File.unlink(client_sock_path) 
    rescue => e 
     $stderr.puts 'WARN: ' + e.inspect 
    end 

    unless wpa_data.nil? 
     @wifis = Hash[wpa_data.split(/\n/).map\ 
       {|line| 
        # first, split into pairs delimited by '=' 
        key,value = line.split('=') 

        # if key is camel-humped then put space in front 
        # of capped letter 
        if key =~ /[a-z][A-Z]/ 
         key.gsub!(/([a-z])([A-Z])/,'\\1_\\2') 
        end 

        # if key is "id" then rename it. 
        key.eql?('id') && key = 'wpa_id' 

        # fix key so that it can be used as a table name 
        # by replacing spaces with underscores 
        key.gsub!(' ','_') 

        # lower case it. 
        key.downcase! 

        [key,value] 
       }] 
    end 
end 

编辑: 到目前为止还没有人能回答这个问题。我想我更喜欢wpa方法,因为我从中获得更多的数据。也就是说,我想说的是,如果有人使用wpa代码,请注意它需要升级权限才能读取套接字。

编辑^ 2(完整的代码片段): 感谢@dasup我已经能够上我的课重因子正确拉BSSID和使用系统的读写控制的ESSID。 (因人而异给出的实施,年龄和其他任何可能破坏稳定的事情你的Linux发行版 - 下面的代码片段与3.2和3.7内核的工作虽然)

require 'socket' 

class Wpa 
    attr_accessor :essid, :bssid, :if 

    def initialize(interface) 
     @if = interface 

     puts 'essid: ' + _get_essid.inspect 
     puts 'bssid: ' + _get_bssid.inspect 
    end 

    def _get_essid 
     # Copied from wireless.h 
     iwreq = [@if, " " * 32, 32, 0 ].pack('a16pII') 

     sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) 
     sock.ioctl(0x8B1B, iwreq) 

     @essid = iwreq.unpack('@16p').pop.strip 
    end 

    def _get_bssid 
     # Copied from wireless.h 
     # supposing a 16 byte address and 32 byte buffer but I'm totally 
     # guessing here. 
     iwreq = [@if, "\0" * 32].pack('a16a32') 
     sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) 

     # from wireless.h 
     # SIOCGIWAP 0x8B15  /* get access point MAC addresses */ 
     sock.ioctl(0x8B15, iwreq) # always get an error: Can't convert string to Integer 

     @bssid = iwreq.unpack('@18H2H2H2H2H2H2').join(':') 
    end 
end 

h = Wpa.new('wlan0') 

我不是很熟悉的红宝石,但是我发现了两个错误:

  • SIOCGIWAP的十六进制数应该没有引号/嘀嗒声。
  • 数据缓冲区的初始化以接口名称后面的一些尾随字节结尾(使用gdb进行调试)。下面给出的初始化工作。

要知道,你的代码将打破,如果任何数据结构或常数的变化(IFNAMSIZ,上sa_family,结构sockaddr等),但我不认为这样的变化很快可能随时。

require 'socket' 

def _get_bssid(interface) 
    # Copied from wireless.h 
    # supposing a 16 byte address and 32 byte buffer but I'm totally 
    # guessing here. 
    iwreq = [interface, "\0" * 32].pack('a16a32') 
    sock = Socket.new(Socket::AF_INET, Socket::SOCK_DGRAM, 0) 

    # from wireless.h 
    # SIOCGIWAP 0x8B15  /* get access point MAC addresses */ 
    sock.ioctl(0x8B15, iwreq) # always get an error: Can't convert string to Integer 

    puts iwreq.inspect 
end 

你会得到一个数组/有缓冲:

  • 您发送的接口名称,补齐0x00字节到16个字节的总长度。
  • 后面跟着struct sockaddr,即一个双字节标识符0x01 0x00(来自ARPHRD_ETHER?),接着是填充有0x00字节的BSSID,总长度为14字节。

祝你好运!

+0

这太棒了。然而,我遇到的一个问题是从ioctl返回的数组中提取BSSID。当我解压缩它时,我得到一个“\ x00”的BSSID。我想我可能会错误地解压缩它:'interface,foo,@bssid = iwreq.unpack('a16x214a')' – Jim

+0

请参见[Ruby包含#unpack方法的文档](http://www.ruby-doc.org/芯2.1.0/String.html#方法-I-解包)。以下工作适用于我,并以当前AP的MAC地址作为十六进制数字串:'bssid = iwreq.unpack('x16x2H12')'。 (它跳过16个字节的零填充接口名称和两个字节的地址类型。)如果我的答案解决了您的问题,请不要忘记选择它作为接受的答案。 – dasup

+0

啊! DUH!我在想什么?十六进制值返回!不是ascii!感谢你! – Jim