Class: WifiWand::MacOsModel

Inherits:
BaseModel show all
Defined in:
lib/wifi-wand/models/mac_os_model.rb

Instance Attribute Summary

Attributes inherited from BaseModel

#verbose_mode, #wifi_interface

Instance Method Summary collapse

Methods inherited from BaseModel

#connect, #connected_to?, #connected_to_internet?, #cycle_network, #preferred_network_password, #public_ip_address_info, #random_mac_address, #remove_preferred_networks, #run_os_command, #till, #try_os_command_until

Constructor Details

#initialize(options = OpenStruct.new) ⇒ MacOsModel

Takes an OpenStruct containing options such as verbose mode and interface name.



13
14
15
# File 'lib/wifi-wand/models/mac_os_model.rb', line 13

def initialize(options = OpenStruct.new)
  super
end

Instance Method Details

#available_network_namesObject

Returns the network names sorted in descending order of signal strength.



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/wifi-wand/models/mac_os_model.rb', line 53

def available_network_names
  return nil unless wifi_on? # no need to try

  # run_swift_command('AvailableWifiNetworkLister').split("\n").uniq

  json_text = run_os_command('system_profiler -json SPAirPortDataType')
  data = JSON.parse(json_text)

  inner_key = connected_network_name ? 'spairport_airport_other_local_wireless_networks' : 'spairport_airport_local_wireless_networks'

  nets = data['SPAirPortDataType'] \
     .detect { |h| h.key?('spairport_airport_interfaces') } \
      ['spairport_airport_interfaces'] \
     .detect { |h| h['_name'] == wifi_interface } \
      [inner_key] \
      .sort_by { |net| -net['spairport_signal_noise'].split('/').first.to_i }
  nets.map { |h| h['_name']}.uniq
end

#connected_network_nameObject

Returns the network currently connected to, or nil if none.



186
187
188
189
190
191
192
193
# File 'lib/wifi-wand/models/mac_os_model.rb', line 186

def connected_network_name
  return nil unless wifi_on? # no need to try

  command_output = run_os_command("ipconfig getsummary #{wifi_interface} | grep ' SSID :'", false)
  return nil if command_output.empty?

  command_output.split('SSID :').last.strip
end

#detect_wifi_interfaceObject

Identifies the (first) wireless network hardware interface in the system, e.g. en0 or en1 This may not detect wifi ports with nonstandard names, such as USB wifi devices.



44
45
46
47
48
49
50
# File 'lib/wifi-wand/models/mac_os_model.rb', line 44

def detect_wifi_interface
  json_text = run_os_command('system_profiler -json SPNetworkDataType')
  net_data = JSON.parse(json_text)
  nets = net_data['SPNetworkDataType']
  wifi = nets.detect { |net| net['_name'] == 'Wi-Fi'}
  wifi['interface']
end

#detect_wifi_interface_using_networksetupObject

Identifies the (first) wireless network hardware interface in the system, e.g. en0 or en1 This may not detect wifi ports with nonstandard names, such as USB wifi devices.



19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/wifi-wand/models/mac_os_model.rb', line 19

def detect_wifi_interface_using_networksetup

  lines = run_os_command("networksetup -listallhardwareports").split("\n")
  # Produces something like this:
  # Hardware Port: Wi-Fi
  # Device: en0
  # Ethernet Address: ac:bc:32:b9:a9:9d
  #
  # Hardware Port: Bluetooth PAN
  # Device: en3
  # Ethernet Address: ac:bc:32:b9:a9:9e

  wifi_interface_line_num = (0...lines.size).detect do |index|
    /: Wi-Fi$/.match(lines[index])
  end

  if wifi_interface_line_num.nil?
    raise Error.new(%Q{Wifi interface (e.g. "en0") not found in output of: networksetup -listallhardwareports})
  else
    lines[wifi_interface_line_num + 1].split(': ').last
  end
end

#disconnectObject

Disconnects from the currently connected network. Does not turn off wifi.



197
198
199
200
201
202
# File 'lib/wifi-wand/models/mac_os_model.rb', line 197

def disconnect
  return nil unless wifi_on? # no need to try

  run_swift_command('WifiNetworkDisconecter')
  nil
end

#ensure_swift_and_corewlan_presentObject



331
332
333
334
335
336
337
338
# File 'lib/wifi-wand/models/mac_os_model.rb', line 331

def ensure_swift_and_corewlan_present
  unless swift_and_corewlan_present?
    raise RuntimeError, "      Swift and/or CoreWLAN are not present and are needed by this task.\n      This can be fixed by installing XCode.\n    MESSAGE\n  end\nend\n"

#ip_addressObject

Returns the IP address assigned to the wifi interface, or nil if none.



164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/wifi-wand/models/mac_os_model.rb', line 164

def ip_address
  return nil unless wifi_on? # no need to try
  begin
    run_os_command("ipconfig getifaddr #{wifi_interface}").chomp
  rescue OsCommandError => error
    if error.exitstatus == 1
      nil
    else
      raise
    end
  end
end

#is_wifi_interface?(interface) ⇒ Boolean

Returns whether or not the specified interface is a WiFi interface.

Returns:

  • (Boolean)


89
90
91
92
93
# File 'lib/wifi-wand/models/mac_os_model.rb', line 89

def is_wifi_interface?(interface)
  run_os_command("networksetup -listpreferredwirelessnetworks #{interface} 2>/dev/null")
  exit_status = $?.exitstatus
  exit_status != 10
end

#mac_addressObject

TODO: Add capability to change the MAC address using a command in the form of:

sudo ifconfig en0 ether aa:bb:cc:dd:ee:ff

However, the MAC address will be set to the real hardware address on restart. One way to implement this is to have an optional address argument, then this method returns the current address if none is provided, but sets to the specified address if it is.



211
212
213
# File 'lib/wifi-wand/models/mac_os_model.rb', line 211

def mac_address
  run_os_command("ifconfig #{wifi_interface} | awk '/ether/{print $2}'").chomp
end

#nameservers_using_networksetupObject



323
324
325
326
327
328
329
# File 'lib/wifi-wand/models/mac_os_model.rb', line 323

def nameservers_using_networksetup
  output = run_os_command("networksetup -getdnsservers Wi-Fi")
  if output == "There aren't any DNS Servers set on Wi-Fi.\n"
    output = ''
  end
  output.split("\n")
end

#nameservers_using_resolv_confObject

Though this is strictly not OS-agnostic, it will be used by most OS’s, and can be overridden by subclasses (e.g. Windows).

Returns:

  • array of nameserver IP addresses from /etc/resolv.conf, or nil if not found



305
306
307
308
309
310
311
# File 'lib/wifi-wand/models/mac_os_model.rb', line 305

def nameservers_using_resolv_conf
  begin
    File.readlines('/etc/resolv.conf').grep(/^nameserver /).map { |line| line.split.last }
  rescue Errno::ENOENT
    nil
  end
end

#nameservers_using_scutilObject



314
315
316
317
318
319
320
# File 'lib/wifi-wand/models/mac_os_model.rb', line 314

def nameservers_using_scutil
  output = run_os_command('scutil --dns')
  nameserver_lines_scoped_and_unscoped = output.split("\n").grep(/^\s*nameserver\[/)
  unique_nameserver_lines = nameserver_lines_scoped_and_unscoped.uniq # take the union
  nameservers = unique_nameserver_lines.map { |line| line.split(' : ').last.strip }
  nameservers
end

#open_application(application_name) ⇒ Object



276
277
278
# File 'lib/wifi-wand/models/mac_os_model.rb', line 276

def open_application(application_name)
  run_os_command('open -a ' + application_name)
end

#open_resource(resource_url) ⇒ Object



281
282
283
# File 'lib/wifi-wand/models/mac_os_model.rb', line 281

def open_resource(resource_url)
  run_os_command('open ' + resource_url)
end

#os_level_connect(network_name, password = nil) ⇒ Object



138
139
140
# File 'lib/wifi-wand/models/mac_os_model.rb', line 138

def os_level_connect(network_name, password = nil)
  os_level_connect_using_swift(network_name, password)
end

#os_level_connect_using_networksetup(network_name, password = nil) ⇒ Object

This method is called by BaseModel#connect to do the OS-specific connection logic.



123
124
125
126
127
128
129
# File 'lib/wifi-wand/models/mac_os_model.rb', line 123

def os_level_connect_using_networksetup(network_name, password = nil)
  command = "networksetup -setairportnetwork #{wifi_interface} #{Shellwords.shellescape(network_name)}"
  if password
    command << ' ' << Shellwords.shellescape(password)
  end
  run_os_command(command)
end

#os_level_connect_using_swift(network_name, password = nil) ⇒ Object



131
132
133
134
135
136
# File 'lib/wifi-wand/models/mac_os_model.rb', line 131

def os_level_connect_using_swift(network_name, password = nil)
  ensure_swift_and_corewlan_present
  args = [Shellwords.shellescape(network_name)]
  args << Shellwords.shellescape(password) if password
  run_swift_command('WifiNetworkConnecter', *args)
end

#os_level_preferred_network_password(preferred_network_name) ⇒ Object

@return:

If the network is in the preferred networks list
  If a password is associated w/this network, return the password
  If not, return nil
else
  raise an error


149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/wifi-wand/models/mac_os_model.rb', line 149

def os_level_preferred_network_password(preferred_network_name)
  command = %Q{security find-generic-password -D "AirPort network password" -a "#{preferred_network_name}" -w 2>&1}
  begin
    return run_os_command(command).chomp
  rescue OsCommandError => error
    if error.exitstatus == 44 # network has no password stored
      nil
    else
      raise
    end
  end
end

#preferred_networksObject

Returns data pertaining to “preferred” networks, many/most of which will probably not be available.



74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/wifi-wand/models/mac_os_model.rb', line 74

def preferred_networks
  lines = run_os_command("networksetup -listpreferredwirelessnetworks #{wifi_interface}").split("\n")
  # Produces something like this, unsorted, and with leading tabs:
  # Preferred networks on en0:
  #         LibraryWiFi
  #         @thePAD/Magma

  lines.delete_at(0)                         # remove title line
  lines.map! { |line| line.gsub("\t", '') }  # remove leading tabs
  lines.sort! { |s1, s2| s1.casecmp(s2) }    # sort alphabetically, case insensitively
  lines
end

#remove_preferred_network(network_name) ⇒ Object



178
179
180
181
182
# File 'lib/wifi-wand/models/mac_os_model.rb', line 178

def remove_preferred_network(network_name)
  network_name = network_name.to_s
  run_os_command("sudo networksetup -removepreferredwirelessnetwork " +
                     "#{wifi_interface} #{Shellwords.shellescape(network_name)}")
end

#run_swift_command(basename, *args) ⇒ Object



345
346
347
348
349
350
351
# File 'lib/wifi-wand/models/mac_os_model.rb', line 345

def run_swift_command(basename, *args)
  ensure_swift_and_corewlan_present
  swift_filespec = File.absolute_path(File.join(File.dirname(__FILE__), "../../../swift/#{basename}.swift"))
  argv = ['swift', swift_filespec] + args
  command = argv.compact.join(' ')
  run_os_command(command)
end

#set_nameservers(nameservers) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# File 'lib/wifi-wand/models/mac_os_model.rb', line 251

def set_nameservers(nameservers)
  arg = if nameservers == :clear
    'empty'
  else
    bad_addresses = nameservers.reject do |ns|
      begin
        IPAddr.new(ns).ipv4?
        true
      rescue => e
        puts e
        false
      end
    end

    unless bad_addresses.empty?
      raise Error.new("Bad IP addresses provided: #{bad_addresses.join(', ')}")
    end
    nameservers.join(' ')
  end # end assignment to arg variable

  run_os_command("networksetup -setdnsservers Wi-Fi #{arg}")
  nameservers
end

#swift_and_corewlan_present?Boolean

Returns:

  • (Boolean)


340
341
342
# File 'lib/wifi-wand/models/mac_os_model.rb', line 340

def swift_and_corewlan_present?
  system("swift -e 'import CoreWLAN' >/dev/null 2>&1")
end

#wifi_infoObject

Returns some useful wifi-related information.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
# File 'lib/wifi-wand/models/mac_os_model.rb', line 217

def wifi_info

  connected = begin
    connected_to_internet?
  rescue
    false
  end

  info = {
      'wifi_on'     => wifi_on?,
      'internet_on' => connected,
      'interface'   => wifi_interface,
      'network'     => connected_network_name,
      'ip_address'  => ip_address,
      'mac_address' => mac_address,
      'nameservers' => nameservers_using_scutil,
      'timestamp'   => Time.now,
  }

  if info['internet_on']
    begin
      info['public_ip'] = public_ip_address_info
    rescue => e
      puts "        \#{e.class} obtaining public IP address info, proceeding with everything else. Error message:\n        \#{e}\n\n      MESSAGE\n    end\n  end\n  info\nend\n"

#wifi_offObject

Turns wifi off.



113
114
115
116
117
118
119
# File 'lib/wifi-wand/models/mac_os_model.rb', line 113

def wifi_off
  return unless wifi_on?

  run_os_command("networksetup -setairportpower #{wifi_interface} off")

  wifi_on? ? Error.new(raise("Wifi could not be disabled.")) : nil
end

#wifi_onObject

Turns wifi on.



104
105
106
107
108
109
# File 'lib/wifi-wand/models/mac_os_model.rb', line 104

def wifi_on
  return if wifi_on?

  run_os_command("networksetup -setairportpower #{wifi_interface} on")
  wifi_on? ? nil : Error.new(raise("Wifi could not be enabled."))
end

#wifi_on?Boolean

Returns true if wifi is on, else false.

Returns:

  • (Boolean)


97
98
99
100
# File 'lib/wifi-wand/models/mac_os_model.rb', line 97

def wifi_on?
  output = run_os_command("networksetup -getairportpower #{wifi_interface}")
  output.chomp.match?(/\): On$/)
end