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



42
43
44
45
46
# File 'lib/wifi-wand/models/mac_os_model.rb', line 42

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

  run_swift_command('AvailableWifiNetworkLister').split("\n")
end

#connected_network_nameObject

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



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

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

  command_output = run_os_command("networksetup -getairportnetwork #{wifi_interface}")
  connected_prefix = 'Current Wi-Fi Network: '
  connected = Regexp.new(connected_prefix).match?(command_output)
  connected ? command_output.split(connected_prefix).last.chomp : nil
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.



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

  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.



162
163
164
165
166
167
# File 'lib/wifi-wand/models/mac_os_model.rb', line 162

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

  run_swift_command('WifiNetworkDisconecter')
  nil
end

#ensure_swift_and_corewlan_presentObject



296
297
298
299
300
301
302
303
# File 'lib/wifi-wand/models/mac_os_model.rb', line 296

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

#ip_addressObject

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



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/wifi-wand/models/mac_os_model.rb', line 129

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)


65
66
67
68
69
# File 'lib/wifi-wand/models/mac_os_model.rb', line 65

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.



176
177
178
# File 'lib/wifi-wand/models/mac_os_model.rb', line 176

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

#nameservers_using_networksetupObject



288
289
290
291
292
293
294
# File 'lib/wifi-wand/models/mac_os_model.rb', line 288

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



270
271
272
273
274
275
276
# File 'lib/wifi-wand/models/mac_os_model.rb', line 270

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



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

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



241
242
243
# File 'lib/wifi-wand/models/mac_os_model.rb', line 241

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

#open_resource(resource_url) ⇒ Object



246
247
248
# File 'lib/wifi-wand/models/mac_os_model.rb', line 246

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

#os_level_connect(network_name, password = nil) ⇒ Object

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



99
100
101
102
103
104
105
# File 'lib/wifi-wand/models/mac_os_model.rb', line 99

def os_level_connect(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_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


114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/wifi-wand/models/mac_os_model.rb', line 114

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.



50
51
52
53
54
55
56
57
58
59
60
61
# File 'lib/wifi-wand/models/mac_os_model.rb', line 50

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



143
144
145
146
147
# File 'lib/wifi-wand/models/mac_os_model.rb', line 143

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) ⇒ Object



309
310
311
312
313
314
315
316
# File 'lib/wifi-wand/models/mac_os_model.rb', line 309

def run_swift_command(basename)
  ensure_swift_and_corewlan_present
  swift_filespec = File.join(
    File.dirname(__FILE__), "../../../swift/#{basename}.swift"
  )
  command = "swift #{swift_filespec}"
  run_os_command(command)
end

#set_nameservers(nameservers) ⇒ Object



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/wifi-wand/models/mac_os_model.rb', line 216

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)


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

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

#wifi_infoObject

Returns some useful wifi-related information.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/wifi-wand/models/mac_os_model.rb', line 182

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 <<~MESSAGE
        #{e.class} obtaining public IP address info, proceeding with everything else. Error message:
        #{e}

      MESSAGE
    end
  end
  info
end

#wifi_offObject

Turns wifi off.



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

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.



80
81
82
83
84
85
# File 'lib/wifi-wand/models/mac_os_model.rb', line 80

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)


73
74
75
76
# File 'lib/wifi-wand/models/mac_os_model.rb', line 73

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