1-- Copyright (c) 2019 Marcel Kaiser <mk@freeshell.de> 2-- 3-- Redistribution and use in source and binary forms, with or without 4-- modification, are permitted provided that the following conditions 5-- are met: 6-- 1. Redistributions of source code must retain the above copyright 7-- notice, this list of conditions and the following disclaimer. 8-- 2. Redistributions in binary form must reproduce the above copyright 9-- notice, this list of conditions and the following disclaimer in the 10-- documentation and/or other materials provided with the distribution. 11-- 12-- THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 13-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 14-- IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 15-- ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 16-- FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 17-- DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 18-- OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 19-- HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 20-- LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 21-- OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 22-- SUCH DAMAGE. 23-- 24 25local netif = {} 26 27-- Constants for the match_netif_type() function 28netif.NETIF_TYPE_WLAN = 1 29netif.NETIF_TYPE_ETHER = 2 30 31netif.path_rc_conf = '/etc/rc.conf' 32netif.path_zoneinfo = '/var/db/zoneinfo' 33netif.path_zone_tab = '/usr/share/zoneinfo/zone.tab' 34 35-- Returns a pair, (true|false, NETIF_TYPE_WLAN|NETIF_TYPE_ETHER|nil), 36-- if the given driver name matches an ethernet or wireless device driver. 37function netif.match_netif_type(driver) 38 local m 39 local wlan_kmods = { 40 "if_zyd", "if_ath", "if_bwi", "if_bwn", 41 "if_ipw", "if_iwi", "if_iwm", "if_iwn", 42 "if_malo", "if_mwl", "if_otus", "if_ral", 43 "if_rsu", "if_rtwn_usb", "if_rtwn_pci", "if_rum", 44 "if_run", "if_uath", "if_upgt", "if_ural", 45 "if_urtw", "if_wi" 46 } 47 local ether_kmods = { 48 "if_ae", "if_age", "if_alc", "if_ale", 49 "if_aue", "if_axe", "if_bce", "if_bfe", 50 "if_bge", "if_bnxt", "if_bxe", "if_cas", 51 "if_cdce", "if_cue", "if_cxgb", "if_dc", 52 "if_de", "if_ed", "if_edsc", "if_em", 53 "if_et", "if_fwe", "if_fxp", "if_gem", 54 "if_hme", "if_ntb", "if_ipheth", "if_ix", 55 "if_ixl", "if_jme", "if_kue", "if_le", 56 "if_lge", "if_mos", "if_msk", "if_mxge", 57 "if_my", "if_nf10bmac", "if_nfe", "if_nge", 58 "if_pcn", "if_ptnet", "if_qlnxe", "if_qlxgb", 59 "if_qlxgbe", "if_qlxge", "if_re", "if_rl", 60 "if_rue", "if_sf", "if_sfxge", "if_sge", 61 "if_sis", "if_sk", "if_smsc", "if_sn", 62 "if_ste", "if_stge", "if_tap", "if_ti", 63 "if_tl", "if_tx", "if_txp", "if_udav", 64 "if_ure", "if_urndis", "if_vge", "if_vr", 65 "if_vte", "if_vtnet", "if_wb", "if_xe", 66 "if_xl" 67 } 68 for _, m in pairs(wlan_kmods) do 69 if driver == m then 70 return true, netif.NETIF_TYPE_WLAN 71 end 72 end 73 for _, m in pairs(ether_kmods) do 74 if driver == m then 75 return true, netif.NETIF_TYPE_ETHER 76 end 77 end 78 return false, nil 79end 80 81-- Takes the name of a kernel module, and removes the "if_" prefix and 82-- the "_pci" or "_usb" suffix. Returns the new string. 83function netif.kmod_to_dev(kmod) 84 local dev = kmod 85 if string.match(kmod, ".*_pci$") or string.match(kmod, ".*_usb$") then 86 dev = string.sub(kmod, 1, string.len(kmod) - 4) 87 end 88 if string.match(dev, "^if_.*") then 89 dev = string.sub(dev, 4) 90 end 91 return dev 92end 93 94-- Takes a device name without unit number (e.g. ath, rtwn), and a list of 95-- network devices. Returns the name of the interface if found, nil otherwise. 96function netif.find_netif(dev, netifs) 97 local d 98 for _, d in pairs(netifs) do 99 if d == dev or string.match(d, dev .. "[0-9]+") then 100 return d 101 end 102 end 103 return nil 104end 105 106-- Returns the the unit (X) of a "wlanX" device name to a given 107-- parent device, or nil 108function wlan_unit_from_parent(pdev) 109 local l 110 local proc, e = io.popen("sysctl net.wlan") 111 if proc == nil then 112 io.stderr:write(e) 113 return nil 114 end 115 for l in proc:lines() do 116 if string.match(l, "%%parent") and string.match(l, pdev) then 117 return tonumber(string.match(l, "net.wlan.([0-9]+).")) 118 end 119 end 120 return nil 121end 122 123-- We define a wlan device object which has the following fields: 124-- parent ::= parent device name (e.g., "ath0", "rtwn0") 125-- child ::= child device unit number ("wlan0" -> unit = 0). If there is no 126-- child device yet, this field is nil. 127-- 128-- This function returns a list of available wlan device objects, or an empty 129-- list if there are none. 130function netif.get_wlan_devs() 131 local i, l, parent 132 local pdevs = {} 133 local wlans = {} 134 local proc, e = io.popen("sysctl -n net.wlan.devices") 135 if proc == nil then 136 io.stderr:write(e) 137 return nil 138 end 139 i = 1 140 for l in proc:lines() do 141 for w in string.gmatch(l, "%w+") do 142 pdevs[i] = w 143 i = i + 1 144 end 145 end 146 proc:close() 147 i = 1 148 for _, parent in pairs(pdevs) do 149 child = wlan_unit_from_parent(parent) 150 wlans[i] = {} 151 wlans[i]["parent"] = parent 152 wlans[i]["child"] = child 153 i = i + 1 154 end 155 return wlans 156end 157 158-- Returns the wlan device object from the given list matching the given 159-- parent device pattern, or nil if there was no match. 160function netif.find_wlan(parent, wlans) 161 local w 162 for _, w in pairs(wlans) do 163 if string.match(w.parent, parent .. "[0-9]+") then 164 return w 165 end 166 end 167 return nil 168end 169 170function netif.get_ifconfig_if_info(ifname) 171 local info = {} 172 local proc, e = io.popen("ifconfig " .. ifname) 173 if proc == nil then 174 io.stderr:write(e) 175 return nil 176 end 177 local l 178 for l in proc:lines() do 179 table.insert(info, l) 180 end 181 proc:close() 182 return info 183end 184 185-- Returns the network interface's media type or nil 186function netif.media_type(ifname) 187 local l 188 local info = netif.get_ifconfig_if_info(ifname) 189 if info == nil then 190 return nil 191 end 192 for _, l in pairs(info) do 193 local type = string.match(l, "^%s+media:%s([%g,%s]+)$") 194 if type then 195 if string.match(type, "%s[wW]ireless%s") or 196 string.match(type, "%s802.11%s") then 197 return netif.NETIF_TYPE_WLAN 198 elseif string.match(type, "^[Ee]thernet") then 199 return netif.NETIF_TYPE_ETHER 200 else 201 return nil 202 end 203 end 204 end 205 return nil 206end 207 208function netif.get_ifconfig_iflist() 209 local l 210 local iflist = {} 211 local proc, e = io.popen("ifconfig -l") 212 if proc == nil then 213 io.stderr:write(e) 214 return nil 215 end 216 for l in proc:lines() do 217 for w in string.gmatch(l, "%w+") do 218 table.insert(iflist, w) 219 end 220 end 221 proc:close() 222 return iflist 223end 224 225-- Returns the list of network interfaces from the output of "ifconfig" as 226-- array. 227function netif.get_netifs() 228 local iflist = {} 229 local all_ifs = netif.get_ifconfig_iflist() 230 if all_ifs == nil then 231 return nil 232 end 233 for _, i in pairs(all_ifs) do 234 type = netif.media_type(i) 235 if type ~= nil then 236 table.insert(iflist, i) 237 end 238 end 239 return iflist 240end 241 242-- Returns the given network interface's status 243function netif.link_status(ifname) 244 local i, status 245 local info = netif.get_ifconfig_if_info(ifname) 246 if info == nil then 247 return nil 248 end 249 for _, i in pairs(info) do 250 if string.match(i, "^[ \t]*status: (%w+)") then 251 status = string.gsub(i, "^[ \t]*status: ([%w, ]+)$", "%1") 252 if status ~= nil then 253 return status 254 end 255 end 256 end 257 return nil 258end 259 260-- Returns "true" if the given network interface was configured 261-- via /etc/rc.conf 262function netif.in_rc_conf(ifname) 263 local l 264 local f, e = io.open(netif.path_rc_conf) 265 if f == nil then 266 io.stderr:write(e) 267 return nil 268 end 269 for l in f:lines() do 270 if string.match(l, "^[ \t]*ifconfig_" .. ifname) then 271 f:close() 272 return true 273 end 274 if string.match(l, "^[ \t]*ifconfig_" .. ifname .. "_ipv6") then 275 f:close() 276 return true 277 end 278 end 279 f:close() 280 return false 281end 282 283-- Returns the inet (v4) address of the given interface from the dhclient 284-- lease file 285function netif.inet_from_lease(ifname) 286 local l, inet, _inet 287 local f, e, errno = io.open("/var/db/dhclient.leases." .. ifname) 288 if f == nil then 289 if errno == 2 then -- ENOENT 290 return nil 291 end 292 io.stderr:write(e) 293 return nil 294 end 295 inet = nil 296 -- Get IP address of the last lease record 297 for l in f:lines() do 298 _inet = string.match(l, "^%s*fixed.address%s*(.+);$") 299 if _inet ~= nil then 300 inet = _inet 301 end 302 end 303 f:close() 304 return inet 305end 306 307-- Get the inet v4 and v6 addresses of the given interface 308function netif.get_inet_addr(ifname) 309 local i, inet4, inet6 310 local info = netif.get_ifconfig_if_info(ifname) 311 if info == nil then 312 return nil, nil 313 end 314 for _, i in pairs(info) do 315 if inet4 == nil then 316 inet4 = string.match(i, "^%s+inet%s+(%g+)") 317 end 318 if inet6 == nil then 319 inet6 = string.match(i, "^%s+inet6%s+([%x:]+)") 320 end 321 if inet4 ~= nil and inet6 ~= nil then 322 break 323 end 324 end 325 return inet4, inet6 326end 327 328-- Returns a list of wlan device objects configured via /etc/rc.conf 329function netif.wlans_from_rc_conf() 330 local i, l 331 local wlans = {} 332 local f, e = io.open(netif.path_rc_conf) 333 if f == nil then 334 io.stderr:write(e) 335 return nil 336 end 337 i = 1 338 for l in f:lines() do 339 local p, c = string.match(l, "^[ \t]*wlans_(%w+)=\"?(%w+)\"?") 340 if p ~= nil and c ~= nil then 341 wlans[i] = {} 342 wlans[i].parent = p 343 wlans[i].child = tonumber(string.match(c, "wlan(%d+)")) 344 i = i + 1 345 end 346 end 347 f:close() 348 return wlans 349end 350 351-- Returns "true" if the given wlan device object was configured via 352-- /etc/rc.conf, else "false". 353function netif.wlan_rc_configured(wlan) 354 local w 355 local wlans = netif.wlans_from_rc_conf() 356 for _, w in pairs(wlans) do 357 if w.parent == wlan.parent and w.child == wlan.child then 358 return true 359 end 360 end 361 return false 362end 363 364-- Returns the country code of the region defined in /var/db/zoneinfo, 365-- or nil if not found 366function netif.get_wlan_region() 367 local l, zone, code 368 local f, e = io.open(netif.path_zoneinfo) 369 if f == nil then 370 io.stderr:write(e) 371 return nil 372 end 373 for l in f:lines() do 374 if string.match(l, "/") then 375 zone = l 376 break 377 end 378 end 379 f:close() 380 if not zone then 381 return nil 382 end 383 f, e = io.open(netif.path_zone_tab) 384 if f == nil then 385 io.stderr:write(e) 386 return nil 387 end 388 for l in f:lines() do 389 code = string.match(l, "^(%u+)%s+[0-9+-]+%s+" .. zone) 390 if code then 391 break 392 end 393 end 394 f:close() 395 return code 396end 397 398-- Sleeps n seconds 399function netif.sleep(n) 400 os.execute("sleep " .. tonumber(n)) 401end 402 403-- Takes a driver name (e.g. if_ath) and waits for not more than "timeout" 404-- seconds for the parent device matching the driver to appear in 405-- net.wlan.devices. If found, the corresponding wlan device object is 406-- returned, else nil. 407function netif.wait_for_new_wlan(driver, timeout) 408 local devname = netif.kmod_to_dev(driver) 409 local tries = 1 410 while true do 411 local wlans = netif.get_wlan_devs() 412 local w = netif.find_wlan(devname, wlans) 413 if w == nil then 414 if tries >= timeout then 415 return nil 416 end 417 netif.sleep(1) 418 else 419 return w 420 end 421 tries = tries + 1 422 end 423end 424 425local function get_wlan_regdomain_args() 426 local country = netif.get_wlan_region() 427 if country == nil then 428 return "" 429 end 430 return "down country " .. country 431end 432 433function netif.restart_netif(ifname) 434 return os.execute("service netif restart " .. ifname) 435end 436 437function netif.run_sysrc(var) 438 return os.execute("sysrc " .. var) 439end 440 441function netif.set_rc_conf_var(var, val) 442 local rc_var = string.format('%s="%s"', var, val) 443 return netif.run_sysrc(rc_var) 444end 445 446function netif.create_wlan_child_dev(parent, child_unit) 447 local cmd = string.format("ifconfig wlan%d create wlandev %s", 448 child_unit, parent) 449 return os.execute(cmd) 450end 451 452function netif.add_wlan_to_rc_conf(wlan) 453 local args 454 455 netif.set_rc_conf_var("wlans_" .. wlan.parent, "wlan" .. wlan.child) 456 if wlan_set_country then 457 args = get_wlan_regdomain_args() 458 end 459 if wlan_create_args ~= nil then 460 if args == nil then 461 args = wlan_create_args 462 463 else 464 args = args .. " " .. wlan_create_args 465 end 466 end 467 if args ~= nil then 468 netif.set_rc_conf_var("create_args_wlan" .. wlan.child, args) 469 end 470 netif.set_rc_conf_var("ifconfig_wlan" .. wlan.child, wlan_ifconfig_args) 471 if enable_ipv6 then 472 netif.set_rc_conf_var("ifconfig_wlan" .. wlan.child .. "_ipv6", 473 wlan_ifconfig_ipv6_args) 474 end 475end 476 477-- Creates and configures a new wlan child device (wlanX) for each wlan 478-- device object which doesn't have a child device yet. 479function netif.create_wlan_devs() 480 local w, max_unit 481 local wlans = netif.get_wlan_devs() 482 483 if wlan_ifconfig_args == nil then 484 wlan_ifconfig_args = "up scan WPA DHCP" 485 end 486 if wlan_ifconfig_ipv6_args == nil then 487 wlan_ifconfig_ipv6_args = "inet6 accept_rtadv" 488 end 489 -- Calculate the next available unit number for the child device 490 max_unit = -1 491 for _, w in pairs(wlans) do 492 if w.child ~= nil then 493 if w.child > max_unit then 494 max_unit = w.child 495 end 496 end 497 end 498 -- Create a child device for each parent device which doesn't have 499 -- a child ("wlanX"), and wasn't configured via /etc/rc.conf with 500 -- 'wlans_parent="wlan<max_unit>"' 501 for _, w in pairs(wlans) do 502 if ignore_netifs == nil or 503 netif.find_netif(w.parent, ignore_netifs) == nil then 504 if not netif.wlan_rc_configured(w) or w.child == nil then 505 if w.child == nil then 506 max_unit = max_unit + 1 507 w.child = max_unit 508 netif.create_wlan_child_dev(w.parent, max_unit) 509 end 510 netif.add_wlan_to_rc_conf(w) 511 local child = "wlan" .. w.child 512 local status = netif.link_status(child) 513 if status == nil or status ~= "associated" then 514 netif.restart_netif(child) 515 end 516 end 517 end 518 end 519end 520 521-- Takes a driver name (e.g. if_alc) and waits for not more than "timeout" 522-- seconds for the device matching the driver to appear in the list of 523-- network interfaces. If found, the interface name is returned, else nil. 524function netif.wait_for_new_ether(driver, timeout) 525 local devname = netif.kmod_to_dev(driver) 526 527 local tries = 1 528 while true do 529 local iflist = netif.get_netifs() 530 local ifname = netif.find_netif(devname, iflist) 531 if ifname == nil then 532 if tries >= timeout then 533 return nil 534 end 535 netif.sleep(1) 536 else 537 return ifname 538 end 539 tries = tries + 1 540 end 541end 542 543-- Starts DHCP on each ethernet device. 544function netif.setup_ether_devs() 545 local i 546 local iflist = netif.get_netifs() 547 if ether_ifconfig_args == nil then 548 ether_ifconfig_args = "DHCP" 549 end 550 if ether_ifconfig_ipv6_args == nil then 551 ether_ifconfig_ipv6_args = "inet6 accept_rtadv" 552 end 553 for _, i in pairs(iflist) do 554 if ignore_netifs == nil or 555 netif.find_netif(i, ignore_netifs) == nil then 556 if not string.match(i, "wlan") then 557 if not netif.in_rc_conf(i) then 558 netif.set_rc_conf_var('ifconfig_' .. i, ether_ifconfig_args) 559 if enable_ipv6 then 560 netif.set_rc_conf_var('ifconfig_' .. i .. "_ipv6", 561 ether_ifconfig_ipv6_args) 562 end 563 end 564 local inet4, inet6 = netif.get_inet_addr(i) 565 if inet6 == nil and inet4 == nil then 566 netif.restart_netif(i) 567 end 568 end 569 end 570 end 571end 572 573-- Configures and starts all network interfaces based on the given driver/kmod 574-- name. 575function netif.config_netif(kmod) 576 if netif_wait_max == nil then 577 netif_wait_max = 1 578 end 579 local is_netif, iftype = netif.match_netif_type(kmod) 580 if is_netif and iftype == netif.NETIF_TYPE_WLAN then 581 if netif.wait_for_new_wlan(kmod, netif_wait_max) ~= nil then 582 netif.create_wlan_devs() 583 end 584 elseif is_netif and iftype == netif.NETIF_TYPE_ETHER then 585 if netif.wait_for_new_ether(kmod, netif_wait_max) ~= nil then 586 netif.setup_ether_devs() 587 end 588 end 589end 590 591return netif 592