Merge pull request #1688 from dibdot/adblock
authorDirk Brenken <dev@brenken.org>
Fri, 16 Mar 2018 12:01:36 +0000 (13:01 +0100)
committerGitHub <noreply@github.com>
Fri, 16 Mar 2018 12:01:36 +0000 (13:01 +0100)
luci-app-adblock: remaining fixes

61 files changed:
applications/luci-app-adblock/po/it/adblock.po
applications/luci-app-adblock/po/ja/adblock.po
applications/luci-app-adblock/po/pt-br/adblock.po
applications/luci-app-adblock/po/ru/adblock.po
applications/luci-app-adblock/po/sv/adblock.po
applications/luci-app-adblock/po/templates/adblock.pot
applications/luci-app-adblock/po/zh-cn/adblock.po
applications/luci-app-adblock/po/zh-tw/adblock.po
applications/luci-app-attendedsysupgrade/root/etc/uci-defaults/40_luci-attendedsysupgrade
applications/luci-app-attendedsysupgrade/root/www/luci-static/resources/attendedsysupgrade.js
applications/luci-app-firewall/luasrc/view/firewall/cbi_addrule.htm
applications/luci-app-firewall/luasrc/view/firewall/cbi_addsnat.htm
applications/luci-app-mwan3/luasrc/model/cbi/mwan/interface.lua
applications/luci-app-mwan3/luasrc/view/mwan/overview_status_interface.htm
applications/luci-app-mwan3/luasrc/view/mwan/status_interface.htm
applications/luci-app-ocserv/luasrc/model/cbi/ocserv/main.lua
applications/luci-app-olsr/luasrc/controller/olsr.lua
applications/luci-app-splash/luasrc/controller/splash/splash.lua
applications/luci-app-splash/luasrc/view/admin_status/splash.htm
applications/luci-app-statistics/luasrc/statistics/rrdtool/definitions/apcups.lua
applications/luci-app-travelmate/Makefile
applications/luci-app-travelmate/luasrc/controller/travelmate.lua
applications/luci-app-travelmate/luasrc/model/cbi/travelmate/overview_tab.lua
applications/luci-app-travelmate/luasrc/model/cbi/travelmate/wifi_add.lua
applications/luci-app-travelmate/luasrc/view/travelmate/ap_qr.htm [new file with mode: 0644]
applications/luci-app-travelmate/luasrc/view/travelmate/stations.htm
applications/luci-app-travelmate/po/ja/travelmate.po
applications/luci-app-travelmate/po/pt-br/travelmate.po
applications/luci-app-travelmate/po/ru/travelmate.po
applications/luci-app-travelmate/po/templates/travelmate.pot
applications/luci-app-wifischedule/luasrc/model/cbi/wifischedule/wifi_schedule.lua
applications/luci-app-wol/luasrc/model/cbi/wol.lua
contrib/package/community-profiles/files/etc/config/profile_potsdam
documentation/api/modules/luci.http.protocol.html
documentation/api/modules/luci.ip.cidr.html
documentation/api/modules/luci.ip.html
documentation/api/modules/luci.util.html
documentation/api/modules/nixio.README.html
documentation/api/modules/nixio.UnifiedIO.html
libs/luci-lib-ip/src/ip.c
libs/luci-lib-ip/src/ip.luadoc
libs/luci-lib-nixio/docsrc/README.lua
libs/luci-lib-nixio/docsrc/nixio.Socket.lua
libs/luci-lib-nixio/docsrc/nixio.UnifiedIO.lua
libs/luci-lib-nixio/docsrc/nixio.fs.lua
libs/luci-lib-nixio/docsrc/nixio.lua
modules/luci-base/luasrc/cbi/datatypes.lua
modules/luci-base/luasrc/http/protocol.luadoc
modules/luci-base/luasrc/model/network.lua
modules/luci-base/luasrc/sys.lua
modules/luci-base/luasrc/tools/status.lua
modules/luci-base/luasrc/util.luadoc
modules/luci-base/luasrc/view/cbi/browser.htm
modules/luci-base/luasrc/view/cbi/firewall_zonelist.htm
modules/luci-mod-admin-full/luasrc/controller/admin/network.lua
modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/dhcp.lua
modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/hosts.lua
modules/luci-mod-admin-full/luasrc/model/cbi/admin_network/ifaces.lua
modules/luci-mod-admin-full/luasrc/view/admin_status/routes.htm
modules/luci-mod-admin-full/luasrc/view/admin_system/flashops.htm
modules/luci-mod-admin-mini/luasrc/model/cbi/mini/dhcp.lua

index bdaa2f2..88adcc8 100644 (file)
@@ -275,7 +275,7 @@ msgid ""
 msgstr ""
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 
index d3a0a5c..60c5a9a 100644 (file)
@@ -301,7 +301,7 @@ msgstr ""
 "処理エラーまたはドメイン カウントが0以下の場合、メールを送信します。<br />"
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 "ダウンロードの制御とリストの処理を同時並行的に行うダウンロード キューのサイズ"
index f264ed6..7eb6e3e 100644 (file)
@@ -267,7 +267,7 @@ msgid ""
 msgstr ""
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 
index 0898213..6e80e80 100644 (file)
@@ -303,7 +303,7 @@ msgstr ""
 "&le; 0.<br />"
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 "Значение очереди загрузки для выполнения параллельных загрузок (по умолчанию "
index 5a86d2c..d875a69 100644 (file)
@@ -256,7 +256,7 @@ msgid ""
 msgstr ""
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 
index 285bb1d..5a93f8f 100644 (file)
@@ -248,7 +248,7 @@ msgid ""
 msgstr ""
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 
index 3b8a35b..1d7dc2b 100644 (file)
@@ -266,7 +266,7 @@ msgid ""
 msgstr ""
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 
index 0d1536b..da00842 100644 (file)
@@ -266,7 +266,7 @@ msgid ""
 msgstr ""
 
 msgid ""
-"Size of the download queue to handle downloads & list processing in parallel "
+"Size of the download queue to handle downloads &amp; list processing in parallel "
 "(default '4').<br />"
 msgstr ""
 
index c2fe81d..604074a 100644 (file)
@@ -128,7 +128,7 @@ function ubus_call(command, argument, params, variable) {
                                        }
                                }
                        } else {
-                               error_box("<b>Ubus call faild:</b></br>Request: " + request_json + "</br>Response: " + JSON.stringify(response))
+                               error_box("<b>Ubus call faild:</b><br />Request: " + request_json + "<br />Response: " + JSON.stringify(response))
                        }
                        ubus_closed++;
                }
@@ -180,7 +180,7 @@ function upgrade_check_callback(request_text) {
        if(request_json.upgrades != undefined) {
                info_output += "<h3>Package upgrades available</h3>"
                for (upgrade in request_json.upgrades) {
-                       info_output += "<b>" + upgrade + "</b>: " + request_json.upgrades[upgrade][1] + " to " + request_json.upgrades[upgrade][0] + "</br>"
+                       info_output += "<b>" + upgrade + "</b>: " + request_json.upgrades[upgrade][1] + " to " + request_json.upgrades[upgrade][0] + "<br />"
                }
        }
        data.packages = request_json.packages
@@ -231,9 +231,9 @@ function upgrade_request_callback(request) {
        var filename_split = data.sysupgrade_url.split("/")
        data.filename = filename_split[filename_split.length - 1]
 
-       info_output = "Firmware created</br><b>" + data.filename + "</b>"
+       info_output = 'Firmware created: <a href="' + data.sysupgrade_url + '"><b>' + data.filename + '</b></a>'
        if(data.advanced_mode == 1) {
-               info_output += '</br><a target="_blank" href="' + data.sysupgrade_url + '.log">Build log</a>'
+               info_output += '<br /><a target="_blank" href="' + data.sysupgrade_url + '.log">Build log</a>'
        }
        info_box(info_output);
 
@@ -373,12 +373,12 @@ function server_request(request_dict, path, callback) {
                error_box("No firmware created due to image size. Try again with less packages selected.")
 
                } else if (request.status === 422) {
-                       error_box("Unknown package in request")
-
+                       var package_missing = response.getResponseHeader("X-Unknown-Package");
+                       error_box("Unknown package in request: <b>" + package_missing + "</b>")
                } else if (request.status === 500) {
                        request_json = JSON.parse(request_text)
 
-                       error_box_content = "<b>Internal server error</b></br>"
+                       error_box_content = "<b>Internal server error</b><br />"
                        error_box_content += request_json.error
                        if(request_json.log != undefined) {
                                data.log_url = request_json.log
index 58121c4..b06fac3 100644 (file)
@@ -2,6 +2,7 @@
        local fw = require "luci.model.firewall".init()
        local wz = fw:get_zone("wan")
        local lz = fw:get_zone("lan")
+       local zones = fw:get_zones()
 %>
 
 <div class="cbi-section-create cbi-tblsection-create">
@@ -38,7 +39,7 @@
                        </tr>
                </table>
        <% end %>
-       <% if wz and lz then %>
+       <% if #zones > 1 then %>
                <table class="cbi-section-table" style="margin-left:5px">
                        <tr class="cbi-section-table-titles">
                                <th class="cbi-section-table-cell left" colspan="6"><br /><%:New forward rule%>:</th>
                                <td class="cbi-section-table-cell" style="width:110px">
                                        <select class="cbi-input-text" id="_newfwd.src" name="_newfwd.src">
                                                <% local k, v; for k, v in ipairs(fw:get_zones()) do -%>
-                                                       <option<%=ifattr(v:name() == "lan", "selected", "selected")%> value="<%=v:name()%>"><%=v:name()%></option>
+                                                       <option<%=ifattr(v:name() == "wan", "selected", "selected")%> value="<%=v:name()%>"><%=v:name()%></option>
                                                <%- end %>
                                        </select>
                                </td>
                                <td class="cbi-section-table-cell" style="width:110px">
                                        <select class="cbi-input-text" id="_newfwd.dest" name="_newfwd.dest">
                                                <% local k, v; for k, v in ipairs(fw:get_zones()) do -%>
-                                                       <option<%=ifattr(v:name() == "wan", "selected", "selected")%> value="<%=v:name()%>"><%=v:name()%></option>
+                                                       <option<%=ifattr(v:name() == "lan", "selected", "selected")%> value="<%=v:name()%>"><%=v:name()%></option>
                                                <%- end %>
                                        </select>
                                </td>
index ce27511..0a5913f 100644 (file)
@@ -1,8 +1,7 @@
 <%
        local fw = require "luci.model.firewall".init()
        local nw = require "luci.model.network".init()
-       local wz = fw:get_zone("wan")
-       local lz = fw:get_zone("lan")
+       local zones = fw:get_zones()
 
        local keys, vals, a, k, v = {}, {}
        for k, v in ipairs(nw:get_interfaces()) do
@@ -14,7 +13,7 @@
 %>
 
 <div class="cbi-section-create cbi-tblsection-create">
-       <% if wz and lz then %>
+       <% if #zones > 1 then %>
                <br />
                <table class="cbi-section-table" style="width:700px; margin-left:5px">
                        <tr class="cbi-section-table-titles">
index 920dc6a..4b1a070 100644 (file)
@@ -93,7 +93,7 @@ function configCheck()
 
                        local trackingNumber = uci:get("mwan3", iface, "track_ip")
                        overview[iface]["tracking"] = 0
-                       if #trackingNumber > 0 then
+                       if trackingNumber and #trackingNumber > 0 then
                                overview[iface]["tracking"] = #trackingNumber
                                overview[iface]["reliability"] = false
                                local reliabilityNumber = tonumber(uci:get("mwan3", iface, "reliability"))
index 6dc3d12..86b5ac6 100644 (file)
@@ -15,30 +15,27 @@ XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_
                                for ( var iface in status.interfaces)
                                {
                                        var state = '';
-                                       var css = '';
                                        switch (status.interfaces[iface].status)
                                        {
                                                case 'online':
                                                        state = '<%:Online (tracking active)%>';
-                                                       css = 'wanon';
                                                        break;
                                                case 'notMonitored':
                                                        state = '<%:Online (tracking off)%>';
-                                                       css = 'wanon';
                                                        break;
                                                case 'offline':
                                                        state = '<%:Offline%>';
-                                                       css = 'wanoff';
                                                        break;
                                                default:
                                                        state = '<%:Disabled%>';
-                                                       css = 'wanoff';
                                                        break;
                                        }
                                        statusview += String.format(
-                                               '<span class="%s"><strong>%s</strong><br />%s</span>',
-                                               css,
-                                               iface,
+                                               '<div><strong>Interface: </strong>%s</div>',
+                                               iface
+                                       );
+                                       statusview += String.format(
+                                               '<div><strong>Status: </strong>%s</div></br></br>',
                                                state
                                        );
                                }
@@ -53,36 +50,8 @@ XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_
 //]]></script>
 
 <fieldset id="interface_field" class="cbi-section">
-       <legend><%:MWAN Interface Live Status%></legend>
-       <div id="mwan_status_text"><img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /><%:Collecting data...%></div>
+       <legend><%:MWAN Interfaces%></legend>
+       <div id="mwan_status_text">
+               <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align:middle" /><%:Collecting data...%>
+       </div>
 </fieldset>
-
-<style type="text/css">
-  .container { /* container for entire page. fixes bootstrap theme's ridiculously small page width */
-       max-width: 1044px;
-  }
-  #mwan_status_text {
-       display: table;
-       font-size: 14px;
-       margin: auto;
-       max-width: 1044px;
-       min-width: 246px;
-       width: 100%;
-  }
-  .wanon {
-       background-color: rgb(144, 240, 144);
-  }
-  .wanoff {
-       background-color: rgb(240, 144, 144);
-  }
-  .wanon, .wanoff {
-       border-radius: 60px;
-       box-shadow: 0px 2px 5px -3px;
-       float: left;
-       margin: 8px 3px 0px 3px;
-       min-height: 30px;
-       min-width: 235px;
-       padding: 5px 10px 8px 10px;
-       text-align: center;
-  }
-</style>
index 21e59a8..cb47696 100644 (file)
 </ul>
 
 <script type="text/javascript" src="<%=resource%>/cbi.js"></script>
-<script type="text/javascript">//<![CDATA[
-       XHR.poll(5, '<%=luci.dispatcher.build_url("admin", "status", "mwan", "interface_status")%>', null,
-               function(x, status)
-               {
-                       var legend = document.getElementById('diag-rc-legend');
-                       var statusDiv = document.getElementById('diag-rc-output');
-                       legend.style.display = 'none';
-                       if (status.interfaces)
-                       {
-                               var statusview = '';
-                               for ( var iface in status.interfaces)
-                               {
-                                       var state = '';
-                                       var css = '';
-                                       switch (status.interfaces[iface].status)
-                                       {
-                                               case 'online':
-                                                       state = '<%:Online (tracking active)%>';
-                                                       css = 'wanon';
-                                                       break;
-                                               case 'notMonitored':
-                                                       state = '<%:Online (tracking off)%>';
-                                                       css = 'wanon';
-                                                       break;
-                                               case 'offline':
-                                                       state = '<%:Offline%>';
-                                                       css = 'wanoff';
-                                                       break;
-                                               default:
-                                                       state = '<%:Disabled%>';
-                                                       css = 'wanoff';
-                                                       break;
-                                       }
-                                       statusview += String.format(
-                                               '<span class="%s"><strong>%s</strong><br />%s</span>',
-                                               css,
-                                               iface,
-                                               state
-                                       );
-                               }
-                               statusDiv.innerHTML = statusview;
-                       }
-                       else
-                       {
-                               statusDiv.innerHTML = '<strong><%:No MWAN interfaces found%></strong>';
-                       }
-               }
-       );
-//]]></script>
 
-<div class="cbi-map">
-       <h2 name="content"><%:MWAN Status - Interface%></h2>
-       <%if not require("luci.sys").init.enabled("mwan3") then%>
-       <div><strong><%:INFO: MWAN not running%></strong></div>
-       <%end%>
-       <fieldset class="cbi-section">
-               <legend id="diag-rc-legend"><%:Collecting data...%></legend>
-               <span id="diag-rc-output">
-                       <img src="<%=resource%>/icons/loading.gif" alt="<%:Loading%>" style="vertical-align: middle;" />
-               </span>
-       </fieldset>
-</div>
-
-<style type="text/css">
-  #mwan_status_text {
-       display: table;
-       font-size: 14px;
-       margin: auto;
-       max-width: 1044px;
-       min-width: 246px;
-       width: 100%;
-  }
-  .wanon {
-       background-color: rgb(144, 240, 144);
-  }
-  .wanoff {
-       background-color: rgb(240, 144, 144);
-  }
-  .wanon, .wanoff {
-       border-radius: 60px;
-       box-shadow: 0px 2px 5px -3px;
-       float: left;
-       margin: 8px 3px 0px 3px;
-       min-height: 30px;
-       min-width: 235px;
-       padding: 5px 10px 8px 10px;
-       text-align: center;
-  }
-  #mwan_statuslog_text {
-       padding: 20px;
-       text-align: left;
-  }
-</style>
+<%+mwan/overview_status_interface%>
 
 <%+footer%>
index 74edaf4..396dedd 100644 (file)
@@ -86,7 +86,7 @@ pip.default = "1"
 
 local compr = s:taboption("general", Flag, "compression", translate("Enable compression"),
        translate("Enable compression"))
-compr.default = "1"
+compr.default = "0"
 
 local udp = s:taboption("general", Flag, "udp", translate("Enable UDP"),
        translate("Enable UDP channel support; this must be enabled unless you know what you are doing"))
index 0564bd4..229f3d6 100644 (file)
@@ -101,41 +101,19 @@ end
 
 
 local function local_mac_lookup(ipaddr)
-       local _, ifa, dev
-
-       ipaddr = tostring(ipaddr)
-
-       if not ifaddr_table then
-               ifaddr_table = nixio.getifaddrs()
-       end
-
-       -- ipaddr -> ifname
-       for _, ifa in ipairs(ifaddr_table) do
-               if ifa.addr == ipaddr then
-                       dev = ifa.name
-                       break
-               end
-       end
-
-       -- ifname -> macaddr
-       for _, ifa in ipairs(ifaddr_table) do
-               if ifa.name == dev and ifa.family == "packet" then
-                       return ifa.addr
-               end
+       local _, rt
+       for _, rt in ipairs(luci.ip.routes({ type = 1, src = ipaddr })) do
+               local link = rt.dev and luci.ip.link(rt.dev)
+               local mac = link and luci.ip.checkmac(link.mac)
+               if mac then return mac end
        end
 end
 
 local function remote_mac_lookup(ipaddr)
        local _, n
-
-       if not neigh_table then
-               neigh_table = luci.ip.neighbors()
-       end
-
-       for _, n in ipairs(neigh_table) do
-               if n.mac and n.dest and n.dest:equal(ipaddr) then
-                       return n.mac
-               end
+       for _, n in ipairs(luci.ip.neighbors({ dest = ipaddr })) do
+               local mac = luci.ip.checkmac(n.mac)
+               if mac then return mac end
        end
 end
 
@@ -201,9 +179,9 @@ function action_neigh(json)
 
                for _, val in ipairs(assoclist) do
                        if val.network == interface and val.list then
+                               local assocmac, assot
                                for assocmac, assot in pairs(val.list) do
-                                       assocmac = string.lower(assocmac or "")
-                                       if rmac == assocmac then
+                                       if rmac == luci.ip.checkmac(assocmac) then
                                                signal = tonumber(assot.signal)
                                                noise = tonumber(assot.noise)
                                                snr = (noise*-1) - (signal*-1)
index 13b8edc..b4fdbd5 100644 (file)
@@ -2,6 +2,7 @@ module("luci.controller.splash.splash", package.seeall)
 
 local uci = luci.model.uci.cursor()
 local util = require "luci.util"
+local ipc = require "luci.ip"
 
 function index()
        entry({"admin", "services", "splash"}, cbi("splash/splash"), _("Client-Splash"), 90)
@@ -24,30 +25,35 @@ function index()
 end
 
 function ip_to_mac(ip)
-       local ipc = require "luci.ip"
        local i, n
-
-       for i, n in ipairs(ipc.neighbors()) do
-               if n.mac and n.dest and n.dest:equal(ip) then
-                       return n.mac
-               end
+       for i, n in ipairs(ipc.neighbors({ dest = ip })) do
+               local mac = ipc.checkmac(n.mac)
+               if mac then return mac end
        end
 end
 
 function action_dispatch()
        local uci = luci.model.uci.cursor_state()
-       local mac = ip_to_mac(luci.http.getenv("REMOTE_ADDR")) or ""
+       local mac = ip_to_mac(luci.http.getenv("REMOTE_ADDR"))
        local access = false
 
-       uci:foreach("luci_splash", "lease", function(s)
-               if s.mac and s.mac:lower() == mac then access = true end
-       end)
+       if mac then
+               uci:foreach("luci_splash", "lease", function(s)
+                       if ipc.checkmac(s.mac) == mac then
+                               access = true
+                               return false
+                       end
+               end)
 
-       uci:foreach("luci_splash", "whitelist", function(s)
-               if s.mac and s.mac:lower() == mac then access = true end
-       end)
+               uci:foreach("luci_splash", "whitelist", function(s)
+                       if ipc.checkmac(s.mac) == mac then
+                               access = true
+                               return false
+                       end
+               end)
+       end
 
-       if #mac > 0 and access then
+       if access then
                luci.http.redirect(luci.dispatcher.build_url())
        else
                luci.http.redirect(luci.dispatcher.build_url("splash", "splash"))
@@ -56,33 +62,39 @@ end
 
 function blacklist()
        leased_macs = { }
-       uci:foreach("luci_splash", "blacklist",
-               function(s) leased_macs[s.mac:lower()] = true
+       uci:foreach("luci_splash", "blacklist", function(s)
+               local m = ipc.checkmac(s.mac)
+               if m then leased_macs[m] = true end
        end)
        return leased_macs
 end
 
 function action_activate()
        local ipc = require "luci.ip"
-       local mac = ip_to_mac(luci.http.getenv("REMOTE_ADDR") or "127.0.0.1") or ""
+       local mac = ip_to_mac(luci.http.getenv("REMOTE_ADDR") or "127.0.0.1")
        local uci_state = require "luci.model.uci".cursor_state()
        local blacklisted = false
        if mac and luci.http.formvalue("accept") then
-               uci:foreach("luci_splash", "blacklist",
-                       function(s) if s.mac and s.mac:lower() == mac then blacklisted = true end
+               uci:foreach("luci_splash", "blacklist", function(s)
+                       if ipc.checkmac(s.mac) == mac then
+                               blacklisted = true
+                               return false
+                       end
                end)
+
                if blacklisted then     
                        luci.http.redirect(luci.dispatcher.build_url("splash" ,"blocked"))
                else
+                       local id = tostring(mac):gsub(':', ''):lower()
                        local redirect_url = uci:get("luci_splash", "general", "redirect_url")
                        if not redirect_url then
-                               redirect_url = uci_state:get("luci_splash_locations", mac:gsub(':', ''):lower(), "location")
+                               redirect_url = uci_state:get("luci_splash_locations", id, "location")
                        end
                        if not redirect_url then
                                redirect_url = luci.model.uci.cursor():get("freifunk", "community", "homepage") or 'http://www.freifunk.net'
                        end
-                       remove_redirect(mac:gsub(':', ''):lower())
-                       os.execute("luci-splash lease "..mac.." >/dev/null 2>&1")
+                       remove_redirect(id)
+                       os.execute("luci-splash lease "..tostring(mac).." >/dev/null 2>&1")
                        luci.http.redirect(redirect_url)
                end
        else
@@ -101,6 +113,7 @@ function action_status_admin()
                remove    = { }
        }
 
+       local key, _
        for key, _ in pairs(macs) do
                local policy = luci.http.formvalue("policy.%s" % key)
                local mac    = luci.http.protocol.urldecode(key)
@@ -141,17 +154,17 @@ function action_status_public()
        luci.template.render("admin_status/splash", { is_admin = false })
 end
 
-function remove_redirect(mac)
-       local mac = mac:lower()
-       mac = mac:gsub(":", "")
+function remove_redirect(id)
        local uci = require "luci.model.uci".cursor_state()
        local redirects = uci:get_all("luci_splash_locations")
        --uci:load("luci_splash_locations")
        uci:revert("luci_splash_locations")
+
        -- For all redirects
+       local k, v
        for k, v in pairs(redirects) do
                if v[".type"] == "redirect" then
-                       if v[".name"] ~= mac then
+                       if v[".name"] ~= id then
                                -- Rewrite state
                                uci:section("luci_splash_locations", "redirect", v[".name"], {
                                        location = v.location
@@ -159,5 +172,6 @@ function remove_redirect(mac)
                        end
                end
        end
+
        uci:save("luci_splash_redirects")
 end
index 3415c20..37f6777 100644 (file)
@@ -6,6 +6,8 @@
 <%-
 
 local utl = require "luci.util"
+local sys = require "luci.sys"
+local ipc = require "luci.ip"
 local ipt = require "luci.sys.iptparser".IptParser()
 local uci = require "luci.model.uci".cursor_state()
 local wat = require "luci.tools.webadmin"
@@ -14,21 +16,15 @@ local fs  = require "nixio.fs"
 
 local clients = { }
 local leasetime = tonumber(uci:get("luci_splash", "general", "leasetime") or 1) * 60 * 60
-local leasefile = "/tmp/dhcp.leases"
-
-uci:foreach("dhcp", "dnsmasq",
-       function(s)
-               if s.leasefile then leasefile = s.leasefile end
-       end)
-
 
 uci:foreach("luci_splash_leases", "lease",
        function(s)
-               if s.start and s.mac then
-                       clients[s.mac:lower()] = {
+               local m = ipc.checkmac(s.mac)
+               if m and s.start then
+                       clients[m] = {
                                start   = tonumber(s.start),
                                limit   = ( tonumber(s.start) + leasetime ),
-                               mac     = s.mac:upper(),
+                               mac     = m,
                                ipaddr  = s.ipaddr,
                                policy  = "normal",
                                packets = 0,
@@ -39,11 +35,12 @@ uci:foreach("luci_splash_leases", "lease",
 
 for _, r in ipairs(ipt:find({table="nat", chain="luci_splash_leases"})) do
        if r.options and #r.options >= 2 and r.options[1] == "MAC" then
-               if not clients[r.options[2]:lower()] then
-                       clients[r.options[2]:lower()] = {
+               local m = ipc.checkmac(r.options[2])
+               if m and not clients[m] then
+                       clients[m] = {
                                start  = 0,
                                limit  = 0,
-                               mac    = r.options[2]:upper(),
+                               mac    = m,
                                policy = ( r.target == "RETURN" ) and "whitelist" or "blacklist",
                                packets = 0,
                                bytes   = 0
@@ -60,7 +57,7 @@ for mac, client in pairs(clients) do
 
        if client.ipaddr then
                local rin  = ipt:find({table="mangle", chain="luci_splash_mark_in", destination=client.ipaddr})
-               local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", client.mac:upper()}})
+               local rout = ipt:find({table="mangle", chain="luci_splash_mark_out", options={"MAC", client.mac}})
 
                if rin and #rin > 0 then
                        client.bytes_in   = rin[1].bytes
@@ -76,39 +73,27 @@ end
 
 uci:foreach("luci_splash", "whitelist",
        function(s)
-               if s.mac and clients[s.mac:lower()] then
-                       clients[s.mac:lower()].policy="whitelist"
+               local m = ipc.checkmac(s.mac)
+               if m and clients[m] then
+                       clients[m].policy="whitelist"
                end
        end)
 
 uci:foreach("luci_splash", "blacklist",
        function(s)
-               if s.mac and clients[s.mac:lower()] then
-                       clients[s.mac:lower()].policy=(s.kicked and "kicked" or "blacklist")
+               local m = ipc.checkmac(s.mac)
+               if m and clients[m] then
+                       clients[m].policy=(s.kicked and "kicked" or "blacklist")
                end
        end)            
 
-if fs.access(leasefile) then
-       for l in io.lines(leasefile) do
-               local time, mac, ip, name = l:match("^(%d+) (%S+) (%S+) (%S+)")
-               if time and mac and ip then
-                       local c = clients[mac:lower()]
-                       if c then
-                               c.ip = ip
-                               c.hostname = ( name ~= "*" ) and name or nil
-                       end
-               end
+sys.net.host_hints(function(mac, v4, v6, name)
+       local c = mac and clients[mac]
+       if c then
+               c.ip = c.ip or v4
+               c.hostname = c.hostname or name
        end
-end
-
-for i, n in ipairs(ipc.neighbors({ family = 4 })) do
-       if n.mac and n.dest then
-               local c = clients[n.mac]
-               if c and not c.ip then
-                       c.ip = n.dest:string()
-               end
-       end
-end
+end)
 
 local function showmac(mac)
        if not is_admin then
@@ -176,7 +161,7 @@ end
                                                splash.hostname, splash.ip, splash.mac, splash.timeleft, splash.trafficin, splash.trafficout);
 
                                <% if is_admin then %>
-                                       s += String.format('<select name="policy.%s" style="width:200px">', splash.mac.toLowerCase());
+                                       s += String.format('<select name="policy.%s" style="width:200px">', splash.mac);
                                        if (splash.policy == 'whitelist') {     
                                                s += '<option value="whitelist" selected="selected"><%:whitelisted%></option>'
                                        } else {
@@ -196,7 +181,7 @@ end
                                        s += String.format(
                                                '</select>' +
                                                '<input type="submit" class="cbi-button cbi-button-save" name="save.%s" value="<%:Save%>" />',
-                                               splash.mac.toLowerCase());
+                                               splash.mac);
                                <% else %>
                                        s += String.format('%s', splash.policy);
                                <% end %>
index 2a8acee..9f7a51a 100644 (file)
@@ -3,25 +3,80 @@
 
 module("luci.statistics.rrdtool.definitions.apcups",package.seeall)
 
-function rrdargs( graph, plugin, plugin_instance, dtype )
+function rrdargs( graph, plugin, plugin_instance )
+
+       local lu = require("luci.util")
+       local rv = { }
+
+       -- Types and instances supported by APC UPS
+       -- e.g. ups_types -> { 'timeleft', 'charge', 'percent', 'voltage' }
+       -- e.g. ups_inst['voltage'] -> { 'input', 'battery' }
+
+       local ups_types = graph.tree:data_types( plugin, plugin_instance )
+
+       local ups_inst = {}
+       for _, t in ipairs(ups_types) do
+               ups_inst[t] = graph.tree:data_instances( plugin, plugin_instance, t )
+       end
+
+
+    -- Check if hash table or array is empty or nil-filled
+
+       local function empty( t )
+               for _, v in pairs(t) do
+                       if type(v) then return false end
+               end
+               return true
+       end
+
+
+       -- Append graph definition but only types/instances which are
+       -- supported and available to the plugin and UPS.
+
+       local function add_supported( t, defs )
+               local def_inst = defs['data']['instances']
+
+               if type(def_inst) == "table" then
+                       for k, v in pairs( def_inst ) do
+                               if lu.contains( ups_types, k) then
+                                       for j = #v, 1, -1 do
+                                               if not lu.contains( ups_inst[k], v[j] ) then
+                                                       table.remove( v, j )
+                                               end
+                                       end
+                                       if #v == 0 then
+                                               def_inst[k] = nil  -- can't assign v: immutable
+                                       end
+                               else
+                                       def_inst[k] = nil  -- can't assign v: immutable
+                               end
+                       end
+                       if empty(def_inst) then return end
+               end
+               table.insert( t, defs )
+       end
+
+
+    -- Graph definitions for APC UPS measurements MUST use only 'instances':
+    -- e.g. instances = { voltage = {  "input", "output" } }
 
        local voltagesdc = {
                title = "%H: Voltages on APC UPS - Battery",
                vlabel = "Volts DC",
-    alt_autoscale = true,
+               alt_autoscale = true,
                number_format = "%5.1lfV",
                data = {
                        instances = {
                                voltage = { "battery" }
                        },
-
-                       options = { 
+                       options = {
                                voltage = { title = "Battery voltage", noarea=true }
                        }
                }
        }
-       
-       local voltages = {
+       add_supported( rv, voltagesdc )
+
+       local voltagesac = {
                title = "%H: Voltages on APC UPS - AC",
                vlabel = "Volts AC",
                alt_autoscale = true,
@@ -30,13 +85,13 @@ function rrdargs( graph, plugin, plugin_instance, dtype )
                        instances = {
                                voltage = {  "input", "output" }
                        },
-
                        options = {
                                voltage_output  = { color = "00e000", title = "Output voltage", noarea=true, overlay=true },
                                voltage_input   = { color = "ffb000", title = "Input voltage", noarea=true, overlay=true }
                        }
                }
        }
+       add_supported( rv, voltagesac )
 
        local percentload = {
                title = "%H: Load on APC UPS ",
@@ -45,17 +100,15 @@ function rrdargs( graph, plugin, plugin_instance, dtype )
                y_max = "100",
                number_format = "%5.1lf%%",
                data = {
-                       sources = {
-                               percent_load = { "value" }
-                       },
                        instances = {
-                               percent = "load"
+                               percent = { "load" }
                        },
                        options = {
                                percent_load = { color = "00ff00", title = "Load level"  }
                        }
                }
        }
+       add_supported( rv, percentload )
 
        local charge_percent = {
                title = "%H: Battery charge on APC UPS ",
@@ -64,54 +117,59 @@ function rrdargs( graph, plugin, plugin_instance, dtype )
                y_max = "100",
                number_format = "%5.1lf%%",
                data = {
-                       types = { "charge" },
+                       instances = {
+                               charge = { "" }
+                       },
                        options = {
                                charge = { color = "00ff0b", title = "Charge level"  }
                        }
                }
        }
+       add_supported( rv, charge_percent )
 
        local temperature = {
                title = "%H: Battery temperature on APC UPS ",
                vlabel = "\176C",
                number_format = "%5.1lf\176C",
                data = {
-                       types = { "temperature" },
+                       instances = {
+                               temperature = { "" }
+                       },
                        options = {
                                temperature = { color = "ffb000", title = "Battery temperature" } }
                }
        }
+       add_supported( rv, temperature )
 
        local timeleft = {
                title = "%H: Time left on APC UPS ",
                vlabel = "Minutes",
                number_format = "%.1lfm",
                data = {
-                       sources = {
-                               timeleft = { "value" }
+                       instances = {
+                               timeleft = { "" }
                        },
                        options = {
                                timeleft = { color = "0000ff", title = "Time left" }
                        }
                }
        }
+       add_supported( rv, timeleft )
 
        local frequency = {
                title = "%H: Incoming line frequency on APC UPS ",
                vlabel = "Hz",
                number_format = "%5.0lfhz",
                data = {
-                       sources = {
-                               frequency_input = { "value" }
-                       },
                        instances = {
-                               frequency = "frequency"
+                               frequency = { "input" }
                        },
                        options = {
-                               frequency_frequency = { color = "000fff", title = "Line frequency" }
+                               frequency_input = { color = "000fff", title = "Line frequency" }
                        }
                }
        }
+       add_supported( rv, frequency )
 
-       return { voltages, voltagesdc, percentload, charge_percent, temperature, timeleft, frequency }
+       return rv
 end
index 6170f9d..2bd25bc 100644 (file)
@@ -1,11 +1,11 @@
-# Copyright 2017 Dirk Brenken (dev@brenken.org)
+# Copyright 2017-2018 Dirk Brenken (dev@brenken.org)
 # This is free software, licensed under the Apache License, Version 2.0
 #
 
 include $(TOPDIR)/rules.mk
 
 LUCI_TITLE:=LuCI support for Travelmate
-LUCI_DEPENDS:=+travelmate +luci-lib-jsonc
+LUCI_DEPENDS:=+travelmate +luci-lib-jsonc +qrencode
 LUCI_PKGARCH:=all
 
 include ../../luci.mk
index 0f75834..14b8d77 100644 (file)
@@ -3,7 +3,6 @@
 
 module("luci.controller.travelmate", package.seeall)
 
-local fs    = require("nixio.fs")
 local util  = require("luci.util")
 local i18n  = require("luci.i18n")
 local templ = require("luci.template")
@@ -15,7 +14,8 @@ function index()
        entry({"admin", "services", "travelmate"}, firstchild(), _("Travelmate"), 40).dependent = false
        entry({"admin", "services", "travelmate", "tab_from_cbi"}, cbi("travelmate/overview_tab", {hideresetbtn=true, hidesavebtn=true}), _("Overview"), 10).leaf = true
        entry({"admin", "services", "travelmate", "stations"}, template("travelmate/stations"), _("Wireless Stations"), 20).leaf = true
-       entry({"admin", "services", "travelmate", "logfile"}, call("logread"), _("View Logfile"), 30).leaf = true
+       entry({"admin", "services", "travelmate", "apqr"}, template("travelmate/ap_qr"), _("AP QR-Codes"), 30).leaf = true
+       entry({"admin", "services", "travelmate", "logfile"}, call("logread"), _("View Logfile"), 40).leaf = true
        entry({"admin", "services", "travelmate", "advanced"}, firstchild(), _("Advanced"), 100)
        entry({"admin", "services", "travelmate", "advanced", "configuration"}, cbi("travelmate/configuration_tab"), _("Edit Travelmate Configuration"), 110).leaf = true
        entry({"admin", "services", "travelmate", "advanced", "cfg_wireless"}, cbi("travelmate/cfg_wireless_tab"), _("Edit Wireless Configuration"), 120).leaf = true
index 4fec920..860ce22 100644 (file)
@@ -8,9 +8,9 @@ local util     = require("luci.util")
 local nw       = require("luci.model.network").init()
 local fw       = require("luci.model.firewall").init()
 local dump     = util.ubus("network.interface", "dump", {})
-local trmiface = uci.get("travelmate", "global", "trm_iface") or "trm_wwan"
-local trminput = uci.get("travelmate", "global", "trm_rtfile") or "/tmp/trm_runtime.json"
-local uplink   = uci.get("network", trmiface) or ""
+local trmiface = uci:get("travelmate", "global", "trm_iface") or "trm_wwan"
+local trminput = uci:get("travelmate", "global", "trm_rtfile") or "/tmp/trm_runtime.json"
+local uplink   = uci:get("network", trmiface) or ""
 local parse    = json.parse(fs.readfile(trminput) or "")
 
 m = Map("travelmate", translate("Travelmate"),
@@ -181,28 +181,27 @@ e2.datatype = "and(uciname,rangelength(6,6))"
 e2.rmempty = true
 
 e3 = e:option(Value, "trm_maxretry", translate("Connection Limit"),
-       translate("Retry limit to connect to an uplink. ")
-       .. translate("To disable this feature set it to '0' which means unlimited retries."))
+       translate("Retry limit to connect to an uplink."))
 e3.default = 3
-e3.datatype = "range(0,30)"
+e3.datatype = "range(1,10)"
 e3.rmempty = false
 
 e4 = e:option(Value, "trm_minquality", translate("Signal Quality Threshold"),
        translate("Minimum signal quality threshold as percent for conditional uplink (dis-) connections."))
-e4.default = 30
-e4.datatype = "range(0,100)"
+e4.default = 35
+e4.datatype = "range(20,80)"
 e4.rmempty = false
 
 e5 = e:option(Value, "trm_maxwait", translate("Interface Timeout"),
-       translate("How long should travelmate wait for a successful wlan interface reload."))
+       translate("How long should travelmate wait for a successful wlan uplink connection."))
 e5.default = 30
-e5.datatype = "range(5,60)"
+e5.datatype = "range(20,40)"
 e5.rmempty = false
 
 e6 = e:option(Value, "trm_timeout", translate("Overall Timeout"),
        translate("Timeout in seconds between retries in 'automatic' mode."))
 e6.default = 60
-e6.datatype = "range(60,300)"
+e6.datatype = "range(30,300)"
 e6.rmempty = false
 
 return m
index 93e8a05..50edd17 100644 (file)
@@ -4,7 +4,7 @@
 local fs       = require("nixio.fs")
 local uci      = require("luci.model.uci").cursor()
 local http     = require("luci.http")
-local trmiface = uci.get("travelmate", "global", "trm_iface") or "trm_wwan"
+local trmiface = uci:get("travelmate", "global", "trm_iface") or "trm_wwan"
 local encr_psk = {"psk", "psk2", "psk-mixed"}
 local encr_wpa = {"wpa", "wpa2", "wpa-mixed"}
 
diff --git a/applications/luci-app-travelmate/luasrc/view/travelmate/ap_qr.htm b/applications/luci-app-travelmate/luasrc/view/travelmate/ap_qr.htm
new file mode 100644 (file)
index 0000000..13342a3
--- /dev/null
@@ -0,0 +1,62 @@
+<%#
+Copyright 2018 Dirk Brenken (dev@brenken.org)
+This is free software, licensed under the Apache License, Version 2.0
+-%>
+
+<%+header%>
+
+<div class="cbi-map">
+    <div class="cbi-map-descr">
+        <%=translate("Here you'll find the QR codes from all of your configured Access Points. It allows you to connect your Android or iOS devices to your router's WiFi using the QR code shown below.")%>
+    </div>
+<%-
+  local write = io.write
+  local uci   = require("luci.model.uci").cursor()
+
+  uci:foreach("wireless", "wifi-iface", function(s)
+    local device = s.device or ""
+    local mode = s.mode or ""
+    local ssid = s.ssid or ""
+    local enc = s.encryption or ""
+    local key = s.key or ""
+    local hidden = s.hidden or "false"
+    local disabled = s.disabled or ""
+    local wep_slots = {s.key1 or "", s.key2 or "", s.key3 or "", s.key4 or ""}
+
+    if device and mode == "ap" and disabled ~= "1" then
+      if string.match(enc, '^psk') then
+        enc = "WPA"
+      elseif string.match(enc, '^wep') then
+        enc = "WEP"
+        if tonumber(key) then
+          key = wep_slots[tonumber(key)]
+        end
+      elseif enc == "none" then
+        enc = "nopass"
+        key = "nokey"
+      else
+        enc = ""
+      end
+      if hidden == "1" then
+        hidden = "true"
+      end
+      if ssid and enc and key then
+        local e_ssid = string.gsub(ssid,"[\"\\';:, ]",[[\\\%1]])
+        local e_key = string.gsub(key,"[\"\\';:, ]",[[\\\%1]])
+        local qrcode = ""
+        if nixio.fs.access("/usr/bin/qrencode") then
+          qrcode = luci.sys.exec("/usr/bin/qrencode --inline --8bit --type=SVG --output=- 'WIFI:S:\"'" .. e_ssid .. "'\";T:'" .. enc .. "';P:\"'" .. e_key .. "'\";H:'" .. hidden .. "';'")
+        end
+-%>
+    <fieldset class="cbi-section">
+        <legend>AP on <%=device%> with SSID "<%=ssid%>"</legend>
+        <h3 name="content"><%=qrcode%></h3>
+    </fieldset>
+<%-
+      end
+    end
+  end)
+%>
+</div>
+
+<%+footer%>
index a267339..1dacb6e 100644 (file)
@@ -12,7 +12,6 @@ This is free software, licensed under the Apache License, Version 2.0
 <%+header%>
 
 <div class="cbi-map">
-<h2 name="content"><%:Wireless Stations%></h2>
 <div class="cbi-map-descr">
   <%=translatef("Provides an overview of all configured uplinks for the travelmate interface (%s). You can edit, delete or re-order existing uplinks or scan for a new one. The currently used uplink is emphasized in blue.", trmiface)%>
 </div>
index 56e3bad..30f34ef 100644 (file)
@@ -7,11 +7,14 @@ msgstr ""
 "Language-Team: \n"
 "MIME-Version: 1.0\n"
 "Content-Transfer-Encoding: 8bit\n"
-"X-Generator: Poedit 2.0.5\n"
+"X-Generator: Poedit 2.0.6\n"
 "Last-Translator: INAGAKI Hiroshi <musashino.open@gmail.com>\n"
 "Plural-Forms: nplurals=1; plural=0;\n"
 "Language: ja\n"
 
+msgid "AP QR-Codes"
+msgstr "AP QR-コード"
+
 msgid "Actions"
 msgstr "操作"
 
@@ -43,6 +46,16 @@ msgstr "BSSID"
 msgid "Back to overview"
 msgstr "概要へ戻る"
 
+msgid "Captive Portal Detection"
+msgstr "キャプティブポータル検知"
+
+msgid ""
+"Check the internet availability, log captive portal redirections and keep "
+"the uplink connection 'alive'."
+msgstr ""
+"インターネットの利用可否を確認し、キャプティブポータル リダイレクトを記録して"
+"アップリンク接続を 'alive' として保持します。"
+
 msgid "Cipher"
 msgstr "暗号化方式"
 
@@ -135,13 +148,18 @@ msgid "Force a manual uplink rescan / reconnect in 'trigger' mode."
 msgstr ""
 "'trigger' モード時に、手動でアップリンクの再スキャンと再接続を行います。"
 
-msgid "How long should travelmate wait for a successful wlan interface reload."
+msgid ""
+"Here you'll find the QR codes from all of your configured Access Points. It "
+"allows you to connect your Android or iOS devices to your router's WiFi "
+"using the QR code shown below."
 msgstr ""
-"無線LAN インターフェースのリロードが成功するまでの、Travelmate の待機時間で"
-"す。"
+"ここには、構成済みの全アクセスポイントの QR コードを表示しています。以下の "
+"QR コードを使用して、 Android または iOS デバイスをルータの WiFi に接続するこ"
+"とができます。"
 
-msgid "How many times should travelmate try to connect to an Uplink."
-msgstr "Travelmate がアップリンクへの接続を試行する回数です。"
+msgid ""
+"How long should travelmate wait for a successful wlan uplink connection."
+msgstr "Travelmate が無線アップリンクへの接続成功を待つ時間です。"
 
 msgid "Identity"
 msgstr "ID"
@@ -171,6 +189,13 @@ msgstr "最終実行日時"
 msgid "Manual Rescan"
 msgstr "手動再スキャン"
 
+msgid ""
+"Minimum signal quality threshold as percent for conditional uplink (dis-) "
+"connections."
+msgstr ""
+"条件付きアップリンク接続(または切断)のための、シグナル品質閾値の下限(%)で"
+"す。"
+
 msgid "Move down"
 msgstr "下へ"
 
@@ -233,6 +258,9 @@ msgstr "再スキャン"
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'."
 msgstr "Travelmate が指定された無線に接続するよう制御します。(例: 'radio0')"
 
+msgid "Retry limit to connect to an uplink."
+msgstr "アップリンクへの接続を試行する回数です。"
+
 msgid "Runtime Information"
 msgstr "実行情報"
 
@@ -248,6 +276,9 @@ msgstr "保存"
 msgid "Scan"
 msgstr "スキャン:"
 
+msgid "Signal Quality Threshold"
+msgstr "シグナル品質閾値"
+
 msgid "Signal strength"
 msgstr "信号強度"
 
@@ -298,24 +329,20 @@ msgstr ""
 msgid "Timeout in seconds between retries in 'automatic' mode."
 msgstr "'automatic' モード時に接続を確認または再試行する間隔(秒)です。"
 
-msgid "To disable this feature set it to '0' which means unlimited retries."
-msgstr ""
-"この機能を無効にして接続の再試行を無制限にする場合、 '0' を設定します。"
-
 msgid "Travelmate"
 msgstr "Travelmate"
 
 msgid "Travelmate Logfile"
 msgstr "Travelmate ログファイル"
 
-msgid "Travelmate Status"
-msgstr "Travelmate ステータス"
+msgid "Travelmate Status (Quality)"
+msgstr "Travelmate ステータス(品質)"
 
 msgid "Travelmate Version"
 msgstr "Travelmate バージョン"
 
-msgid "Trigger delay"
-msgstr "トリガ遅延"
+msgid "Trigger Delay"
+msgstr "トリガ遅延"
 
 msgid "Unknown"
 msgstr "不明"
@@ -365,20 +392,8 @@ msgstr ""
 "ファイアウォールの wan ゾーンに追加します。このステップは、一度だけ実行される"
 "必要があります。"
 
-msgid "connected"
-msgstr "接続済"
-
-msgid "error"
-msgstr "エラー"
-
 msgid "hidden"
 msgstr "(不明)"
 
 msgid "n/a"
 msgstr "利用不可"
-
-msgid "not connected"
-msgstr "未接続"
-
-msgid "running"
-msgstr "実行中"
index cf17b02..7cb6ac0 100644 (file)
@@ -12,6 +12,9 @@ msgstr ""
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 "Language: pt_BR\n"
 
+msgid "AP QR-Codes"
+msgstr ""
+
 msgid "Actions"
 msgstr ""
 
@@ -43,6 +46,14 @@ msgstr ""
 msgid "Back to overview"
 msgstr ""
 
+msgid "Captive Portal Detection"
+msgstr ""
+
+msgid ""
+"Check the internet availability, log captive portal redirections and keep "
+"the uplink connection 'alive'."
+msgstr ""
+
 msgid "Cipher"
 msgstr ""
 
@@ -129,10 +140,14 @@ msgstr ""
 msgid "Force a manual uplink rescan / reconnect in 'trigger' mode."
 msgstr ""
 
-msgid "How long should travelmate wait for a successful wlan interface reload."
+msgid ""
+"Here you'll find the QR codes from all of your configured Access Points. It "
+"allows you to connect your Android or iOS devices to your router's WiFi "
+"using the QR code shown below."
 msgstr ""
 
-msgid "How many times should travelmate try to connect to an Uplink."
+msgid ""
+"How long should travelmate wait for a successful wlan uplink connection."
 msgstr ""
 
 msgid "Identity"
@@ -161,6 +176,11 @@ msgstr ""
 msgid "Manual Rescan"
 msgstr ""
 
+msgid ""
+"Minimum signal quality threshold as percent for conditional uplink (dis-) "
+"connections."
+msgstr ""
+
 msgid "Move down"
 msgstr ""
 
@@ -219,6 +239,9 @@ msgstr ""
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'."
 msgstr ""
 
+msgid "Retry limit to connect to an uplink."
+msgstr ""
+
 msgid "Runtime Information"
 msgstr ""
 
@@ -234,6 +257,9 @@ msgstr ""
 msgid "Scan"
 msgstr ""
 
+msgid "Signal Quality Threshold"
+msgstr ""
+
 msgid "Signal strength"
 msgstr ""
 
@@ -274,22 +300,19 @@ msgstr ""
 msgid "Timeout in seconds between retries in 'automatic' mode."
 msgstr ""
 
-msgid "To disable this feature set it to '0' which means unlimited retries."
-msgstr ""
-
 msgid "Travelmate"
 msgstr "Travelmate"
 
 msgid "Travelmate Logfile"
 msgstr ""
 
-msgid "Travelmate Status"
+msgid "Travelmate Status (Quality)"
 msgstr ""
 
 msgid "Travelmate Version"
 msgstr ""
 
-msgid "Trigger delay"
+msgid "Trigger Delay"
 msgstr ""
 
 msgid "Unknown"
@@ -338,24 +361,12 @@ msgid ""
 "add it to the wan zone of the firewall. This step has only to be done once."
 msgstr ""
 
-msgid "connected"
-msgstr ""
-
-msgid "error"
-msgstr ""
-
 msgid "hidden"
 msgstr ""
 
 msgid "n/a"
 msgstr ""
 
-msgid "not connected"
-msgstr ""
-
-msgid "running"
-msgstr ""
-
 #~ msgid ""
 #~ "Brief advice: Create a wwan interface, configure it to use dhcp and add "
 #~ "it to the wan zone in firewall. Create the wifi interfaces to be used "
index fe0e476..0b04e27 100644 (file)
@@ -15,6 +15,9 @@ msgstr ""
 "Project-Info: Это технический перевод, не дословный. Главное-удобный русский "
 "интерфейс, все проверялось в графическом режиме, совместим с другими apps\n"
 
+msgid "AP QR-Codes"
+msgstr ""
+
 msgid "Actions"
 msgstr "Действия"
 
@@ -46,6 +49,14 @@ msgstr "BSSID"
 msgid "Back to overview"
 msgstr "Назад в меню"
 
+msgid "Captive Portal Detection"
+msgstr ""
+
+msgid ""
+"Check the internet availability, log captive portal redirections and keep "
+"the uplink connection 'alive'."
+msgstr ""
+
 msgid "Cipher"
 msgstr "Алгоритм шифрования"
 
@@ -138,13 +149,15 @@ msgstr ""
 "Принудительно выполнить повторное сканирование/повторное подключение внешних "
 "сетей в режиме 'ручной'."
 
-msgid "How long should travelmate wait for a successful wlan interface reload."
+msgid ""
+"Here you'll find the QR codes from all of your configured Access Points. It "
+"allows you to connect your Android or iOS devices to your router's WiFi "
+"using the QR code shown below."
 msgstr ""
-"Временная задержка необходима TravelMate для полной перезагрузки wlan "
-"интерфейса."
 
-msgid "How many times should travelmate try to connect to an Uplink."
-msgstr "Сколько раз TravelMate должен пытаться подключиться к сети. "
+msgid ""
+"How long should travelmate wait for a successful wlan uplink connection."
+msgstr ""
 
 msgid "Identity"
 msgstr "Идентификация EAP"
@@ -174,6 +187,11 @@ msgstr "Дата последнего запуска"
 msgid "Manual Rescan"
 msgstr "Поиск сети вручную"
 
+msgid ""
+"Minimum signal quality threshold as percent for conditional uplink (dis-) "
+"connections."
+msgstr ""
+
 msgid "Move down"
 msgstr "Переместить вниз"
 
@@ -238,6 +256,9 @@ msgstr "Пересканировать"
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'."
 msgstr "Выделить TravelMate-у конкретное Wi-Fi устройство, например 'radio0'."
 
+msgid "Retry limit to connect to an uplink."
+msgstr ""
+
 msgid "Runtime Information"
 msgstr "Информация о состоянии"
 
@@ -253,6 +274,9 @@ msgstr "Сохранить"
 msgid "Scan"
 msgstr "Поиск"
 
+msgid "Signal Quality Threshold"
+msgstr ""
+
 msgid "Signal strength"
 msgstr "Мощность сигнала"
 
@@ -305,25 +329,20 @@ msgstr ""
 "Время ожидания в секундах между повторными попытками соединения в режиме "
 "'автоматически'."
 
-msgid "To disable this feature set it to '0' which means unlimited retries."
-msgstr ""
-"<br />Чтобы отключить эту функцию, установите значение '0', что означает "
-"неограниченное количество попыток."
-
 msgid "Travelmate"
 msgstr "TravelMate"
 
 msgid "Travelmate Logfile"
 msgstr "Системный журнал TravelMate"
 
-msgid "Travelmate Status"
-msgstr "Состояние Travelmate"
+msgid "Travelmate Status (Quality)"
+msgstr ""
 
 msgid "Travelmate Version"
 msgstr "Версия TravelMate"
 
-msgid "Trigger delay"
-msgstr "Задержка запуска"
+msgid "Trigger Delay"
+msgstr ""
 
 msgid "Unknown"
 msgstr "Неизвестно"
@@ -371,20 +390,40 @@ msgid ""
 "add it to the wan zone of the firewall. This step has only to be done once."
 msgstr "добавить в wan зону межсетевого экрана. Можно сделать только один раз."
 
-msgid "connected"
-msgstr "подключен"
-
-msgid "error"
-msgstr "ошибка"
-
 msgid "hidden"
 msgstr "скрытый"
 
 msgid "n/a"
 msgstr "нет данных"
 
-msgid "not connected"
-msgstr "не подключено"
+#~ msgid ""
+#~ "How long should travelmate wait for a successful wlan interface reload."
+#~ msgstr ""
+#~ "Временная задержка необходима TravelMate для полной перезагрузки wlan "
+#~ "интерфейса."
+
+#~ msgid "How many times should travelmate try to connect to an Uplink."
+#~ msgstr "Сколько раз TravelMate должен пытаться подключиться к сети. "
+
+#~ msgid "To disable this feature set it to '0' which means unlimited retries."
+#~ msgstr ""
+#~ "<br />Чтобы отключить эту функцию, установите значение '0', что означает "
+#~ "неограниченное количество попыток."
+
+#~ msgid "Travelmate Status"
+#~ msgstr "Состояние Travelmate"
+
+#~ msgid "Trigger delay"
+#~ msgstr "Задержка запуска"
+
+#~ msgid "connected"
+#~ msgstr "подключен"
+
+#~ msgid "error"
+#~ msgstr "ошибка"
+
+#~ msgid "not connected"
+#~ msgstr "не подключено"
 
-msgid "running"
-msgstr "работает"
+#~ msgid "running"
+#~ msgstr "работает"
index aeefa49..e27c61a 100644 (file)
@@ -1,6 +1,9 @@
 msgid ""
 msgstr "Content-Type: text/plain; charset=UTF-8"
 
+msgid "AP QR-Codes"
+msgstr ""
+
 msgid "Actions"
 msgstr ""
 
@@ -32,6 +35,14 @@ msgstr ""
 msgid "Back to overview"
 msgstr ""
 
+msgid "Captive Portal Detection"
+msgstr ""
+
+msgid ""
+"Check the internet availability, log captive portal redirections and keep "
+"the uplink connection 'alive'."
+msgstr ""
+
 msgid "Cipher"
 msgstr ""
 
@@ -118,10 +129,14 @@ msgstr ""
 msgid "Force a manual uplink rescan / reconnect in 'trigger' mode."
 msgstr ""
 
-msgid "How long should travelmate wait for a successful wlan interface reload."
+msgid ""
+"Here you'll find the QR codes from all of your configured Access Points. It "
+"allows you to connect your Android or iOS devices to your router's WiFi "
+"using the QR code shown below."
 msgstr ""
 
-msgid "How many times should travelmate try to connect to an Uplink."
+msgid ""
+"How long should travelmate wait for a successful wlan uplink connection."
 msgstr ""
 
 msgid "Identity"
@@ -150,6 +165,11 @@ msgstr ""
 msgid "Manual Rescan"
 msgstr ""
 
+msgid ""
+"Minimum signal quality threshold as percent for conditional uplink (dis-) "
+"connections."
+msgstr ""
+
 msgid "Move down"
 msgstr ""
 
@@ -208,6 +228,9 @@ msgstr ""
 msgid "Restrict travelmate to a dedicated radio, e.g. 'radio0'."
 msgstr ""
 
+msgid "Retry limit to connect to an uplink."
+msgstr ""
+
 msgid "Runtime Information"
 msgstr ""
 
@@ -223,6 +246,9 @@ msgstr ""
 msgid "Scan"
 msgstr ""
 
+msgid "Signal Quality Threshold"
+msgstr ""
+
 msgid "Signal strength"
 msgstr ""
 
@@ -263,22 +289,19 @@ msgstr ""
 msgid "Timeout in seconds between retries in 'automatic' mode."
 msgstr ""
 
-msgid "To disable this feature set it to '0' which means unlimited retries."
-msgstr ""
-
 msgid "Travelmate"
 msgstr ""
 
 msgid "Travelmate Logfile"
 msgstr ""
 
-msgid "Travelmate Status"
+msgid "Travelmate Status (Quality)"
 msgstr ""
 
 msgid "Travelmate Version"
 msgstr ""
 
-msgid "Trigger delay"
+msgid "Trigger Delay"
 msgstr ""
 
 msgid "Unknown"
@@ -327,20 +350,8 @@ msgid ""
 "add it to the wan zone of the firewall. This step has only to be done once."
 msgstr ""
 
-msgid "connected"
-msgstr ""
-
-msgid "error"
-msgstr ""
-
 msgid "hidden"
 msgstr ""
 
 msgid "n/a"
 msgstr ""
-
-msgid "not connected"
-msgstr ""
-
-msgid "running"
-msgstr ""
index 1d30121..0773022 100644 (file)
@@ -12,8 +12,9 @@
 --
 -- Author: Nils Koenig <openwrt@newk.it>
 
-local fs = require "nixio.fs"
+local fs  = require "nixio.fs"
 local sys = require "luci.sys"
+local uci = require("luci.model.uci").cursor()
 
 function time_validator(self, value, desc)
     if value ~= nil then
@@ -110,7 +111,7 @@ modules.wrap = "off"
 modules.rows = 10
 
 function modules.cfgvalue(self, section)
-    mod = uci.get("wifi_schedule", section, "modules")
+    mod = uci:get("wifi_schedule", section, "modules")
     if mod == nil then
         mod = ""
     end
@@ -121,7 +122,7 @@ function modules.write(self, section, value)
     if value then
         value_list = value:gsub("\r\n", " ")
         ListValue.write(self, section, value_list)
-        uci.set("wifi_schedule", section, "modules", value_list)
+        uci:set("wifi_schedule", section, "modules", value_list)
     end
 end
 -- END Modules
index ec6a1be..d40dde0 100644 (file)
@@ -2,6 +2,7 @@
 -- Licensed to the public under the Apache License 2.0.
 
 local sys = require "luci.sys"
+local ipc = require "luci.ip"
 local fs  = require "nixio.fs"
 
 m = SimpleForm("wol", translate("Wake on LAN"),
@@ -58,7 +59,8 @@ end
 
 function host.write(self, s, val)
        local host = luci.http.formvalue("cbid.wol.1.mac")
-       if host and #host > 0 and host:match("^[a-fA-F0-9:]+$") then
+       local mac = ipc.checkmac(host)
+       if mac then
                local cmd
                local util = luci.http.formvalue("cbid.wol.1.binary") or (
                        has_ewk and "/usr/bin/etherwake" or "/usr/bin/wol"
@@ -69,10 +71,10 @@ function host.write(self, s, val)
                        local broadcast = luci.http.formvalue("cbid.wol.1.broadcast")
                        cmd = "%s -D%s %s %q" %{
                                util, (iface ~= "" and " -i %q" % iface or ""),
-                               (broadcast == "1" and " -b" or ""), host
+                               (broadcast == "1" and " -b" or ""), mac
                        }
                else
-                       cmd = "%s -v %q" %{ util, host }
+                       cmd = "%s -v %q" %{ util, mac }
                end
 
                local msg = "<p><strong>%s</strong><br /><br /><code>%s<br /><br />" %{
index 9bdb603..6a8f1f1 100644 (file)
@@ -1,7 +1,7 @@
 config 'community' 'profile'
        option 'name' 'Freifunk Potsdam'
        option 'homepage' 'http://potsdam.freifunk.net'
-       option 'ssid' 'Freifunk-Potsdam-XXX-YYY'
+       option 'ssid' 'freifunk-potsdam.de'
        option 'mesh_network' '10.22.0.0/16'
        option 'splash_network' '192.168.22.0/24'
        option 'splash_prefix' '24'
index 57063bb..e6e93ad 100644 (file)
@@ -341,7 +341,7 @@ Ltn12 source function
 Decode a mime encoded http message body with multipart/form-data 
  
 Content-Type. Stores all extracted data associated with its parameter name 
-in the params table within the given message object. Multiple parameter
+in the params table within the given message object. Multiple parameter 
 values are stored as tables, ordinary ones as strings. 
 If an optional file callback function is given then it is feeded with the 
 file contents chunk by chunk and only the extracted file name is stored 
@@ -556,7 +556,7 @@ The decoded string
 Decode an urlencoded http message body with application/x-www-urlencoded 
  
 Content-Type. Stores all extracted data associated with its parameter name 
-in the params table within the given message object. Multiple parameter
+in the params table within the given message object. Multiple parameter 
 values are stored as tables, ordinary ones as strings.
 
 
index ce8c567..5a2b06e 100644 (file)
@@ -260,6 +260,30 @@ Checks whether the CIDR instance is an IPv6 mapped IPv4 address
        </tr>
 
        <tr>
+       <td class="name" nowrap><a href="#cidr.ismac">cidr:ismac</a>&nbsp;()</td>
+       <td class="summary">
+Checks whether the CIDR instance is an ethernet MAC address range 
+ </td>
+       </tr>
+
+       <tr>
+       <td class="name" nowrap><a href="#cidr.ismaclocal">cidr:ismaclocal</a>&nbsp;()</td>
+       <td class="summary">
+Checks whether the CIDR instance is a locally administered (LAA) MAC address 
+ </td>
+       </tr>
+
+       <tr>
+       <td class="name" nowrap><a href="#cidr.ismacmcast">cidr:ismacmcast</a>&nbsp;()</td>
+       <td class="summary">
+Checks whether the CIDR instance is a multicast MAC address 
+ </td>
+       </tr>
+
+       <tr>
        <td class="name" nowrap><a href="#cidr.lower">cidr:lower</a>&nbsp;(addr)</td>
        <td class="summary">
  
@@ -323,6 +347,20 @@ Derive mapped IPv4 address of CIDR instance.</td>
        </tr>
 
        <tr>
+       <td class="name" nowrap><a href="#cidr.tomac">cidr:tomac</a>&nbsp;()</td>
+       <td class="summary">
+Derive MAC address of IPv6 link local CIDR instance.</td>
+       </tr>
+
+       <tr>
+       <td class="name" nowrap><a href="#cidr.tolinklocal">cidr:tolinklocal</a>&nbsp;()</td>
+       <td class="summary">
+Derive IPv6 link local address from MAC address CIDR instance.</td>
+       </tr>
+
+       <tr>
        <td class="name" nowrap><a href="#cidr.contains">cidr:contains</a>&nbsp;(addr)</td>
        <td class="summary">
  
@@ -405,6 +443,10 @@ Checks whether the CIDR instance is an IPv4 address range
                cidr:is6
        </a>
        
+       <li><a href="#cidr.ismac">
+               cidr:ismac
+       </a>
+       
 </ul>
 
 </dd>
@@ -499,6 +541,10 @@ Checks whether the CIDR instance is an IPv6 address range
                cidr:is4
        </a>
        
+       <li><a href="#cidr.ismac">
+               cidr:ismac
+       </a>
+       
 </ul>
 
 </dd>
@@ -566,13 +612,108 @@ end</pre>
 
 
 
+<dt><a name="cidr.ismac"></a><strong>cidr:ismac</strong>&nbsp;()</dt>
+<dd>
+
+Checks whether the CIDR instance is an ethernet MAC address range 
+
+
+
+
+
+
+
+
+<h3>Return value:</h3>
+<code>true</code> if the CIDR is a MAC address range, else <code>false</code>
+
+
+
+<h3>See also:</h3>
+<ul>
+       
+       <li><a href="#cidr.is4">
+               cidr:is4
+       </a>
+       
+       <li><a href="#cidr.is6">
+               cidr:is6
+       </a>
+       
+</ul>
+
+</dd>
+
+
+
+
+<dt><a name="cidr.ismaclocal"></a><strong>cidr:ismaclocal</strong>&nbsp;()</dt>
+<dd>
+
+Checks whether the CIDR instance is a locally administered (LAA) MAC address 
+
+
+
+
+
+
+<h3>Usage:</h3>
+<pre>local mac = luci.ip.new("02:C0:FF:EE:00:01") 
+if mac:ismaclocal() then 
+  print("Is an LAA MAC address") 
+end</pre>
+
+
+
+<h3>Return value:</h3>
+<code>true</code> if the MAC address sets the locally administered bit.
+
+
+
+</dd>
+
+
+
+
+<dt><a name="cidr.ismacmcast"></a><strong>cidr:ismacmcast</strong>&nbsp;()</dt>
+<dd>
+
+Checks whether the CIDR instance is a multicast MAC address 
+
+
+
+
+
+
+<h3>Usage:</h3>
+<pre>local mac = luci.ip.new("01:00:5E:7F:00:10") 
+if addr:ismacmcast() then 
+  print("Is a multicast MAC address") 
+end</pre>
+
+
+
+<h3>Return value:</h3>
+<code>true</code> if the MAC address sets the multicast bit.
+
+
+
+</dd>
+
+
+
+
 <dt><a name="cidr.lower"></a><strong>cidr:lower</strong>&nbsp;(addr)</dt>
 <dd>
 
  
 Checks whether this CIDR instance is lower than the given argument. 
 The comparisation follows these rules: 
-<ul><li>An IPv4 address is always lower than an IPv6 address</li> 
+<ul><li>An IPv4 address is always lower than an IPv6 address and IPv6 addresses 
+are considered lower than MAC addresses</li> 
 <li>Prefix sizes are ignored</li></ul> 
 
 
@@ -595,7 +736,8 @@ The comparisation follows these rules:
 print(addr:lower(addr)) -- false 
 print(addr:lower("10.10.10.10/24")) -- false 
 print(addr:lower(luci.ip.new("::1"))) -- true 
-print(addr:lower(luci.ip.new("192.168.200.1"))) -- true</pre>
+print(addr:lower(luci.ip.new("192.168.200.1"))) -- true 
+print(addr:lower(luci.ip.new("00:14:22:01:23:45"))) -- true</pre>
 
 
 
@@ -629,7 +771,8 @@ print(addr:lower(luci.ip.new("192.168.200.1"))) -- true</pre>
  
 Checks whether this CIDR instance is higher than the given argument. 
 The comparisation follows these rules: 
-<ul><li>An IPv4 address is always lower than an IPv6 address</li> 
+<ul><li>An IPv4 address is always lower than an IPv6 address and IPv6 addresses 
+are considered lower than MAC addresses</li> 
 <li>Prefix sizes are ignored</li></ul> 
 
 
@@ -652,7 +795,8 @@ The comparisation follows these rules:
 print(addr:higher(addr)) -- false 
 print(addr:higher("10.10.10.10/24")) -- true 
 print(addr:higher(luci.ip.new("::1"))) -- false 
-print(addr:higher(luci.ip.new("192.168.200.1"))) -- false</pre>
+print(addr:higher(luci.ip.new("192.168.200.1"))) -- false 
+print(addr:higher(luci.ip.new("00:14:22:01:23:45"))) -- false</pre>
 
 
 
@@ -709,7 +853,11 @@ print(addr:equal(luci.ip.new("::1"))) -- false
  
 local addr6 = luci.ip.new("::1") 
 print(addr6:equal("0:0:0:0:0:0:0:1/64")) -- true 
-print(addr6:equal(luci.ip.new("fe80::221:63ff:fe75:aa17"))) -- false</pre>
+print(addr6:equal(luci.ip.new("fe80::221:63ff:fe75:aa17"))) -- false 
+local mac = luci.ip.new("00:14:22:01:23:45") 
+print(mac:equal("0:14:22:1:23:45")) -- true 
+print(mac:equal(luci.ip.new("01:23:45:67:89:AB")) -- false</pre>
 
 
 
@@ -752,8 +900,8 @@ else the current prefix size is returned.
        
        <li>
          mask: Either a number containing the number of bits (<code>0..32</code> 
-       for IPv4, <code>0..128</code> for IPv6) or a string containing a valid 
-       netmask (optional)
+       for IPv4, <code>0..128</code> for IPv6 or <code>0..48</code> for MAC addresses) or a string 
+  containing a valid netmask (optional)
        </li>
        
 </ul>
@@ -800,8 +948,8 @@ optional mask parameter.
        
        <li>
          mask: Either a number containing the number of bits (<code>0..32</code> 
-       for IPv4, <code>0..128</code> for IPv6) or a string containing a valid 
-       netmask (optional)
+       for IPv4, <code>0..128</code> for IPv6 or <code>0..48</code> for MAC addresses) or a string 
+  containing a valid netmask (optional)
        </li>
        
 </ul>
@@ -837,7 +985,7 @@ CIDR instance representing the network address
 Derive host address of CIDR instance. 
  
 This function essentially constructs a copy of this CIDR with the prefix size 
-set to <code>32</code> for IPv4 and <code>128</code> for IPv6
+set to <code>32</code> for IPv4, <code>128</code> for IPv6 or <code>48</code> for MAC addresses
 
 
 
@@ -877,8 +1025,8 @@ prefix size can be overridden by the optional mask parameter.
        
        <li>
          mask: Either a number containing the number of bits (<code>0..32</code> 
-       for IPv4, <code>0..128</code> for IPv6) or a string containing a valid 
-       netmask (optional)
+       for IPv4, <code>0..128</code> for IPv6 or <code>0..48</code> for MAC addresses) or a string 
+  containing a valid netmask (optional)
        </li>
        
 </ul>
@@ -913,8 +1061,8 @@ Derive broadcast address of CIDR instance.
 Constructs a CIDR instance representing the broadcast address of this instance. 
 The used prefix size can be overridden by the optional mask parameter. 
  
-This function has no effect on IPv6 instances, it will return nothing in this 
-case. 
+This function has no effect on IPv6 or MAC address instances, it will return 
+nothing in this case. 
 
 
 
@@ -922,9 +1070,8 @@ case.
 <ul>
        
        <li>
-         mask: Either a number containing the number of bits (<code>0..32</code> 
-       for IPv4, <code>0..128</code> for IPv6) or a string containing a valid 
-       netmask (optional)
+         mask: Either a number containing the number of bits (<code>0..32</code> for IPv4) or 
+  a string containing a valid netmask (optional)
        </li>
        
 </ul>
@@ -960,8 +1107,8 @@ Derive mapped IPv4 address of CIDR instance.
 Constructs a CIDR instance representing the IPv4 address of the IPv6 mapped 
 IPv4 address in this instance. 
  
-This function has no effect on IPv4 instances or IPv6 instances which are not a 
-mapped address, it will return nothing in this case. 
+This function has no effect on IPv4 instances, MAC address instances or IPv6 
+instances which are not a mapped address, it will return nothing in this case. 
 
 
 
@@ -985,6 +1132,74 @@ Return a new CIDR instance representing the IPv4 address if this
 
 
 
+<dt><a name="cidr.tomac"></a><strong>cidr:tomac</strong>&nbsp;()</dt>
+<dd>
+
+Derive MAC address of IPv6 link local CIDR instance. 
+Constructs a CIDR instance representing the MAC address contained in the IPv6 
+link local address of this instance. 
+This function has no effect on IPv4 instances, MAC address instances or IPv6 
+instances which are not a link local address, it will return nothing in this 
+case. 
+
+
+
+
+
+
+<h3>Usage:</h3>
+<pre>local addr = luci.ip.new("fe80::6666:b3ff:fe47:e1b9") 
+print(addr:tomac()) -- "64:66:B3:47:E1:B9"</pre>
+
+
+
+<h3>Return value:</h3>
+Return a new CIDR instance representing the MAC address if this 
+  instance is an IPv6 link local address, else return nothing.
+
+
+
+</dd>
+
+
+
+
+<dt><a name="cidr.tolinklocal"></a><strong>cidr:tolinklocal</strong>&nbsp;()</dt>
+<dd>
+
+Derive IPv6 link local address from MAC address CIDR instance. 
+Constructs a CIDR instance representing the IPv6 link local address of the 
+MAC address represented by this instance. 
+This function has no effect on IPv4 instances or IPv6 instances, it will return 
+nothing in this case. 
+
+
+
+
+
+
+<h3>Usage:</h3>
+<pre>local mac = luci.ip.new("64:66:B3:47:E1:B9") 
+print(mac:tolinklocal()) -- "fe80::6666:b3ff:fe47:e1b9"</pre>
+
+
+
+<h3>Return value:</h3>
+Return a new CIDR instance representing the IPv6 link local address.
+
+
+
+</dd>
+
+
+
+
 <dt><a name="cidr.contains"></a><strong>cidr:contains</strong>&nbsp;(addr)</dt>
 <dd>
 
@@ -1014,7 +1229,11 @@ print(range:contains("10.0.0.0/8")) -- false
  
 local range6 = luci.ip.new("fe80::/10") 
 print(range6:contains("fe80::221:63f:fe75:aa17/64"))         -- true 
-print(range6:contains("fd9b:6b3:c5:0:221:63f:fe75:aa17/64")) -- false</pre>
+print(range6:contains("fd9b:6b3:c5:0:221:63f:fe75:aa17/64")) -- false 
+local intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/24") 
+print(intel_macs:contains("C0:B6:F9:A3:C:11"))  -- true 
+print(intel_macs:contains("64:66:B3:47:E1:B9")) -- false</pre>
 
 
 
@@ -1059,30 +1278,40 @@ address space, the result is set to the highest possible address.
 
 <h3>Usage:</h3>
 <pre>local addr = luci.ip.new("192.168.1.1/24") 
-print(addr:add(250))         -- "192.168.1.251/24" 
-print(addr:add("0.0.99.0"))  -- "192.168.100.1/24" 
+print(addr:add(250))           -- "192.168.1.251/24" 
+print(addr:add("0.0.99.0"))    -- "192.168.100.1/24" 
  
-addr:add(256, true)          -- true 
-print(addr)                  -- "192.168.2.1/24 
+addr:add(256, true)            -- true 
+print(addr)                    -- "192.168.2.1/24 
  
-addr:add("255.0.0.0", true)  -- false (overflow) 
-print(addr)                  -- "255.255.255.255/24 
+addr:add("255.0.0.0", true)    -- false (overflow) 
+print(addr)                    -- "255.255.255.255/24 
  
 local addr6 = luci.ip.new("fe80::221:63f:fe75:aa17/64") 
-print(addr6:add(256))        -- "fe80::221:63f:fe75:ab17/64" 
-print(addr6:add("::ffff:0")) -- "fe80::221:640:fe74:aa17/64" 
+print(addr6:add(256))          -- "fe80::221:63f:fe75:ab17/64" 
+print(addr6:add("::ffff:0"))   -- "fe80::221:640:fe74:aa17/64" 
+addr6:add(256, true)           -- true 
+print(addr6)                   -- "fe80::221:63f:fe75:ab17/64 
+addr6:add("ffff::", true)      -- false (overflow) 
+print(addr6)                   -- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64" 
+local mac = luci.ip.new("00:14:22:01:23:45") 
+print(mac:add(256))            -- "00:14:22:01:24:45" 
+print(mac:add("0:0:0:0:FF:0")  -- "00:14:22:02:22:45" 
  
-addr:add(256, true)          -- true 
-print(addr)                  -- "fe80::221:63f:fe75:ab17/64 
+mac:add(256, true)             -- true 
+print(mac)                     -- "00:14:22:01:24:45" 
  
-addr:add("ffff::", true)     -- false (overflow) 
-print(addr)                  -- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64"</pre>
+mac:add("FF:FF:0:0:0:0", true) -- false (overflow) 
+print(mac)                     -- "FF:FF:FF:FF:FF:FF"</pre>
 
 
 
 <h3>Return value:</h3>
 <ul> 
-       <li>When adding inplace: Return <code>true</code> if the addition succeeded
+       <li>When adding inplace: Return <code>true</code> if the addition succeded 
            or <code>false</code> when the addition overflowed.</li> 
        <li>When deriving new CIDR: Return new instance representing the value of 
         this instance plus the added amount or the highest possible address if 
@@ -1099,7 +1328,7 @@ print(addr)                  -- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64"</pr
 <dd>
 
  
-Subtract given amount from CIDR instance. If the result would under, the lowest
+Subtract given amount from CIDR instance. If the result would under, the lowest 
 possible address is returned. 
 
 
@@ -1142,16 +1371,26 @@ addr:sub(256, true)          -- true
 print(addr)                  -- "fe80::221:63f:fe75:a917/64" 
  
 addr:sub("ffff::", true)     -- false (underflow) 
-print(addr)                  -- "::/64"</pre>
+print(addr)                  -- "::/64" 
+local mac = luci.ip.new("00:14:22:01:23:45") 
+print(mac:sub(256))            -- "00:14:22:01:22:45" 
+print(mac:sub("0:0:0:0:FF:0")  -- "00:14:22:00:24:45" 
+mac:sub(256, true)             -- true 
+print(mac)                     -- "00:14:22:01:22:45" 
+mac:sub("FF:FF:0:0:0:0", true) -- false (overflow) 
+print(mac)                     -- "00:00:00:00:00:00"</pre>
 
 
 
 <h3>Return value:</h3>
 <ul> 
-       <li>When subtracting inplace: Return <code>true</code> if the subtraction
-           succeeded or <code>false</code> when the subtraction underflowed.</li>
+       <li>When subtracting inplace: Return <code>true</code> if the subtraction 
+           succeeded or <code>false</code> when the subtraction underflowed.</li> 
        <li>When deriving new CIDR: Return new instance representing the value of 
-        this instance minus the subtracted amount or the lowest address if
+        this instance minus the subtracted amount or the lowest address if 
            the subtraction underflowed.</li></ul>
 
 
@@ -1177,7 +1416,10 @@ Calculate the lowest possible host address within this CIDR instance.
 print(addr:minhost())  -- "192.168.123.1" 
  
 local addr6 = luci.ip.new("fd9b:62b3:9cc5:0:221:63ff:fe75:aa17/64") 
-print(addr6:minhost()) -- "fd9b:62b3:9cc5::1"</pre>
+print(addr6:minhost()) -- "fd9b:62b3:9cc5::1" 
+local mac = luci.ip.new("00:14:22:01:22:45/32") 
+print(mac:minhost())   -- "00:14:22:01:00:01"</pre>
 
 
 
@@ -1208,7 +1450,10 @@ Calculate the highest possible host address within this CIDR instance.
 print(addr:maxhost())  -- "192.168.123.254" (.255 is broadcast) 
  
 local addr6 = luci.ip.new("fd9b:62b3:9cc5:0:221:63ff:fe75:aa17/64") 
-print(addr6:maxhost()) -- "fd9b:62b3:9cc5:0:ffff:ffff:ffff:ffff"</pre>
+print(addr6:maxhost()) -- "fd9b:62b3:9cc5:0:ffff:ffff:ffff:ffff" 
+local mac = luci.ip.new("00:14:22:01:22:45/32") 
+print(mac:maxhost())   -- "00:14:22:01:FF:FF"</pre>
 
 
 
@@ -1229,8 +1474,9 @@ Returns a new CIDR instance representing the highest host address
  
 Convert CIDR instance into string representation. 
  
-If the prefix size of instance is less than 32 for IPv4 or 128 for IPv6, the 
-address is returned in the form "address/prefix" otherwise just "address". 
+If the prefix size of instance is less than 32 for IPv4, 128 for IPv6 or 48 for 
+MACs, the address is returned in the form "address/prefix" otherwise just 
+"address". 
  
 It is usually not required to call this function directly as CIDR objects 
 define it as __tostring function in the associated metatable. 
index 549a553..1f89626 100644 (file)
@@ -232,6 +232,34 @@ Construct a new IPv6 luci.ip.cidr instance.</td>
        </tr>
 
        <tr>
+       <td class="name" nowrap><a href="#MAC">MAC</a>&nbsp;(address, netmask)</td>
+       <td class="summary">
+Construct a new MAC luci.ip.cidr instance.</td>
+       </tr>
+
+       <tr>
+       <td class="name" nowrap><a href="#checkip4">checkip4</a>&nbsp;(address)</td>
+       <td class="summary">
+Verify an IPv4 address.</td>
+       </tr>
+
+       <tr>
+       <td class="name" nowrap><a href="#checkip6">checkip6</a>&nbsp;(address)</td>
+       <td class="summary">
+Verify an IPv6 address.</td>
+       </tr>
+
+       <tr>
+       <td class="name" nowrap><a href="#checkmac">checkmac</a>&nbsp;(address)</td>
+       <td class="summary">
+Verify an ethernet MAC address.</td>
+       </tr>
+
+       <tr>
        <td class="name" nowrap><a href="#route">route</a>&nbsp;(address)</td>
        <td class="summary">
  
@@ -334,6 +362,10 @@ address/mask range.
                IPv6
        </a>
        
+       <li><a href="#MAC">
+               MAC
+       </a>
+       
 </ul>
 
 </dd>
@@ -389,6 +421,10 @@ A <code>luci.ip.cidr</code> object representing the given IPv4 range.
                IPv6
        </a>
        
+       <li><a href="#MAC">
+               MAC
+       </a>
+       
 </ul>
 
 </dd>
@@ -444,6 +480,252 @@ A <code>luci.ip.cidr</code> object representing the given IPv6 range.
                IPv4
        </a>
        
+       <li><a href="#MAC">
+               MAC
+       </a>
+       
+</ul>
+
+</dd>
+
+
+
+
+<dt><a name="MAC"></a><strong>MAC</strong>&nbsp;(address, netmask)</dt>
+<dd>
+
+Construct a new MAC luci.ip.cidr instance. 
+Throws an error if the given string does not represent a valid ethernet MAC 
+address or if the given optional mask is of a different family.
+
+
+<h3>Parameters</h3>
+<ul>
+       
+       <li>
+         address: String containing a valid ethernet MAC address, optionally with 
+prefix size (CIDR notation) or mask separated by slash.
+       </li>
+       
+       <li>
+         netmask: String containing a valid MAC address mask or number 
+containing a prefix size between <code>0</code> and <code>48</code> bit. 
+Overrides mask embedded in the first argument if specified. (optional)
+       </li>
+       
+</ul>
+
+
+
+
+<h3>Usage:</h3>
+<pre>intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/24") 
+intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/FF:FF:FF:0:0:0") 
+intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00", "FF:FF:FF:0:0:0") 
+intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/24", 48) -- override mask</pre>
+
+
+
+<h3>Return value:</h3>
+A <code>luci.ip.cidr</code> object representing the given MAC address range.
+
+
+
+<h3>See also:</h3>
+<ul>
+       
+       <li><a href="#IPv4">
+               IPv4
+       </a>
+       
+       <li><a href="#IPv6">
+               IPv6
+       </a>
+       
+</ul>
+
+</dd>
+
+
+
+
+<dt><a name="checkip4"></a><strong>checkip4</strong>&nbsp;(address)</dt>
+<dd>
+
+Verify an IPv4 address. 
+Checks whether given argument is a preexisting luci.ip.cidr IPv4 address 
+instance or a string literal convertible to an IPv4 address and returns a 
+plain Lua string containing the canonical representation of the address. 
+If the argument is not a valid address, returns nothing. This function is 
+intended to aid in safely verifying address literals without having to deal 
+with exceptions.
+
+
+<h3>Parameters</h3>
+<ul>
+       
+       <li>
+         address: String containing a valid IPv4 address or existing 
+luci.ip.cidr IPv4 instance.
+       </li>
+       
+</ul>
+
+
+
+
+<h3>Usage:</h3>
+<pre>ipv4 = luci.ip.checkip4(luci.ip.new("127.0.0.1"))  -- "127.0.0.1" 
+ipv4 = luci.ip.checkip4("127.0.0.1")               -- "127.0.0.1" 
+ipv4 = luci.ip.checkip4("nonesense")               -- nothing 
+ipv4 = luci.ip.checkip4(123)                       -- nothing 
+ipv4 = luci.ip.checkip4(nil)                       -- nothing 
+ipv4 = luci.ip.checkip4()                          -- nothing</pre>
+
+
+
+<h3>Return value:</h3>
+A string representing the given IPv4 address.
+
+
+
+<h3>See also:</h3>
+<ul>
+       
+       <li><a href="#checkip6">
+               checkip6
+       </a>
+       
+       <li><a href="#checkmac">
+               checkmac
+       </a>
+       
+</ul>
+
+</dd>
+
+
+
+
+<dt><a name="checkip6"></a><strong>checkip6</strong>&nbsp;(address)</dt>
+<dd>
+
+Verify an IPv6 address. 
+Checks whether given argument is a preexisting luci.ip.cidr IPv6 address 
+instance or a string literal convertible to an IPv6 address and returns a 
+plain Lua string containing the canonical representation of the address. 
+If the argument is not a valid address, returns nothing. This function is 
+intended to aid in safely verifying address literals without having to deal 
+with exceptions.
+
+
+<h3>Parameters</h3>
+<ul>
+       
+       <li>
+         address: String containing a valid IPv6 address or existing 
+luci.ip.cidr IPv6 instance.
+       </li>
+       
+</ul>
+
+
+
+
+<h3>Usage:</h3>
+<pre>ipv6 = luci.ip.checkip6(luci.ip.new("0:0:0:0:0:0:0:1"))  -- "::1" 
+ipv6 = luci.ip.checkip6("0:0:0:0:0:0:0:1")               -- "::1" 
+ipv6 = luci.ip.checkip6("nonesense")                     -- nothing 
+ipv6 = luci.ip.checkip6(123)                             -- nothing 
+ipv6 = luci.ip.checkip6(nil)                             -- nothing 
+ipv6 = luci.ip.checkip6()                                -- nothing</pre>
+
+
+
+<h3>Return value:</h3>
+A string representing the given IPv6 address.
+
+
+
+<h3>See also:</h3>
+<ul>
+       
+       <li><a href="#checkip4">
+               checkip4
+       </a>
+       
+       <li><a href="#checkmac">
+               checkmac
+       </a>
+       
+</ul>
+
+</dd>
+
+
+
+
+<dt><a name="checkmac"></a><strong>checkmac</strong>&nbsp;(address)</dt>
+<dd>
+
+Verify an ethernet MAC address. 
+Checks whether given argument is a preexisting luci.ip.cidr MAC address 
+instance or a string literal convertible to an ethernet MAC and returns a 
+plain Lua string containing the canonical representation of the address. 
+If the argument is not a valid address, returns nothing. This function is 
+intended to aid in safely verifying address literals without having to deal 
+with exceptions.
+
+
+<h3>Parameters</h3>
+<ul>
+       
+       <li>
+         address: String containing a valid MAC address or existing luci.ip.cidr 
+MAC address instance.
+       </li>
+       
+</ul>
+
+
+
+
+<h3>Usage:</h3>
+<pre>mac = luci.ip.checkmac(luci.ip.new("00-11-22-cc-dd-ee"))  -- "00:11:22:CC:DD:EE" 
+mac = luci.ip.checkmac("00:11:22:cc:dd:ee")               -- "00:11:22:CC:DD:EE" 
+mac = luci.ip.checkmac("nonesense")                       -- nothing 
+mac = luci.ip.checkmac(123)                               -- nothing 
+mac = luci.ip.checkmac(nil)                               -- nothing 
+mac = luci.ip.checkmac()                                  -- nothing</pre>
+
+
+
+<h3>Return value:</h3>
+A string representing the given MAC address.
+
+
+
+<h3>See also:</h3>
+<ul>
+       
+       <li><a href="#checkip4">
+               checkip4
+       </a>
+       
+       <li><a href="#checkip6">
+               checkip6
+       </a>
+       
 </ul>
 
 </dd>
@@ -787,7 +1069,7 @@ A neighbour entry is a table containing the following fields:
 </tr> 
 <tr> 
   <td><code>mac</code></td> 
-  <td>String containing the associated MAC address</td> 
+  <td>MAC address <code>luci.ip.cidr</code> instance</td> 
 </tr> 
 <tr> 
   <td><code>router</code></td> 
@@ -905,8 +1187,8 @@ described below is returned, else an empty table.
 </tr> 
 <tr> 
   <td><code>mac</code></td> 
-  <td>String containing the link local address of the device in 
-      dotted hex notation</td> 
+  <td>MAC address <code>luci.ip.cidr</code> instance representing the device ethernet 
+      address</td> 
 </tr> 
 </table>
 
index 847230b..70d27d3 100644 (file)
@@ -629,7 +629,7 @@ Cloned table value
 <dd>
 
  
-Count the occurrences of given substring in given string.
+Count the occurrences of given substring in given string. 
 
 
 
index d8a17f7..99e036b 100644 (file)
  <br />In general all functions are namend and behave like their POSIX API 
  counterparts - where applicable - applying the following rules: 
  <ul> 
- <li>Functions should be named like the underlying POSIX API function omitting
+ <li>Functions should be named like the underlying POSIX API function omitting 
  prefixes or suffixes - especially when placed in an object-context ( 
  lockf -> File:lock, fsync -> File:sync, dup2 -> dup, ...)</li> 
  <li>If you are unclear about the behaviour of a function you should consult 
  <li>If the name is significantly different from the POSIX-function, the 
  underlying function(s) are stated in the documentation.</li> 
  <li>Parameters should reflect those of the C-API, buffer length arguments and  
- by-reference parameters should be omitted for pratical purposes.</li>
+ by-reference parameters should be omitted for practical purposes.</li> 
  <li>If a C function accepts a bitfield as parameter, it should be translated 
  into lower case string flags representing the flags if the bitfield is the  
- last parameter and also omitting prefixes or suffixes. (e.g.  waitpid
+ last parameter and also omitting prefixes or suffixes. (e.g.  waitpid 
  (pid, &s, WNOHANG | WUNTRACED) -> waitpid(pid, "nohang", "untraced"),  
  getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) -> 
  Socket:getopt("socket", "reuseaddr"), etc.) </li> 
index 1717980..700485d 100644 (file)
        <li>The blocksize given is only advisory and to be seen as an upper limit, 
  if an underlying read returns less bytes the chunk is nevertheless returned.
        
-       <li>If the limit parameter is omitted, the iterator returns data
+       <li>If the limit parameter is omitted, the iterator returns data 
  until an end-of-file, end-of-stream, connection shutdown or similar happens.
        
        <li>The iterator will not buffer so it is safe to mix with calls to read.
@@ -402,7 +402,7 @@ true
        <li>This function uses the blocksource function of the source descriptor 
  and the sink function of the target descriptor.
        
-       <li>If the limit parameter is omitted, data is copied
+       <li>If the limit parameter is omitted, data is copied 
  until an end-of-file, end-of-stream, connection shutdown or similar happens.
        
        <li>If the descriptor is non-blocking the function may fail with EAGAIN.
@@ -461,7 +461,7 @@ true
  blocksource function of the source descriptor and the sink function 
  of the target descriptor as a fallback mechanism.
        
-       <li>If the limit parameter is omitted, data is copied
+       <li>If the limit parameter is omitted, data is copied 
  until an end-of-file, end-of-stream, connection shutdown or similar happens.
        
        <li>If the descriptor is non-blocking the function may fail with EAGAIN.
@@ -584,7 +584,7 @@ boolean
  you can pass "true" to the iterator which will flush the buffer  
  and return the bufferd data.
        
-       <li>If the limit parameter is omitted, this function uses the nixio
+       <li>If the limit parameter is omitted, this function uses the nixio 
  buffersize (8192B by default).
        
        <li>If the descriptor is non-blocking the iterator may fail with EAGAIN.
@@ -628,7 +628,7 @@ Line-based Iterator
        
        <li>This function uses the low-level read function of the descriptor.
        
-       <li>If the length parameter is omitted, this function returns all data
+       <li>If the length parameter is omitted, this function returns all data 
  that can be read before an end-of-file, end-of-stream, connection shutdown 
  or similar happens.
        
index b91966c..854a0c0 100644 (file)
@@ -1,5 +1,5 @@
 /*
-Copyright 2015 Jo-Philipp Wich <jow@openwrt.org>
+Copyright 2015-2018 Jo-Philipp Wich <jo@mein.io>
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
@@ -42,6 +42,16 @@ limitations under the License.
 #define RTA_INT(x)     (*(int *)RTA_DATA(x))
 #define RTA_U32(x)     (*(uint32_t *)RTA_DATA(x))
 
+#define AF_BITS(f) \
+       ((f) == AF_INET ? 32 : \
+               ((f) == AF_INET6 ? 128 : \
+                       ((f) == AF_PACKET ? 48 : 0)))
+
+#define AF_BYTES(f) \
+       ((f) == AF_INET ? 4 : \
+               ((f) == AF_INET6 ? 16 : \
+                       ((f) == AF_PACKET ? 6 : 0)))
+
 static int hz = 0;
 static struct nl_sock *sock = NULL;
 
@@ -49,11 +59,11 @@ typedef struct {
        union {
                struct in_addr v4;
                struct in6_addr v6;
+               struct ether_addr mac;
+               uint8_t u8[16];
        } addr;
-       int len;
-       int bits;
-       int family;
-       bool exact;
+       uint16_t family;
+       int16_t bits;
 } cidr_t;
 
 struct dump_filter {
@@ -70,6 +80,8 @@ struct dump_filter {
        cidr_t src;
        cidr_t dst;
        struct ether_addr mac;
+       bool from_exact;
+       bool dst_exact;
 };
 
 struct dump_state {
@@ -95,29 +107,68 @@ static cidr_t *L_checkcidr (lua_State *L, int index, cidr_t *p)
        return NULL;
 }
 
-static bool parse_mask(int family, const char *mask, int *bits)
+static bool parse_mac(const char *mac, struct ether_addr *ea)
+{
+       unsigned long int n;
+       char *e, sep = 0;
+       int i;
+
+       for (i = 0; i < 6; i++)
+       {
+               if (i > 0)
+               {
+                       if (sep == 0 && (mac[0] == ':' || mac[0] == '-'))
+                               sep = mac[0];
+
+                       if (sep == 0 || mac[0] != sep)
+                               return false;
+
+                       mac++;
+               }
+
+               n = strtoul(mac, &e, 16);
+
+               if (n > 0xFF)
+                       return false;
+
+               mac += (e - mac);
+               ea->ether_addr_octet[i] = n;
+       }
+
+       if (mac[0] != 0)
+               return false;
+
+       return true;
+}
+
+static bool parse_mask(int family, const char *mask, int16_t *bits)
 {
        char *e;
-       struct in_addr m;
-       struct in6_addr m6;
+       union {
+               struct in_addr v4;
+               struct in6_addr v6;
+               struct ether_addr mac;
+               uint8_t u8[16];
+       } m;
 
-       if (family == AF_INET && inet_pton(AF_INET, mask, &m))
+       if (family == AF_INET && inet_pton(AF_INET, mask, &m.v4))
        {
-               for (*bits = 0, m.s_addr = ntohl(m.s_addr);
-                        *bits < 32 && (m.s_addr << *bits) & 0x80000000;
+               for (*bits = 0, m.v4.s_addr = ntohl(m.v4.s_addr);
+                        *bits < AF_BITS(AF_INET) && (m.v4.s_addr << *bits) & 0x80000000;
                         ++*bits);
        }
-       else if (family == AF_INET6 && inet_pton(AF_INET6, mask, &m6))
+       else if ((family == AF_INET6 && inet_pton(AF_INET6, mask, &m.v6)) ||
+                (family == AF_PACKET && parse_mac(mask, &m.mac)))
        {
                for (*bits = 0;
-                        *bits < 128 && (m6.s6_addr[*bits / 8] << (*bits % 8)) & 128;
+                        *bits < AF_BITS(family) && (m.u8[*bits / 8] << (*bits % 8)) & 128;
                         ++*bits);
        }
        else
        {
                *bits = strtoul(mask, &e, 10);
 
-               if (e == mask || *e != 0 || *bits > ((family == AF_INET) ? 32 : 128))
+               if (e == mask || *e != 0 || *bits > AF_BITS(family))
                        return false;
        }
 
@@ -127,7 +178,6 @@ static bool parse_mask(int family, const char *mask, int *bits)
 static bool parse_cidr(const char *dest, cidr_t *pp)
 {
        char *p, buf[INET6_ADDRSTRLEN * 2 + 2];
-       uint8_t bitlen = 0;
 
        strncpy(buf, dest, sizeof(buf) - 1);
 
@@ -137,17 +187,11 @@ static bool parse_cidr(const char *dest, cidr_t *pp)
                *p++ = 0;
 
        if (inet_pton(AF_INET, buf, &pp->addr.v4))
-       {
-               bitlen = 32;
                pp->family = AF_INET;
-               pp->len = sizeof(struct in_addr);
-       }
        else if (inet_pton(AF_INET6, buf, &pp->addr.v6))
-       {
-               bitlen = 128;
                pp->family = AF_INET6;
-               pp->len = sizeof(struct in6_addr);
-       }
+       else if (parse_mac(buf, &pp->addr.mac))
+               pp->family = AF_PACKET;
        else
                return false;
 
@@ -158,12 +202,45 @@ static bool parse_cidr(const char *dest, cidr_t *pp)
        }
        else
        {
-               pp->bits = bitlen;
+               pp->bits = AF_BITS(pp->family);
        }
 
        return true;
 }
 
+static int format_cidr(lua_State *L, cidr_t *p)
+{
+       char buf[INET6_ADDRSTRLEN];
+
+       if (p->family == AF_PACKET)
+       {
+               snprintf(buf, sizeof(buf), "%02X:%02X:%02X:%02X:%02X:%02X",
+                        p->addr.mac.ether_addr_octet[0],
+                        p->addr.mac.ether_addr_octet[1],
+                        p->addr.mac.ether_addr_octet[2],
+                        p->addr.mac.ether_addr_octet[3],
+                        p->addr.mac.ether_addr_octet[4],
+                        p->addr.mac.ether_addr_octet[5]);
+
+               if (p->bits < AF_BITS(AF_PACKET))
+                       lua_pushfstring(L, "%s/%d", buf, p->bits);
+               else
+                       lua_pushstring(L, buf);
+       }
+       else
+       {
+               if (p->bits < AF_BITS(p->family))
+                       lua_pushfstring(L, "%s/%d",
+                                       inet_ntop(p->family, &p->addr.v6, buf, sizeof(buf)),
+                                       p->bits);
+               else
+                       lua_pushstring(L,
+                                      inet_ntop(p->family, &p->addr.v6, buf, sizeof(buf)));
+       }
+
+       return 1;
+}
+
 static int L_getint(lua_State *L, int index, const char *name)
 {
        int rv = 0;
@@ -220,17 +297,21 @@ static void L_setaddr(struct lua_State *L, const char *name,
        if (family == AF_INET)
        {
                p->family = AF_INET;
-               p->bits = (bits < 0) ? 32 : bits;
-               p->len = sizeof(p->addr.v4);
+               p->bits = (bits < 0) ? AF_BITS(AF_INET) : bits;
                p->addr.v4 = *(struct in_addr *)addr;
        }
-       else
+       else if (family == AF_INET6)
        {
                p->family = AF_INET6;
-               p->bits = (bits < 0) ? 128 : bits;
-               p->len = sizeof(p->addr.v6);
+               p->bits = (bits < 0) ? AF_BITS(AF_INET6) : bits;
                p->addr.v6 = *(struct in6_addr *)addr;
        }
+       else
+       {
+               p->family = AF_PACKET;
+               p->bits = (bits < 0) ? AF_BITS(AF_PACKET) : bits;
+               p->addr.mac = *(struct ether_addr *)addr;
+       }
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
        lua_setmetatable(L, -2);
@@ -254,6 +335,7 @@ static void L_setdev(struct lua_State *L, const char *name,
 
 static int L_checkbits(lua_State *L, int index, cidr_t *p)
 {
+       int16_t s16;
        int bits;
 
        if (lua_gettop(L) < index || lua_isnil(L, index))
@@ -264,13 +346,15 @@ static int L_checkbits(lua_State *L, int index, cidr_t *p)
        {
                bits = lua_tointeger(L, index);
 
-               if (bits < 0 || bits > ((p->family == AF_INET) ? 32 : 128))
+               if (bits < 0 || bits > AF_BITS(p->family))
                        return luaL_error(L, "Invalid prefix size");
        }
        else if (lua_type(L, index) == LUA_TSTRING)
        {
-               if (!parse_mask(p->family, lua_tostring(L, index), &bits))
+               if (!parse_mask(p->family, lua_tostring(L, index), &s16))
                        return luaL_error(L, "Invalid netmask format");
+
+               bits = s16;
        }
        else
        {
@@ -293,20 +377,26 @@ static int _cidr_new(lua_State *L, int index, int family, bool mask)
                if (family == AF_INET6)
                {
                        cidr.family = AF_INET6;
-                       cidr.bits = 128;
-                       cidr.len = sizeof(cidr.addr.v6);
                        cidr.addr.v6.s6_addr[12] = n;
                        cidr.addr.v6.s6_addr[13] = (n >> 8);
                        cidr.addr.v6.s6_addr[14] = (n >> 16);
                        cidr.addr.v6.s6_addr[15] = (n >> 24);
                }
-               else
+               else if (family == AF_INET)
                {
                        cidr.family = AF_INET;
-                       cidr.bits = 32;
-                       cidr.len = sizeof(cidr.addr.v4);
                        cidr.addr.v4.s_addr = n;
                }
+               else
+               {
+                       cidr.family = AF_PACKET;
+                       cidr.addr.mac.ether_addr_octet[2] = n;
+                       cidr.addr.mac.ether_addr_octet[3] = (n >> 8);
+                       cidr.addr.mac.ether_addr_octet[4] = (n >> 16);
+                       cidr.addr.mac.ether_addr_octet[5] = (n >> 24);
+               }
+
+               cidr.bits = AF_BITS(cidr.family);
        }
        else
        {
@@ -346,6 +436,62 @@ static int cidr_ipv6(lua_State *L)
        return _cidr_new(L, 1, AF_INET6, true);
 }
 
+static int cidr_mac(lua_State *L)
+{
+       return _cidr_new(L, 1, AF_PACKET, true);
+}
+
+static int cidr_check(lua_State *L, int family)
+{
+       cidr_t cidr = { }, *cidrp;
+       const char *addr;
+
+       if (lua_type(L, 1) == LUA_TSTRING)
+       {
+               addr = lua_tostring(L, 1);
+
+               if (addr && parse_cidr(addr, &cidr) && cidr.family == family)
+                       return format_cidr(L, &cidr);
+       }
+       else
+       {
+               cidrp = lua_touserdata(L, 1);
+
+               if (cidrp == NULL)
+                       return 0;
+
+               if (!lua_getmetatable(L, 1))
+                       return 0;
+
+               lua_getfield(L, LUA_REGISTRYINDEX, LUCI_IP_CIDR);
+
+               if (!lua_rawequal(L, -1, -2))
+                       cidrp = NULL;
+
+               lua_pop(L, 2);
+
+               if (cidrp != NULL && cidrp->family == family)
+                       return format_cidr(L, cidrp);
+       }
+
+       return 0;
+}
+
+static int cidr_checkip4(lua_State *L)
+{
+       return cidr_check(L, AF_INET);
+}
+
+static int cidr_checkip6(lua_State *L)
+{
+       return cidr_check(L, AF_INET6);
+}
+
+static int cidr_checkmac(lua_State *L)
+{
+       return cidr_check(L, AF_PACKET);
+}
+
 static int cidr_is4(lua_State *L)
 {
        cidr_t *p = L_checkcidr(L, 1, NULL);
@@ -424,6 +570,34 @@ static int cidr_is6linklocal(lua_State *L)
        return 1;
 }
 
+static int cidr_ismac(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, p->family == AF_PACKET);
+       return 1;
+}
+
+static int cidr_ismacmcast(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, (p->family == AF_PACKET &&
+                           (p->addr.mac.ether_addr_octet[0] & 0x1)));
+
+       return 1;
+}
+
+static int cidr_ismaclocal(lua_State *L)
+{
+       cidr_t *p = L_checkcidr(L, 1, NULL);
+
+       lua_pushboolean(L, (p->family == AF_PACKET &&
+                           (p->addr.mac.ether_addr_octet[0] & 0x2)));
+
+       return 1;
+}
+
 static int _cidr_cmp(lua_State *L)
 {
        cidr_t *a = L_checkcidr(L, 1, NULL);
@@ -432,7 +606,7 @@ static int _cidr_cmp(lua_State *L)
        if (a->family != b->family)
                return (a->family - b->family);
 
-       return memcmp(&a->addr.v6, &b->addr.v6, a->len);
+       return memcmp(&a->addr.v6, &b->addr.v6, AF_BYTES(a->family));
 }
 
 static int cidr_lower(lua_State *L)
@@ -475,24 +649,24 @@ static void _apply_mask(cidr_t *p, int bits, bool inv)
 
        if (bits <= 0)
        {
-               memset(&p->addr.v6, inv * 0xFF, p->len);
+               memset(&p->addr.u8, inv * 0xFF, AF_BYTES(p->family));
        }
-       else if (p->family == AF_INET && bits <= 32)
+       else if (p->family == AF_INET && bits <= AF_BITS(AF_INET))
        {
                if (inv)
-                       p->addr.v4.s_addr |= ntohl((1 << (32 - bits)) - 1);
+                       p->addr.v4.s_addr |= ntohl((1 << (AF_BITS(AF_INET) - bits)) - 1);
                else
-                       p->addr.v4.s_addr &= ntohl(~((1 << (32 - bits)) - 1));
+                       p->addr.v4.s_addr &= ntohl(~((1 << (AF_BITS(AF_INET) - bits)) - 1));
        }
-       else if (p->family == AF_INET6 && bits <= 128)
+       else if (bits <= AF_BITS(p->family))
        {
-               for (i = 0; i < sizeof(p->addr.v6.s6_addr); i++)
+               for (i = 0; i < AF_BYTES(p->family); i++)
                {
                        b = (bits > 8) ? 8 : bits;
                        if (inv)
-                               p->addr.v6.s6_addr[i] |= ~((uint8_t)(0xFF << (8 - b)));
+                               p->addr.u8[i] |= ~((uint8_t)(0xFF << (8 - b)));
                        else
-                               p->addr.v6.s6_addr[i] &= (uint8_t)(0xFF << (8 - b));
+                               p->addr.u8[i] &= (uint8_t)(0xFF << (8 - b));
                        bits -= b;
                }
        }
@@ -507,7 +681,7 @@ static int cidr_network(lua_State *L)
                return 0;
 
        *p2 = *p1;
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(p1->family);
        _apply_mask(p2, bits, false);
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
@@ -524,7 +698,7 @@ static int cidr_host(lua_State *L)
                return 0;
 
        *p2 = *p1;
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(p1->family);
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
        lua_setmetatable(L, -2);
@@ -539,7 +713,7 @@ static int cidr_mask(lua_State *L)
        if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
                return 0;
 
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(p1->family);
        p2->family = p1->family;
 
        memset(&p2->addr.v6.s6_addr, 0xFF, sizeof(p2->addr.v6.s6_addr));
@@ -556,14 +730,14 @@ static int cidr_broadcast(lua_State *L)
        cidr_t *p2;
        int bits = L_checkbits(L, 2, p1);
 
-       if (p1->family == AF_INET6)
+       if (p1->family != AF_INET)
                return 0;
 
        if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
                return 0;
 
        *p2 = *p1;
-       p2->bits = (p1->family == AF_INET) ? 32 : 128;
+       p2->bits = AF_BITS(AF_INET);
        _apply_mask(p2, bits, true);
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
@@ -583,7 +757,7 @@ static int cidr_mapped4(lua_State *L)
                return 0;
 
        p2->family = AF_INET;
-       p2->bits = (p1->bits > 32) ? 32 : p1->bits;
+       p2->bits = (p1->bits > AF_BITS(AF_INET)) ? AF_BITS(AF_INET) : p1->bits;
        memcpy(&p2->addr.v4, p1->addr.v6.s6_addr + 12, sizeof(p2->addr.v4));
 
        luaL_getmetatable(L, LUCI_IP_CIDR);
@@ -591,6 +765,72 @@ static int cidr_mapped4(lua_State *L)
        return 1;
 }
 
+static int cidr_tolinklocal(lua_State *L)
+{
+       cidr_t *p1 = L_checkcidr(L, 1, NULL);
+       cidr_t *p2;
+       int i;
+
+       if (p1->family != AF_PACKET)
+               return 0;
+
+       if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
+               return 0;
+
+       p2->family = AF_INET6;
+       p2->bits = AF_BITS(AF_INET6);
+       p2->addr.u8[0] = 0xFE;
+       p2->addr.u8[1] = 0x80;
+       p2->addr.u8[8] = p1->addr.u8[0] ^ 0x02;
+       p2->addr.u8[9] = p1->addr.u8[1];
+       p2->addr.u8[10] = p1->addr.u8[2];
+       p2->addr.u8[11] = 0xFF;
+       p2->addr.u8[12] = 0xFE;
+       p2->addr.u8[13] = p1->addr.u8[3];
+       p2->addr.u8[14] = p1->addr.u8[4];
+       p2->addr.u8[15] = p1->addr.u8[5];
+
+       luaL_getmetatable(L, LUCI_IP_CIDR);
+       lua_setmetatable(L, -2);
+       return 1;
+}
+
+static int cidr_tomac(lua_State *L)
+{
+       cidr_t *p1 = L_checkcidr(L, 1, NULL);
+       cidr_t *p2;
+       int i;
+
+       if (p1->family != AF_INET6 ||
+           p1->addr.u8[0] != 0xFE ||
+           p1->addr.u8[1] != 0x80 ||
+           p1->addr.u8[2] != 0x00 ||
+           p1->addr.u8[3] != 0x00 ||
+           p1->addr.u8[4] != 0x00 ||
+           p1->addr.u8[5] != 0x00 ||
+           p1->addr.u8[6] != 0x00 ||
+           p1->addr.u8[7] != 0x00 ||
+           p1->addr.u8[11] != 0xFF ||
+           p1->addr.u8[12] != 0xFE)
+           return 0;
+
+       if (!(p2 = lua_newuserdata(L, sizeof(*p2))))
+               return 0;
+
+       p2->family = AF_PACKET;
+       p2->bits = AF_BITS(AF_PACKET);
+       p2->addr.u8[0] = p1->addr.u8[8] ^ 0x02;
+       p2->addr.u8[1] = p1->addr.u8[9];
+       p2->addr.u8[2] = p1->addr.u8[10];
+       p2->addr.u8[3] = p1->addr.u8[13];
+       p2->addr.u8[4] = p1->addr.u8[14];
+       p2->addr.u8[5] = p1->addr.u8[15];
+
+       luaL_getmetatable(L, LUCI_IP_CIDR);
+       lua_setmetatable(L, -2);
+       return 1;
+}
+
 static int cidr_contains(lua_State *L)
 {
        cidr_t *p1 = L_checkcidr(L, 1, NULL);
@@ -603,15 +843,15 @@ static int cidr_contains(lua_State *L)
                _apply_mask(&a, p1->bits, false);
                _apply_mask(&b, p1->bits, false);
 
-               rv = !memcmp(&a.addr.v6, &b.addr.v6, a.len);
+               rv = !memcmp(&a.addr.v6, &b.addr.v6, AF_BYTES(a.family));
        }
 
        lua_pushboolean(L, rv);
        return 1;
 }
 
-#define S6_BYTE(a, i) \
-       (a)->addr.v6.s6_addr[sizeof((a)->addr.v6.s6_addr) - (i) - 1]
+#define BYTE(a, i) \
+       (a)->addr.u8[AF_BYTES((a)->family) - (i) - 1]
 
 static int _cidr_add_sub(lua_State *L, bool add)
 {
@@ -625,45 +865,45 @@ static int _cidr_add_sub(lua_State *L, bool add)
 
        if (p1->family == p2->family)
        {
-               if (p1->family == AF_INET6)
+               if (p1->family == AF_INET)
+               {
+                       a = ntohl(p1->addr.v4.s_addr);
+                       b = ntohl(p2->addr.v4.s_addr);
+
+                       /* would over/underflow */
+                       if ((add && (UINT_MAX - a) < b) || (!add && a < b))
+                       {
+                               r.addr.v4.s_addr = add * 0xFFFFFFFF;
+                               ok = false;
+                       }
+                       else
+                       {
+                               r.addr.v4.s_addr = add ? htonl(a + b) : htonl(a - b);
+                       }
+               }
+               else
                {
-                       for (i = 0, carry = 0; i < sizeof(r.addr.v6.s6_addr); i++)
+                       for (i = 0, carry = 0; i < AF_BYTES(p1->family); i++)
                        {
                                if (add)
                                {
-                                       S6_BYTE(&r, i) = S6_BYTE(p1, i) + S6_BYTE(p2, i) + carry;
-                                       carry = (S6_BYTE(p1, i) + S6_BYTE(p2, i) + carry) / 256;
+                                       BYTE(&r, i) = BYTE(p1, i) + BYTE(p2, i) + carry;
+                                       carry = (BYTE(p1, i) + BYTE(p2, i) + carry) / 256;
                                }
                                else
                                {
-                                       S6_BYTE(&r, i) = (S6_BYTE(p1, i) - S6_BYTE(p2, i) - carry);
-                                       carry = (S6_BYTE(p1, i) < (S6_BYTE(p2, i) + carry));
+                                       BYTE(&r, i) = (BYTE(p1, i) - BYTE(p2, i) - carry);
+                                       carry = (BYTE(p1, i) < (BYTE(p2, i) + carry));
                                }
                        }
 
                        /* would over/underflow */
                        if (carry)
                        {
-                               memset(&r.addr.v6, add * 0xFF, sizeof(r.addr.v6));
+                               memset(&r.addr.u8, add * 0xFF, AF_BYTES(r.family));
                                ok = false;
                        }
                }
-               else
-               {
-                       a = ntohl(p1->addr.v4.s_addr);
-                       b = ntohl(p2->addr.v4.s_addr);
-
-                       /* would over/underflow */
-                       if ((add && (UINT_MAX - a) < b) || (!add && a < b))
-                       {
-                               r.addr.v4.s_addr = add * 0xFFFFFFFF;
-                               ok = false;
-                       }
-                       else
-                       {
-                               r.addr.v4.s_addr = add ? htonl(a + b) : htonl(a - b);
-                       }
-               }
        }
        else
        {
@@ -705,22 +945,22 @@ static int cidr_minhost(lua_State *L)
 
        _apply_mask(&r, r.bits, false);
 
-       if (r.family == AF_INET6 && r.bits < 128)
+       if (r.family == AF_INET && r.bits < AF_BITS(AF_INET))
+       {
+               r.bits = AF_BITS(AF_INET);
+               r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) + 1);
+       }
+       else if (r.bits < AF_BITS(r.family))
        {
-               r.bits = 128;
+               r.bits = AF_BITS(r.family);
 
-               for (i = 0, carry = 1; i < sizeof(r.addr.v6.s6_addr); i++)
+               for (i = 0, carry = 1; i < AF_BYTES(r.family); i++)
                {
-                       rest = (S6_BYTE(&r, i) + carry) > 255;
-                       S6_BYTE(&r, i) += carry;
+                       rest = (BYTE(&r, i) + carry) > 255;
+                       BYTE(&r, i) += carry;
                        carry = rest;
                }
        }
-       else if (r.family == AF_INET && r.bits < 32)
-       {
-               r.bits = 32;
-               r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) + 1);
-       }
 
        if (!(p = lua_newuserdata(L, sizeof(*p))))
                return 0;
@@ -739,14 +979,14 @@ static int cidr_maxhost(lua_State *L)
 
        _apply_mask(&r, r.bits, true);
 
-       if (r.family == AF_INET && r.bits < 32)
+       if (r.family == AF_INET && r.bits < AF_BITS(AF_INET))
        {
-               r.bits = 32;
+               r.bits = AF_BITS(AF_INET);
                r.addr.v4.s_addr = htonl(ntohl(r.addr.v4.s_addr) - 1);
        }
-       else if (r.family == AF_INET6)
+       else
        {
-               r.bits = 128;
+               r.bits = AF_BITS(r.family);
        }
 
        if (!(p = lua_newuserdata(L, sizeof(*p))))
@@ -766,31 +1006,17 @@ static int cidr_gc (lua_State *L)
 
 static int cidr_tostring (lua_State *L)
 {
-       char buf[INET6_ADDRSTRLEN];
        cidr_t *p = L_checkcidr(L, 1, NULL);
-
-       if ((p->family == AF_INET && p->bits < 32) ||
-           (p->family == AF_INET6 && p->bits < 128))
-       {
-               lua_pushfstring(L, "%s/%d",
-                               inet_ntop(p->family, &p->addr.v6, buf, sizeof(buf)),
-                                               p->bits);
-       }
-       else
-       {
-               lua_pushstring(L, inet_ntop(p->family, &p->addr.v6, buf, sizeof(buf)));
-       }
-
-       return 1;
+       return format_cidr(L, p);
 }
 
 /*
  * route functions
  */
 
-static bool diff_prefix(int family, void *addr, int bits, cidr_t *p)
+static bool diff_prefix(int family, void *addr, int bits, bool exact, cidr_t *p)
 {
-       uint8_t i, b, r;
+       uint8_t i, b, r, *a;
        uint32_t m;
 
        if (!p->family)
@@ -799,28 +1025,27 @@ static bool diff_prefix(int family, void *addr, int bits, cidr_t *p)
        if (!addr || p->family != family || p->bits > bits)
                return true;
 
-       if (family == AF_INET6)
+       if (family == AF_INET)
        {
-               for (i = 0, r = p->bits; i < sizeof(struct in6_addr); i++)
+               m = p->bits ? htonl(~((1 << (AF_BITS(AF_INET) - p->bits)) - 1)) : 0;
+
+               if ((((struct in_addr *)addr)->s_addr & m) != (p->addr.v4.s_addr & m))
+                       return true;
+       }
+       else
+       {
+               for (i = 0, a = addr, r = p->bits; i < AF_BYTES(p->family); i++)
                {
                        b = r ? (0xFF << (8 - ((r > 8) ? 8 : r))) : 0;
 
-                       if ((((struct in6_addr *)addr)->s6_addr[i] & b) !=
-                           (p->addr.v6.s6_addr[i] & b))
+                       if ((a[i] & b) != (p->addr.u8[i] & b))
                                return true;
 
                        r -= ((r > 8) ? 8 : r);
                }
        }
-       else
-       {
-               m = p->bits ? htonl(~((1 << (32 - p->bits)) - 1)) : 0;
 
-               if ((((struct in_addr *)addr)->s_addr & m) != (p->addr.v4.s_addr & m))
-                       return true;
-       }
-
-       return (p->exact && p->bits != bits);
+       return (exact && p->bits != bits);
 }
 
 static int cb_dump_route(struct nl_msg *msg, void *arg)
@@ -848,7 +1073,7 @@ static int cb_dump_route(struct nl_msg *msg, void *arg)
        dst   = tb[RTA_DST]     ? RTA_DATA(tb[RTA_DST])     : &def;
        gw    = tb[RTA_GATEWAY] ? RTA_DATA(tb[RTA_GATEWAY]) : NULL;
 
-       bitlen = (rt->rtm_family == AF_INET6) ? 128 : 32;
+       bitlen = AF_BITS(rt->rtm_family);
 
        if ((f->type   && rt->rtm_type     != f->type)   ||
            (f->family && rt->rtm_family   != f->family) ||
@@ -857,10 +1082,14 @@ static int cb_dump_route(struct nl_msg *msg, void *arg)
                (f->iif    && iif              != f->iif)    ||
                (f->oif    && oif              != f->oif)    ||
                (f->table  && table            != f->table)  ||
-           diff_prefix(rt->rtm_family, from, rt->rtm_src_len, &f->from) ||
-           diff_prefix(rt->rtm_family, dst,  rt->rtm_dst_len, &f->dst)  ||
-           diff_prefix(rt->rtm_family, gw,   bitlen, &f->gw)            ||
-           diff_prefix(rt->rtm_family, src,  bitlen, &f->src))
+           diff_prefix(rt->rtm_family, from, rt->rtm_src_len,
+                       f->from_exact, &f->from)         ||
+           diff_prefix(rt->rtm_family, dst,  rt->rtm_dst_len,
+                       f->dst_exact, &f->dst)           ||
+           diff_prefix(rt->rtm_family, gw,   bitlen,
+                       false, &f->gw)                   ||
+           diff_prefix(rt->rtm_family, src,  bitlen,
+                       false, &f->src))
                goto out;
 
        if (s->callback)
@@ -988,7 +1217,8 @@ static int _route_dump(lua_State *L, struct dump_filter *filter)
        nlmsg_append(msg, &rtm, sizeof(rtm), 0);
 
        if (filter->get)
-               nla_put(msg, RTA_DST, filter->dst.len, &filter->dst.addr.v6);
+               nla_put(msg, RTA_DST, AF_BYTES(filter->dst.family),
+                       &filter->dst.addr.v6);
 
        nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, cb_dump_route, &s);
        nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, cb_done, &s);
@@ -1063,10 +1293,10 @@ static int route_dump(lua_State *L)
                        filter.dst = p;
 
                if ((s = L_getstr(L, 1, "from_exact")) != NULL && parse_cidr(s, &p))
-                       filter.from = p, filter.from.exact = true;
+                       filter.from = p, filter.from_exact = true;
 
                if ((s = L_getstr(L, 1, "dest_exact")) != NULL && parse_cidr(s, &p))
-                       filter.dst = p, filter.dst.exact = true;
+                       filter.dst = p, filter.dst_exact = true;
        }
 
        return _route_dump(L, &filter);
@@ -1107,12 +1337,12 @@ static int cb_dump_neigh(struct nl_msg *msg, void *arg)
        mac = tb[NDA_LLADDR] ? RTA_DATA(tb[NDA_LLADDR]) : NULL;
        dst = tb[NDA_DST]    ? RTA_DATA(tb[NDA_DST])    : NULL;
 
-       bitlen = (nd->ndm_family == AF_INET) ? 32 : 128;
+       bitlen = AF_BITS(nd->ndm_family);
 
        if ((f->family && nd->ndm_family  != f->family) ||
            (f->iif    && nd->ndm_ifindex != f->iif) ||
                (f->type   && !(f->type & nd->ndm_state)) ||
-           diff_prefix(nd->ndm_family, dst, bitlen, &f->dst) ||
+           diff_prefix(nd->ndm_family, dst, bitlen, false, &f->dst) ||
            diff_macaddr(mac, &f->mac))
                goto out;
 
@@ -1140,15 +1370,7 @@ static int cb_dump_neigh(struct nl_msg *msg, void *arg)
                L_setaddr(s->L, "dest", nd->ndm_family, dst, -1);
 
        if (mac)
-       {
-               snprintf(buf, sizeof(buf), "%02x:%02x:%02x:%02x:%02x:%02x",
-                        mac->ether_addr_octet[0], mac->ether_addr_octet[1],
-                        mac->ether_addr_octet[2], mac->ether_addr_octet[3],
-                        mac->ether_addr_octet[4], mac->ether_addr_octet[5]);
-
-               lua_pushstring(s->L, buf);
-               lua_setfield(s->L, -2, "mac");
-       }
+               L_setaddr(s->L, "mac", AF_PACKET, mac, -1);
 
        s->index++;
 
@@ -1241,7 +1463,7 @@ out:
 
 static int cb_dump_link(struct nl_msg *msg, void *arg)
 {
-       char *p, *addr, buf[48];
+       char buf[48];
        struct dump_state *s = arg;
        struct nlmsghdr *hdr = nlmsg_hdr(msg);
        struct ifinfomsg *ifm = NLMSG_DATA(hdr);
@@ -1266,19 +1488,8 @@ static int cb_dump_link(struct nl_msg *msg, void *arg)
        if (tb[IFLA_MASTER])
                L_setdev(s->L, "master", tb[IFLA_MASTER]);
 
-       if (tb[IFLA_ADDRESS])
-       {
-               len  = nla_len(tb[IFLA_ADDRESS]);
-               addr = nla_get_string(tb[IFLA_ADDRESS]);
-
-               if ((len * 3) <= sizeof(buf))
-               {
-                       for (p = buf, i = 0; i < len; i++)
-                               p += sprintf(p, "%s%02x", (i ? ":" : ""), (uint8_t)*addr++);
-
-                       L_setstr(s->L, "mac", buf);
-               }
-       }
+       if (tb[IFLA_ADDRESS] && nla_len(tb[IFLA_ADDRESS]) == AF_BYTES(AF_PACKET))
+               L_setaddr(s->L, "mac", AF_PACKET, nla_get_string(tb[IFLA_ADDRESS]), -1);
 
        s->pending = 0;
        return NL_SKIP;
@@ -1333,13 +1544,18 @@ static const luaL_reg ip_methods[] = {
        { "new",                        cidr_new          },
        { "IPv4",                       cidr_ipv4         },
        { "IPv6",                       cidr_ipv6         },
+       { "MAC",                        cidr_mac          },
+
+       { "checkip4",                   cidr_checkip4     },
+       { "checkip6",                   cidr_checkip6     },
+       { "checkmac",                   cidr_checkmac     },
 
        { "route",                      route_get         },
        { "routes",                     route_dump        },
 
        { "neighbors",          neighbor_dump     },
 
-       { "link",           link_get          },
+       { "link",                       link_get          },
 
        { }
 };
@@ -1351,6 +1567,9 @@ static const luaL_reg ip_cidr_methods[] = {
        { "is6",                        cidr_is6          },
        { "is6linklocal",       cidr_is6linklocal },
        { "is6mapped4",         cidr_is6mapped4   },
+       { "ismac",                      cidr_ismac        },
+       { "ismaclocal",         cidr_ismaclocal   },
+       { "ismacmcast",         cidr_ismacmcast   },
        { "lower",                      cidr_lower        },
        { "higher",                     cidr_higher       },
        { "equal",                      cidr_equal        },
@@ -1360,7 +1579,9 @@ static const luaL_reg ip_cidr_methods[] = {
        { "mask",                       cidr_mask         },
        { "broadcast",          cidr_broadcast    },
        { "mapped4",            cidr_mapped4      },
-       { "contains",       cidr_contains     },
+       { "tomac",                      cidr_tomac        },
+       { "tolinklocal",        cidr_tolinklocal  },
+       { "contains",           cidr_contains     },
        { "add",                        cidr_add          },
        { "sub",                        cidr_sub          },
        { "minhost",            cidr_minhost      },
index e32ae72..b1ecae1 100644 (file)
@@ -27,6 +27,7 @@ addr6 = luci.ip.new("fe80::221:63ff:fe75:aa17", "ffff:ffff:ffff:ffff::")
 addr6 = luci.ip.new("fe80::221:63ff:fe75:aa17/64", 128) -- override netmask`
 @see IPv4
 @see IPv6
+@see MAC
 ]]
 
 ---[[
@@ -47,6 +48,7 @@ addr = luci.ip.IPv4("10.24.0.1/255.255.255.0")
 addr = luci.ip.IPv4("10.24.0.1", "255.255.255.0")        -- separate netmask
 addr = luci.ip.IPv4("10.24.0.1/24", 16)                  -- override netmask`
 @see IPv6
+@see MAC
 ]]
 
 ---[[
@@ -67,12 +69,112 @@ addr6 = luci.ip.IPv6("fe80::221:63ff:fe75:aa17/ffff:ffff:ffff:ffff::")
 addr6 = luci.ip.IPv6("fe80::221:63ff:fe75:aa17", "ffff:ffff:ffff:ffff::")
 addr6 = luci.ip.IPv6("fe80::221:63ff:fe75:aa17/64", 128) -- override netmask`
 @see IPv4
+@see MAC
 ]]
 
 ---[[
-Determine the route leading to the given destination.
+Construct a new MAC luci.ip.cidr instance.
+Throws an error if the given string does not represent a valid ethernet MAC
+address or if the given optional mask is of a different family.
 @class function
 @sort 4
+@name MAC
+@param address  String containing a valid ethernet MAC address, optionally with
+prefix size (CIDR notation) or mask separated by slash.
+@param netmask  String containing a valid MAC address mask or number
+containing a prefix size between `0` and `48` bit.
+Overrides mask embedded in the first argument if specified. (optional)
+@return A `luci.ip.cidr` object representing the given MAC address range.
+@usage `intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/24")
+intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/FF:FF:FF:0:0:0")
+intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00", "FF:FF:FF:0:0:0")
+intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/24", 48) -- override mask`
+@see IPv4
+@see IPv6
+]]
+
+---[[
+Verify an IPv4 address.
+
+Checks whether given argument is a preexisting luci.ip.cidr IPv4 address
+instance or a string literal convertible to an IPv4 address and returns a
+plain Lua string containing the canonical representation of the address.
+
+If the argument is not a valid address, returns nothing. This function is
+intended to aid in safely verifying address literals without having to deal
+with exceptions.
+@class function
+@sort 5
+@name checkip4
+@param address  String containing a valid IPv4 address or existing
+luci.ip.cidr IPv4 instance.
+@return A string representing the given IPv4 address.
+@usage `ipv4 = luci.ip.checkip4(luci.ip.new("127.0.0.1"))  -- "127.0.0.1"
+ipv4 = luci.ip.checkip4("127.0.0.1")               -- "127.0.0.1"
+ipv4 = luci.ip.checkip4("nonesense")               -- nothing
+ipv4 = luci.ip.checkip4(123)                       -- nothing
+ipv4 = luci.ip.checkip4(nil)                       -- nothing
+ipv4 = luci.ip.checkip4()                          -- nothing`
+@see checkip6
+@see checkmac
+]]
+
+---[[
+Verify an IPv6 address.
+
+Checks whether given argument is a preexisting luci.ip.cidr IPv6 address
+instance or a string literal convertible to an IPv6 address and returns a
+plain Lua string containing the canonical representation of the address.
+
+If the argument is not a valid address, returns nothing. This function is
+intended to aid in safely verifying address literals without having to deal
+with exceptions.
+@class function
+@sort 6
+@name checkip6
+@param address  String containing a valid IPv6 address or existing
+luci.ip.cidr IPv6 instance.
+@return A string representing the given IPv6 address.
+@usage `ipv6 = luci.ip.checkip6(luci.ip.new("0:0:0:0:0:0:0:1"))  -- "::1"
+ipv6 = luci.ip.checkip6("0:0:0:0:0:0:0:1")               -- "::1"
+ipv6 = luci.ip.checkip6("nonesense")                     -- nothing
+ipv6 = luci.ip.checkip6(123)                             -- nothing
+ipv6 = luci.ip.checkip6(nil)                             -- nothing
+ipv6 = luci.ip.checkip6()                                -- nothing`
+@see checkip4
+@see checkmac
+]]
+
+---[[
+Verify an ethernet MAC address.
+
+Checks whether given argument is a preexisting luci.ip.cidr MAC address
+instance or a string literal convertible to an ethernet MAC and returns a
+plain Lua string containing the canonical representation of the address.
+
+If the argument is not a valid address, returns nothing. This function is
+intended to aid in safely verifying address literals without having to deal
+with exceptions.
+@class function
+@sort 7
+@name checkmac
+@param address  String containing a valid MAC address or existing luci.ip.cidr
+MAC address instance.
+@return A string representing the given MAC address.
+@usage `mac = luci.ip.checkmac(luci.ip.new("00-11-22-cc-dd-ee"))  -- "00:11:22:CC:DD:EE"
+mac = luci.ip.checkmac("00:11:22:cc:dd:ee")               -- "00:11:22:CC:DD:EE"
+mac = luci.ip.checkmac("nonesense")                       -- nothing
+mac = luci.ip.checkmac(123)                               -- nothing
+mac = luci.ip.checkmac(nil)                               -- nothing
+mac = luci.ip.checkmac()                                  -- nothing`
+@see checkip4
+@see checkip6
+]]
+
+---[[
+Determine the route leading to the given destination.
+@class function
+@sort 8
 @name route
 @param address A `luci.ip.cidr` instance or a string containing
 a valid IPv4 or IPv6 range as specified by `luci.ip.new()`.
@@ -178,7 +280,7 @@ end`</li>
 ---[[
 Fetch all routes, optionally matching the given criteria.
 @class function
-@sort 5
+@sort 9
 @name routes
 @param filter  <p>Table containing one or more of the possible filter
 critera described below (optional)</p><table>
@@ -258,7 +360,7 @@ end`</li>
 ---[[
 Fetches entries from the IPv4 ARP and IPv6 neighbour kernel table
 @class function
-@sort 6
+@sort 10
 @name neighbors
 @param filter  <p>Table containing one or more of the possible filter
 critera described below (optional)</p><table>
@@ -306,7 +408,7 @@ A neighbour entry is a table containing the following fields:
 </tr>
 <tr>
   <td>`mac`</td>
-  <td>String containing the associated MAC address</td>
+  <td>MAC address `luci.ip.cidr` instance</td>
 </tr>
 <tr>
   <td>`router`</td>
@@ -367,7 +469,7 @@ end)`</li>
 ---[[
 Fetch basic device information
 @class function
-@sort 7
+@sort 11
 @name link
 @param device  String containing the network device to query
 @return  If the given interface is found, a table containing the fields
@@ -403,8 +505,8 @@ described below is returned, else an empty table.
 </tr>
 <tr>
   <td>`mac`</td>
-  <td>String containing the link local address of the device in
-      dotted hex notation</td>
+  <td>MAC address `luci.ip.cidr` instance representing the device ethernet
+      address</td>
 </tr>
 </table>
 @usage <ul>
@@ -430,6 +532,7 @@ Checks whether the CIDR instance is an IPv4 address range
 @sort 1
 @name cidr.is4
 @see cidr.is6
+@see cidr.ismac
 @return `true` if the CIDR is an IPv4 range, else `false`
 ]]
 
@@ -470,6 +573,7 @@ Checks whether the CIDR instance is an IPv6 address range
 @sort 4
 @name cidr.is6
 @see cidr.is4
+@see cidr.ismac
 @return `true` if the CIDR is an IPv6 range, else `false`
 ]]
 
@@ -502,13 +606,51 @@ end`
 ]]
 
 ---[[
+Checks whether the CIDR instance is an ethernet MAC address range
+
+@class function
+@sort 7
+@name cidr.ismac
+@see cidr.is4
+@see cidr.is6
+@return `true` if the CIDR is a MAC address range, else `false`
+]]
+
+---[[
+Checks whether the CIDR instance is a locally administered (LAA) MAC address
+
+@class function
+@sort 8
+@name cidr.ismaclocal
+@return `true` if the MAC address sets the locally administered bit.
+@usage `local mac = luci.ip.new("02:C0:FF:EE:00:01")
+if mac:ismaclocal() then
+  print("Is an LAA MAC address")
+end`
+]]
+
+---[[
+Checks whether the CIDR instance is a multicast MAC address
+
+@class function
+@sort 9
+@name cidr.ismacmcast
+@return `true` if the MAC address sets the multicast bit.
+@usage `local mac = luci.ip.new("01:00:5E:7F:00:10")
+if addr:ismacmcast() then
+  print("Is a multicast MAC address")
+end`
+]]
+
+---[[
 Checks whether this CIDR instance is lower than the given argument.
 The comparisation follows these rules:
-<ul><li>An IPv4 address is always lower than an IPv6 address</li>
+<ul><li>An IPv4 address is always lower than an IPv6 address and IPv6 addresses
+are considered lower than MAC addresses</li>
 <li>Prefix sizes are ignored</li></ul>
 
 @class function
-@sort 7
+@sort 10
 @name cidr.lower
 @param addr A `luci.ip.cidr` instance or a string convertable by
        `luci.ip.new()` to compare against.
@@ -518,7 +660,8 @@ The comparisation follows these rules:
 print(addr:lower(addr)) -- false
 print(addr:lower("10.10.10.10/24")) -- false
 print(addr:lower(luci.ip.new("::1"))) -- true
-print(addr:lower(luci.ip.new("192.168.200.1"))) -- true`
+print(addr:lower(luci.ip.new("192.168.200.1"))) -- true
+print(addr:lower(luci.ip.new("00:14:22:01:23:45"))) -- true`
 @see cidr.higher
 @see cidr.equal
 ]]
@@ -526,11 +669,12 @@ print(addr:lower(luci.ip.new("192.168.200.1"))) -- true`
 ---[[
 Checks whether this CIDR instance is higher than the given argument.
 The comparisation follows these rules:
-<ul><li>An IPv4 address is always lower than an IPv6 address</li>
+<ul><li>An IPv4 address is always lower than an IPv6 address and IPv6 addresses
+are considered lower than MAC addresses</li>
 <li>Prefix sizes are ignored</li></ul>
 
 @class function
-@sort 8
+@sort 11
 @name cidr.higher
 @param addr A `luci.ip.cidr` instance or a string convertable by
        `luci.ip.new()` to compare against.
@@ -540,7 +684,8 @@ The comparisation follows these rules:
 print(addr:higher(addr)) -- false
 print(addr:higher("10.10.10.10/24")) -- true
 print(addr:higher(luci.ip.new("::1"))) -- false
-print(addr:higher(luci.ip.new("192.168.200.1"))) -- false`
+print(addr:higher(luci.ip.new("192.168.200.1"))) -- false
+print(addr:higher(luci.ip.new("00:14:22:01:23:45"))) -- false`
 @see cidr.lower
 @see cidr.equal
 ]]
@@ -549,7 +694,7 @@ print(addr:higher(luci.ip.new("192.168.200.1"))) -- false`
 Checks whether this CIDR instance is equal to the given argument.
 
 @class function
-@sort 9
+@sort 12
 @name cidr.equal
 @param addr A `luci.ip.cidr` instance or a string convertable by
        `luci.ip.new()` to compare against.
@@ -562,7 +707,11 @@ print(addr:equal(luci.ip.new("::1"))) -- false
 
 local addr6 = luci.ip.new("::1")
 print(addr6:equal("0:0:0:0:0:0:0:1/64")) -- true
-print(addr6:equal(luci.ip.new("fe80::221:63ff:fe75:aa17"))) -- false`
+print(addr6:equal(luci.ip.new("fe80::221:63ff:fe75:aa17"))) -- false
+
+local mac = luci.ip.new("00:14:22:01:23:45")
+print(mac:equal("0:14:22:1:23:45")) -- true
+print(mac:equal(luci.ip.new("01:23:45:67:89:AB")) -- false`
 @see cidr.lower
 @see cidr.higher
 ]]
@@ -573,11 +722,11 @@ If the optional mask parameter is given, the prefix size of this CIDR is altered
 else the current prefix size is returned.
 
 @class function
-@sort 10
+@sort 13
 @name cidr.prefix
 @param mask Either a number containing the number of bits (`0..32`
-       for IPv4, `0..128` for IPv6) or a string containing a valid
-       netmask (optional)
+       for IPv4, `0..128` for IPv6 or `0..48` for MAC addresses) or a string
+  containing a valid netmask (optional)
 @return Bit count of the current prefix size
 @usage `local range = luci.ip.new("192.168.1.1/255.255.255.0")
 print(range:prefix()) -- 24
@@ -597,11 +746,11 @@ with all host parts masked out. The used prefix size can be overridden by the
 optional mask parameter.
 
 @class function
-@sort 11
+@sort 14
 @name cidr.network
 @param mask Either a number containing the number of bits (`0..32`
-       for IPv4, `0..128` for IPv6) or a string containing a valid
-       netmask (optional)
+       for IPv4, `0..128` for IPv6 or `0..48` for MAC addresses) or a string
+  containing a valid netmask (optional)
 @return CIDR instance representing the network address
 @usage `local range = luci.ip.new("192.168.62.243/255.255.0.0")
 print(range:network())                -- "192.168.0.0"
@@ -616,10 +765,10 @@ print(range6:network())               -- "fd9b:62b3:9cc5::"`
 Derive host address of CIDR instance.
 
 This function essentially constructs a copy of this CIDR with the prefix size
-set to `32` for IPv4 and `128` for IPv6.
+set to `32` for IPv4, `128` for IPv6 or `48` for MAC addresses.
 
 @class function
-@sort 12
+@sort 15
 @name cidr.host
 @return CIDR instance representing the host address
 @usage `local range = luci.ip.new("172.19.37.45/16")
@@ -634,11 +783,11 @@ Constructs a CIDR instance representing the netmask of this instance. The used
 prefix size can be overridden by the optional mask parameter.
 
 @class function
-@sort 13
+@sort 16
 @name cidr.mask
 @param mask Either a number containing the number of bits (`0..32`
-       for IPv4, `0..128` for IPv6) or a string containing a valid
-       netmask (optional)
+       for IPv4, `0..128` for IPv6 or `0..48` for MAC addresses) or a string
+  containing a valid netmask (optional)
 @return CIDR instance representing the netmask
 @usage `local range = luci.ip.new("172.19.37.45/16")
 print(range:mask())            -- "255.255.0.0"
@@ -652,15 +801,14 @@ Derive broadcast address of CIDR instance.
 Constructs a CIDR instance representing the broadcast address of this instance.
 The used prefix size can be overridden by the optional mask parameter.
 
-This function has no effect on IPv6 instances, it will return nothing in this
-case.
+This function has no effect on IPv6 or MAC address instances, it will return
+nothing in this case.
 
 @class function
-@sort 14
+@sort 17
 @name cidr.broadcast
-@param mask Either a number containing the number of bits (`0..32`
-       for IPv4, `0..128` for IPv6) or a string containing a valid
-       netmask (optional)
+@param mask Either a number containing the number of bits (`0..32` for IPv4) or
+  a string containing a valid netmask (optional)
 @return Return a new CIDR instance representing the broadcast address if this
        instance is an IPv4 range, else return nothing.
 @usage `local range = luci.ip.new("172.19.37.45/16")
@@ -675,11 +823,11 @@ Derive mapped IPv4 address of CIDR instance.
 Constructs a CIDR instance representing the IPv4 address of the IPv6 mapped
 IPv4 address in this instance.
 
-This function has no effect on IPv4 instances or IPv6 instances which are not a
-mapped address, it will return nothing in this case.
+This function has no effect on IPv4 instances, MAC address instances or IPv6
+instances which are not a mapped address, it will return nothing in this case.
 
 @class function
-@sort 15
+@sort 18
 @name cidr.mapped4
 @return Return a new CIDR instance representing the IPv4 address if this
        instance is an IPv6 mapped IPv4 address, else return nothing.
@@ -688,10 +836,46 @@ print(addr:mapped4()) -- "172.16.19.1"`
 ]]
 
 ---[[
+Derive MAC address of IPv6 link local CIDR instance.
+
+Constructs a CIDR instance representing the MAC address contained in the IPv6
+link local address of this instance.
+
+This function has no effect on IPv4 instances, MAC address instances or IPv6
+instances which are not a link local address, it will return nothing in this
+case.
+
+@class function
+@sort 19
+@name cidr.tomac
+@return Return a new CIDR instance representing the MAC address if this
+  instance is an IPv6 link local address, else return nothing.
+@usage `local addr = luci.ip.new("fe80::6666:b3ff:fe47:e1b9")
+print(addr:tomac()) -- "64:66:B3:47:E1:B9"`
+]]
+
+---[[
+Derive IPv6 link local address from MAC address CIDR instance.
+
+Constructs a CIDR instance representing the IPv6 link local address of the
+MAC address represented by this instance.
+
+This function has no effect on IPv4 instances or IPv6 instances, it will return
+nothing in this case.
+
+@class function
+@sort 20
+@name cidr.tolinklocal
+@return Return a new CIDR instance representing the IPv6 link local address.
+@usage `local mac = luci.ip.new("64:66:B3:47:E1:B9")
+print(mac:tolinklocal()) -- "fe80::6666:b3ff:fe47:e1b9"`
+]]
+
+---[[
 Test whether CIDR contains given range.
 
 @class function
-@sort 16
+@sort 21
 @name cidr.contains
 @param addr A `luci.ip.cidr` instance or a string convertable by
        `luci.ip.new()` to test.
@@ -704,7 +888,11 @@ print(range:contains("10.0.0.0/8")) -- false
 
 local range6 = luci.ip.new("fe80::/10")
 print(range6:contains("fe80::221:63f:fe75:aa17/64"))         -- true
-print(range6:contains("fd9b:6b3:c5:0:221:63f:fe75:aa17/64")) -- false`
+print(range6:contains("fd9b:6b3:c5:0:221:63f:fe75:aa17/64")) -- false
+
+local intel_macs = luci.ip.MAC("C0:B6:F9:00:00:00/24")
+print(intel_macs:contains("C0:B6:F9:A3:C:11"))  -- true
+print(intel_macs:contains("64:66:B3:47:E1:B9")) -- false`
 ]]
 
 ---[[
@@ -712,7 +900,7 @@ Add given amount to CIDR instance. If the result would overflow the maximum
 address space, the result is set to the highest possible address.
 
 @class function
-@sort 17
+@sort 22
 @name cidr.add
 @param amount A numeric value between 0 and 0xFFFFFFFF, a
        `luci.ip.cidr` instance or a string convertable by
@@ -726,32 +914,42 @@ address space, the result is set to the highest possible address.
         this instance plus the added amount or the highest possible address if
            the addition overflowed the available address space.</li></ul>
 @usage `local addr = luci.ip.new("192.168.1.1/24")
-print(addr:add(250))         -- "192.168.1.251/24"
-print(addr:add("0.0.99.0"))  -- "192.168.100.1/24"
+print(addr:add(250))           -- "192.168.1.251/24"
+print(addr:add("0.0.99.0"))    -- "192.168.100.1/24"
 
-addr:add(256, true)          -- true
-print(addr)                  -- "192.168.2.1/24
+addr:add(256, true)            -- true
+print(addr)                    -- "192.168.2.1/24
 
-addr:add("255.0.0.0", true)  -- false (overflow)
-print(addr)                  -- "255.255.255.255/24
+addr:add("255.0.0.0", true)    -- false (overflow)
+print(addr)                    -- "255.255.255.255/24
 
 local addr6 = luci.ip.new("fe80::221:63f:fe75:aa17/64")
-print(addr6:add(256))        -- "fe80::221:63f:fe75:ab17/64"
-print(addr6:add("::ffff:0")) -- "fe80::221:640:fe74:aa17/64"
+print(addr6:add(256))          -- "fe80::221:63f:fe75:ab17/64"
+print(addr6:add("::ffff:0"))   -- "fe80::221:640:fe74:aa17/64"
+
+addr6:add(256, true)           -- true
+print(addr6)                   -- "fe80::221:63f:fe75:ab17/64
+
+addr6:add("ffff::", true)      -- false (overflow)
+print(addr6)                   -- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64"
 
-addr:add(256, true)          -- true
-print(addr)                  -- "fe80::221:63f:fe75:ab17/64
+local mac = luci.ip.new("00:14:22:01:23:45")
+print(mac:add(256))            -- "00:14:22:01:24:45"
+print(mac:add("0:0:0:0:FF:0")  -- "00:14:22:02:22:45"
 
-addr:add("ffff::", true)     -- false (overflow)
-print(addr)                  -- "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/64"`
+mac:add(256, true)             -- true
+print(mac)                     -- "00:14:22:01:24:45"
+
+mac:add("FF:FF:0:0:0:0", true) -- false (overflow)
+print(mac)                     -- "FF:FF:FF:FF:FF:FF"`
 ]]
 
 ---[[
-Substract given amount from CIDR instance. If the result would under, the lowest
+Subtract given amount from CIDR instance. If the result would under, the lowest
 possible address is returned.
 
 @class function
-@sort 18
+@sort 23
 @name cidr.sub
 @param amount A numeric value between 0 and 0xFFFFFFFF, a
        `luci.ip.cidr` instance or a string convertable by
@@ -759,11 +957,11 @@ possible address is returned.
 @param inplace If `true`, modify this instance instead of returning
        a new derived CIDR instance.
 @return <ul>
-       <li>When substracting inplace: Return `true` if the substraction
-           succeded or `false` when the substraction underflowed.</li>
+       <li>When subtracting inplace: Return `true` if the subtraction
+           succeeded or `false` when the subtraction underflowed.</li>
        <li>When deriving new CIDR: Return new instance representing the value of
-        this instance minus the substracted amount or the lowest address if
-           the substraction underflowed.</li></ul>
+        this instance minus the subtracted amount or the lowest address if
+           the subtraction underflowed.</li></ul>
 @usage `local addr = luci.ip.new("192.168.1.1/24")
 print(addr:sub(256))         -- "192.168.0.1/24"
 print(addr:sub("0.168.0.0")) -- "192.0.1.1/24"
@@ -782,14 +980,24 @@ addr:sub(256, true)          -- true
 print(addr)                  -- "fe80::221:63f:fe75:a917/64"
 
 addr:sub("ffff::", true)     -- false (underflow)
-print(addr)                  -- "::/64"`
+print(addr)                  -- "::/64"
+
+local mac = luci.ip.new("00:14:22:01:23:45")
+print(mac:sub(256))            -- "00:14:22:01:22:45"
+print(mac:sub("0:0:0:0:FF:0")  -- "00:14:22:00:24:45"
+
+mac:sub(256, true)             -- true
+print(mac)                     -- "00:14:22:01:22:45"
+
+mac:sub("FF:FF:0:0:0:0", true) -- false (overflow)
+print(mac)                     -- "00:00:00:00:00:00"`
 ]]
 
 ---[[
 Calculate the lowest possible host address within this CIDR instance.
 
 @class function
-@sort 19
+@sort 24
 @name cidr.minhost
 @return Returns a new CIDR instance representing the lowest host address
        within this range.
@@ -797,14 +1005,17 @@ Calculate the lowest possible host address within this CIDR instance.
 print(addr:minhost())  -- "192.168.123.1"
 
 local addr6 = luci.ip.new("fd9b:62b3:9cc5:0:221:63ff:fe75:aa17/64")
-print(addr6:minhost()) -- "fd9b:62b3:9cc5::1"`
+print(addr6:minhost()) -- "fd9b:62b3:9cc5::1"
+
+local mac = luci.ip.new("00:14:22:01:22:45/32")
+print(mac:minhost())   -- "00:14:22:01:00:01"`
 ]]
 
 ---[[
 Calculate the highest possible host address within this CIDR instance.
 
 @class function
-@sort 20
+@sort 25
 @name cidr.maxhost
 @return Returns a new CIDR instance representing the highest host address
        within this range.
@@ -812,20 +1023,24 @@ Calculate the highest possible host address within this CIDR instance.
 print(addr:maxhost())  -- "192.168.123.254" (.255 is broadcast)
 
 local addr6 = luci.ip.new("fd9b:62b3:9cc5:0:221:63ff:fe75:aa17/64")
-print(addr6:maxhost()) -- "fd9b:62b3:9cc5:0:ffff:ffff:ffff:ffff"`
+print(addr6:maxhost()) -- "fd9b:62b3:9cc5:0:ffff:ffff:ffff:ffff"
+
+local mac = luci.ip.new("00:14:22:01:22:45/32")
+print(mac:maxhost())   -- "00:14:22:01:FF:FF"`
 ]]
 
 ---[[
 Convert CIDR instance into string representation.
 
-If the prefix size of instance is less than 32 for IPv4 or 128 for IPv6, the
-address is returned in the form "address/prefix" otherwise just "address".
+If the prefix size of instance is less than 32 for IPv4, 128 for IPv6 or 48 for
+MACs, the address is returned in the form "address/prefix" otherwise just
+"address".
 
 It is usually not required to call this function directly as CIDR objects
 define it as __tostring function in the associated metatable.
 
 @class function
-@sort 21
+@sort 26
 @name cidr.string
 @return Returns a string representing the range or address of this CIDR instance
 ]]
index ee3e3a2..9860cf0 100644 (file)
@@ -33,7 +33,7 @@ module "nixio.README"
 -- <br />In general all functions are namend and behave like their POSIX API
 -- counterparts - where applicable - applying the following rules:
 -- <ul>
--- <li>Functions should be named like the underlying POSIX API function ommiting
+-- <li>Functions should be named like the underlying POSIX API function omitting
 -- prefixes or suffixes - especially when placed in an object-context (
 -- lockf -> File:lock, fsync -> File:sync, dup2 -> dup, ...)</li>
 -- <li>If you are unclear about the behaviour of a function you should consult
@@ -41,10 +41,10 @@ module "nixio.README"
 -- <li>If the name is significantly different from the POSIX-function, the
 -- underlying function(s) are stated in the documentation.</li>
 -- <li>Parameters should reflect those of the C-API, buffer length arguments and 
--- by-reference parameters should be ommitted for pratical purposes.</li>
+-- by-reference parameters should be omitted for practical purposes.</li>
 -- <li>If a C function accepts a bitfield as parameter, it should be translated
 -- into lower case string flags representing the flags if the bitfield is the 
--- last parameter and also ommiting prefixes or suffixes. (e.g.  waitpid
+-- last parameter and also omitting prefixes or suffixes. (e.g.  waitpid
 -- (pid, &s, WNOHANG | WUNTRACED) -> waitpid(pid, "nohang", "untraced"), 
 -- getsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) ->
 -- Socket:getopt("socket", "reuseaddr"), etc.) </li>
index 7123393..c970534 100644 (file)
@@ -71,7 +71,7 @@ module "nixio.Socket"
 
 --- Send a message on the socket.
 -- This function is identical to sendto except for the missing destination
--- paramters. See the sendto description for a detailed description.
+-- parameters. See the sendto description for a detailed description.
 -- @class function
 -- @name Socket.send
 -- @param buffer       Buffer holding the data to be written.
@@ -167,4 +167,4 @@ module "nixio.Socket"
 -- "mtu" (IP, IPv6), "hdrincl" (IP), "multicast_ttl" (IP), "multicast_loop"
 -- (IP, IPv6), "multicast_if" (IP, IPv6), "v6only" (IPv6), "multicast_hops"
 -- (IPv6), "add_membership" (IP, IPv6), "drop_membership" (IP, IPv6)]
--- @return Value
\ No newline at end of file
+-- @return Value
index 6e407ff..6d1d5ea 100644 (file)
@@ -24,15 +24,15 @@ module "nixio.UnifiedIO"
 -- @class function
 -- @name UnifiedIO.readall
 -- @usage This function uses the low-level read function of the descriptor.
--- @usage If the length parameter is ommited, this function returns all data
+-- @usage If the length parameter is omitted, this function returns all data
 -- that can be read before an end-of-file, end-of-stream, connection shutdown
 -- or similar happens.
 -- @usage If the descriptor is non-blocking this function may fail with EAGAIN.
 -- @param      length  Bytes to read (optional)
--- @return     data that was successfully read if no error occured
+-- @return     data that was successfully read if no error occurred
 -- @return     - reserved for error code -
 -- @return     - reserved for error message -
--- @return  data that was successfully read even if an error occured
+-- @return  data that was successfully read even if an error occurred
 
 --- Write a block of data and wait until all data is written.
 -- @class function
@@ -40,10 +40,10 @@ module "nixio.UnifiedIO"
 -- @usage This function uses the low-level write function of the descriptor.
 -- @usage If the descriptor is non-blocking this function may fail with EAGAIN.
 -- @param      block   Bytes to write
--- @return     bytes that were successfully written if no error occured
+-- @return     bytes that were successfully written if no error occurred
 -- @return     - reserved for error code -
 -- @return     - reserved for error message -
--- @return  bytes that were successfully written even if an error occured
+-- @return  bytes that were successfully written even if an error occurred
 
 --- Create a line-based iterator.
 -- Lines may end with either \n or \r\n, these control chars are not included
@@ -56,7 +56,7 @@ module "nixio.UnifiedIO"
 -- to stop reading line-based and want to use the read(all) functions instead
 -- you can pass "true" to the iterator which will flush the buffer 
 -- and return the bufferd data.
--- @usage If the limit parameter is ommited, this function uses the nixio
+-- @usage If the limit parameter is omitted, this function uses the nixio
 -- buffersize (8192B by default).
 -- @usage If the descriptor is non-blocking the iterator may fail with EAGAIN.
 -- @usage The iterator can be used as an LTN12 source.
@@ -69,7 +69,7 @@ module "nixio.UnifiedIO"
 -- @usage This function uses the low-level read function of the descriptor.
 -- @usage The blocksize given is only advisory and to be seen as an upper limit,
 -- if an underlying read returns less bytes the chunk is nevertheless returned.
--- @usage If the limit parameter is ommited, the iterator returns data
+-- @usage If the limit parameter is omitted, the iterator returns data
 -- until an end-of-file, end-of-stream, connection shutdown or similar happens.
 -- @usage The iterator will not buffer so it is safe to mix with calls to read.
 -- @usage If the descriptor is non-blocking the iterator may fail with EAGAIN.
@@ -94,15 +94,15 @@ module "nixio.UnifiedIO"
 -- @name UnifiedIO.copy
 -- @usage This function uses the blocksource function of the source descriptor
 -- and the sink function of the target descriptor.
--- @usage If the limit parameter is ommited, data is copied
+-- @usage If the limit parameter is omitted, data is copied
 -- until an end-of-file, end-of-stream, connection shutdown or similar happens.
 -- @usage If the descriptor is non-blocking the function may fail with EAGAIN.
 -- @param      fdout Target Descriptor
 -- @param      size  Bytes to copy (optional)
--- @return     bytes that were successfully written if no error occured
+-- @return     bytes that were successfully written if no error occurred
 -- @return     - reserved for error code -
 -- @return     - reserved for error message -
--- @return  bytes that were successfully written even if an error occured
+-- @return  bytes that were successfully written even if an error occurred
 
 --- Copy data from the current descriptor to another one using kernel-space
 -- copying if possible.
@@ -111,19 +111,19 @@ module "nixio.UnifiedIO"
 -- @usage This function uses the sendfile() syscall to copy the data or the
 -- blocksource function of the source descriptor and the sink function
 -- of the target descriptor as a fallback mechanism.
--- @usage If the limit parameter is ommited, data is copied
+-- @usage If the limit parameter is omitted, data is copied
 -- until an end-of-file, end-of-stream, connection shutdown or similar happens.
 -- @usage If the descriptor is non-blocking the function may fail with EAGAIN.
 -- @param      fdout Target Descriptor
 -- @param      size  Bytes to copy (optional)
--- @return     bytes that were successfully written if no error occured
+-- @return     bytes that were successfully written if no error occurred
 -- @return     - reserved for error code -
 -- @return     - reserved for error message -
--- @return  bytes that were successfully written even if an error occured
+-- @return  bytes that were successfully written even if an error occurred
 
 --- Close the descriptor.
 -- @class function
 -- @name UnifiedIO.close
 -- @usage      If the descriptor is a TLS-socket the underlying descriptor is
 -- closed without touching the TLS connection.
--- @return     true
\ No newline at end of file
+-- @return     true
index 5bfd7a6..ef495f2 100644 (file)
@@ -47,7 +47,7 @@ module "nixio.fs"
 -- @name nixio.fs.rename
 -- @param      src     Source path
 -- @param      dest Destination path
--- @usage      It is normally not possible to rename files accross filesystems.
+-- @usage      It is normally not possible to rename files across filesystems.
 -- @return     true
 
 --- Remove an empty directory.
@@ -262,4 +262,4 @@ module "nixio.fs"
 -- omit the basename even if source and destination basename are equal. 
 -- @param      src     Source path
 -- @param      dest Destination path
--- @return     true
\ No newline at end of file
+-- @return     true
index 1b434d7..1a0d69a 100644 (file)
@@ -118,7 +118,7 @@ module "nixio"
 -- @param flag1 First Flag ["append", "creat", "excl", "nonblock", "ndelay",
 -- "sync", "trunc", "rdonly", "wronly", "rdwr"]
 -- @param ...  More Flags [-"-]
--- @return flag to be used as second paramter to open
+-- @return flag to be used as second parameter to open
 
 --- Duplicate a file descriptor.
 -- @class function
@@ -167,7 +167,7 @@ module "nixio"
 
 --- Wait for some event on a file descriptor.
 -- poll() sets the revents-field of the tables provided by fds to a bitfield
--- indicating the events that occured.
+-- indicating the events that occurred.
 -- @class function
 -- @usage This function works in-place on the provided table and only
 -- writes the revents field, you can use other fields on your demand.
@@ -303,7 +303,7 @@ module "nixio"
 --- Set or unset a environment variable.
 -- @class function
 -- @name nixio.setenv
--- @usage The environment variable will be unset if value is ommited.
+-- @usage The environment variable will be unset if value is omitted.
 -- @param variable     Variable
 -- @param value                Value (optional)
 -- @return true
index a7e02f3..55cdf8a 100644 (file)
@@ -196,23 +196,7 @@ function portrange(val)
 end
 
 function macaddr(val)
-       if val and val:match(
-               "^[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+:" ..
-                "[a-fA-F0-9]+:[a-fA-F0-9]+:[a-fA-F0-9]+$"
-       ) then
-               local parts = util.split( val, ":" )
-
-               for i = 1,6 do
-                       parts[i] = tonumber( parts[i], 16 )
-                       if parts[i] < 0 or parts[i] > 255 then
-                               return false
-                       end
-               end
-
-               return true
-       end
-
-       return false
+       return ip.checkmac(val) and true or false
 end
 
 function hostname(val)
index 67a60d9..19a0a34 100644 (file)
@@ -69,7 +69,7 @@ data line by line with the trailing \r\n stripped of.
 Decode a mime encoded http message body with multipart/form-data
 
 Content-Type. Stores all extracted data associated with its parameter name
-in the params table withing the given message object. Multiple parameter
+in the params table within the given message object. Multiple parameter
 values are stored as tables, ordinary ones as strings.
 If an optional file callback function is given then it is feeded with the
 file contents chunk by chunk and only the extracted file name is stored
@@ -92,7 +92,7 @@ with three arguments:
 Decode an urlencoded http message body with application/x-www-urlencoded
 
 Content-Type. Stores all extracted data associated with its parameter name
-in the params table withing the given message object. Multiple parameter
+in the params table within the given message object. Multiple parameter
 values are stored as tables, ordinary ones as strings.
 @class function
 @name urldecode_message_body
index 9ea8e36..056fc67 100644 (file)
@@ -330,7 +330,7 @@ function init(cursor)
                        if i.family == "packet" then
                                _interfaces[name].flags   = i.flags
                                _interfaces[name].stats   = i.data
-                               _interfaces[name].macaddr = i.addr
+                               _interfaces[name].macaddr = ipc.checkmac(i.addr)
                        elseif i.family == "inet" then
                                _interfaces[name].ipaddrs[#_interfaces[name].ipaddrs+1] = ipc.IPv4(i.addr, i.netmask)
                        elseif i.family == "inet6" then
@@ -543,6 +543,9 @@ end
 function del_network(self, n)
        local r = _uci:delete("network", n)
        if r then
+               _uci:delete_all("luci", "ifstate",
+                       function(s) return (s.interface == n) end)
+
                _uci:delete_all("network", "alias",
                        function(s) return (s.interface == n) end)
 
@@ -998,7 +1001,10 @@ function protocol.ip6addrs(self)
 
        if type(addrs) == "table" then
                for n, addr in ipairs(addrs) do
-                       if type(addr["local-address"]) == "table" then
+                       if type(addr["local-address"]) == "table" and
+                          type(addr["local-address"].mask) == "number" and
+                          type(addr["local-address"].address) == "string"
+                       then
                                rv[#rv+1] = "%s/%d" %{
                                        addr["local-address"].address,
                                        addr["local-address"].mask
@@ -1227,8 +1233,7 @@ function interface.name(self)
 end
 
 function interface.mac(self)
-       local mac = self:_ubus("macaddr")
-       return mac and mac:upper()
+       return ipc.checkmac(self:_ubus("macaddr"))
 end
 
 function interface.ipaddrs(self)
index 84c747f..12b20e4 100644 (file)
@@ -138,17 +138,22 @@ local function _nethints(what, callback)
 
        luci.ip.neighbors(nil, function(neigh)
                if neigh.mac and neigh.family == 4 then
-                       _add(what, neigh.mac:upper(), neigh.dest:string(), nil, nil)
+                       _add(what, neigh.mac:string(), neigh.dest:string(), nil, nil)
                elseif neigh.mac and neigh.family == 6 then
-                       _add(what, neigh.mac:upper(), nil, neigh.dest:string(), nil)
+                       _add(what, neigh.mac:string(), nil, neigh.dest:string(), nil)
                end
        end)
 
        if fs.access("/etc/ethers") then
                for e in io.lines("/etc/ethers") do
-                       mac, ip = e:match("^([a-f0-9]%S+) (%S+)")
-                       if mac and ip then
-                               _add(what, mac:upper(), ip, nil, nil)
+                       mac, name = e:match("^([a-fA-F0-9:-]+)%s+(%S+)")
+                       mac = luci.ip.checkmac(mac)
+                       if mac and name then
+                               if luci.ip.checkip4(name) then
+                                       _add(what, mac, name, nil, nil)
+                               else
+                                       _add(what, mac, nil, nil, name)
+                               end
                        end
                end
        end
@@ -158,8 +163,9 @@ local function _nethints(what, callback)
                        if s.leasefile and fs.access(s.leasefile) then
                                for e in io.lines(s.leasefile) do
                                        mac, ip, name = e:match("^%d+ (%S+) (%S+) (%S+)")
+                                       mac = luci.ip.checkmac(mac)
                                        if mac and ip then
-                                               _add(what, mac:upper(), ip, nil, name ~= "*" and name)
+                                               _add(what, mac, ip, nil, name ~= "*" and name)
                                        end
                                end
                        end
@@ -169,7 +175,10 @@ local function _nethints(what, callback)
        cur:foreach("dhcp", "host",
                function(s)
                        for mac in luci.util.imatch(s.mac) do
-                               _add(what, mac:upper(), s.ip, nil, s.name)
+                               mac = luci.ip.checkmac(mac)
+                               if mac then
+                                       _add(what, mac, s.ip, nil, s.name)
+                               end
                        end
                end)
 
index 95ff46d..5012111 100644 (file)
@@ -4,6 +4,7 @@
 module("luci.tools.status", package.seeall)
 
 local uci = require "luci.model.uci".cursor()
+local ipc = require "luci.ip"
 
 local function dhcp_leases_common(family)
        local rv = { }
@@ -31,7 +32,7 @@ local function dhcp_leases_common(family)
                                        if family == 4 and not ip:match(":") then
                                                rv[#rv+1] = {
                                                        expires  = (expire ~= 0) and os.difftime(expire, os.time()),
-                                                       macaddr  = mac,
+                                                       macaddr  = ipc.checkmac(mac) or "00:00:00:00:00:00",
                                                        ipaddr   = ip,
                                                        hostname = (name ~= "*") and name
                                                }
@@ -74,19 +75,9 @@ local function dhcp_leases_common(family)
                                                hostname = (name ~= "-") and name
                                        }
                                elseif ip and iaid == "ipv4" and family == 4 then
-                                       local mac, mac1, mac2, mac3, mac4, mac5, mac6
-                                       if duid and type(duid) == "string" then
-                                                mac1, mac2, mac3, mac4, mac5, mac6 = duid:match("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$")
-                                       end
-                                       if not (mac1 and mac2 and mac3 and mac4 and mac5 and mac6) then
-                                               mac = "FF:FF:FF:FF:FF:FF"
-                                       else
-                                               mac = mac1..":"..mac2..":"..mac3..":"..mac4..":"..mac5..":"..mac6
-                                       end
                                        rv[#rv+1] = {
                                                expires  = (expire >= 0) and os.difftime(expire, os.time()),
-                                               macaddr  = duid,
-                                               macaddr  = mac:lower(),
+                                               macaddr  = ipc.checkmac(duid:gsub("^(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)(%x%x)$", "%1:%2:%3:%4:%5:%6")) or "00:00:00:00:00:00",
                                                ipaddr   = ip,
                                                hostname = (name ~= "-") and name
                                        }
index 805eeb7..949aeb2 100644 (file)
@@ -109,13 +109,13 @@ Remove leading and trailing whitespace from given string value.
 ]]
 
 ---[[
-Count the occurences of given substring in given string.
+Count the occurrences of given substring in given string.
 
 @class function
 @name cmatch
 @param str             String to search in
 @param pattern String containing pattern to find
-@return                        Number of found occurences
+@return                        Number of found occurrences
 ]]
 
 ---[[
index a181201..2abc975 100644 (file)
@@ -2,6 +2,7 @@
 <%+cbi/valueheader%>
        <input class="cbi-input-text" type="text"<%= attr("value", v) .. attr("name", cbid) .. attr("id", cbid) %> />
        <script type="text/javascript">
+cbi_init()
 cbi_browser_init('<%=cbid%>', '<%=resource%>', '<%=url('admin/filebrowser')%>'<%=self.default_path and ", '"..self.default_path.."'"%>);
        </script>
 <%+cbi/valuefooter%>
index 5cb3151..b426070 100644 (file)
        end
 -%>
 
-<ul style="margin:0; list-style-type:none; text-align:left">
-       <% if self.allowlocal then %>
-       <li style="padding:0.5em">
-               <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_empty") .. attr("name", cbid) .. attr("value", "") .. ifattr(checked[""], "checked", "checked")%> /> &#160;
-               <label<%=attr("for", cbid .. "_empty")%>></label>
-               <label<%=attr("for", cbid .. "_empty")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
-                       <strong><%:Device%></strong>
-                       <% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %>
-               </label>
-       </li>
-       <% end %>
-       <% if self.allowany then %>
-       <li style="padding:0.5em">
-               <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_any") .. attr("name", cbid) .. attr("value", "*") .. ifattr(checked["*"], "checked", "checked")%> /> &#160;
-               <label<%=attr("for", cbid .. "_any")%>></label>
-               <label<%=attr("for", cbid .. "_any")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
-                       <strong><%:Any zone%></strong>
-                       <% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %>
-               </label>
-       </li>
-       <% end %>
-       <%
-               for _, zone in utl.spairs(zones, function(a,b) return (zones[a]:name() < zones[b]:name()) end) do
-                       if zone:name() ~= self.exclude then
-                               selected = selected or (value == zone:name())
-       %>
-       <li style="padding:0.5em">
-               <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "." .. zone:name()) .. attr("name", cbid) .. attr("value", zone:name()) .. ifattr(checked[zone:name()], "checked", "checked")%> /> &#160;
-               <label<%=attr("for", cbid .. "." .. zone:name())%>></label>
-               <label<%=attr("for", cbid .. "." .. zone:name())%> style="background-color:<%=zone:get_color()%>" class="zonebadge">
-                       <strong><%=zone:name()%>:</strong>
-                       <%
-                               local zempty = true
-                               for _, net in ipairs(zone:get_networks()) do
-                                       net = nwm:get_network(net)
-                                       if net then
-                                               zempty = false
-                       %>
-                               <span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>:
+<span>
+       <ul style="margin:0; list-style-type:none; text-align:left">
+               <% if self.allowlocal then %>
+               <li style="padding:0.5em">
+                       <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_empty") .. attr("name", cbid) .. attr("value", "") .. ifattr(checked[""], "checked", "checked")%> /> &#160;
+                       <label<%=attr("for", cbid .. "_empty")%>></label>
+                       <label<%=attr("for", cbid .. "_empty")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
+                               <strong><%:Device%></strong>
+                               <% if self.allowany and self.allowlocal then %>(<%:input%>)<% end %>
+                       </label>
+               </li>
+               <% end %>
+               <% if self.allowany then %>
+               <li style="padding:0.5em">
+                       <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "_any") .. attr("name", cbid) .. attr("value", "*") .. ifattr(checked["*"], "checked", "checked")%> /> &#160;
+                       <label<%=attr("for", cbid .. "_any")%>></label>
+                       <label<%=attr("for", cbid .. "_any")%> style="background-color:<%=fwm.zone.get_color()%>" class="zonebadge">
+                               <strong><%:Any zone%></strong>
+                               <% if self.allowany and self.allowlocal then %>(<%:forward%>)<% end %>
+                       </label>
+               </li>
+               <% end %>
+               <%
+                       for _, zone in utl.spairs(zones, function(a,b) return (zones[a]:name() < zones[b]:name()) end) do
+                               if zone:name() ~= self.exclude then
+                                       selected = selected or (value == zone:name())
+               %>
+               <li style="padding:0.5em">
+                       <input class="cbi-input-radio" data-update="click change"<%=attr("type", self.widget or "radio") .. attr("id", cbid .. "." .. zone:name()) .. attr("name", cbid) .. attr("value", zone:name()) .. ifattr(checked[zone:name()], "checked", "checked")%> /> &#160;
+                       <label<%=attr("for", cbid .. "." .. zone:name())%>></label>
+                       <label<%=attr("for", cbid .. "." .. zone:name())%> style="background-color:<%=zone:get_color()%>" class="zonebadge">
+                               <strong><%=zone:name()%>:</strong>
                                <%
-                                       local nempty = true
-                                       for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
-                                               nempty = false
-                                %>
-                                       <img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
-                               <% end %>
-                               <% if nempty then %><em><%:(empty)%></em><% end %>
-                               </span>
-                       <% end end %>
-                       <% if zempty then %><em><%:(empty)%></em><% end %>
-               </label>
-       </li>
-       <% end end %>
+                                       local zempty = true
+                                       for _, net in ipairs(zone:get_networks()) do
+                                               net = nwm:get_network(net)
+                                               if net then
+                                                       zempty = false
+                               %>
+                                       <span class="ifacebadge<% if net:name() == self.network then %> ifacebadge-active<% end %>"><%=net:name()%>:
+                                       <%
+                                               local nempty = true
+                                               for _, iface in ipairs(net:is_bridge() and net:get_interfaces() or { net:get_interface() }) do
+                                                       nempty = false
+                                       %>
+                                               <img<%=attr("title", iface:get_i18n())%> style="width:16px; height:16px; vertical-align:middle" src="<%=resource%>/icons/<%=iface:type()%><%=iface:is_up() and "" or "_disabled"%>.png" />
+                                       <% end %>
+                                       <% if nempty then %><em><%:(empty)%></em><% end %>
+                                       </span>
+                               <% end end %>
+                               <% if zempty then %><em><%:(empty)%></em><% end %>
+                       </label>
+               </li>
+               <% end end %>
 
-       <% if self.widget ~= "checkbox" and not self.nocreate then %>
-       <li style="padding:0.5em">
-               <input class="cbi-input-radio" data-update="click change" type="radio"<%=attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not selected, "checked", "checked")%> /> &#160;
-               <label<%=attr("for", cbid .. "_new")%>></label>
-               <div onclick="document.getElementById('<%=cbid%>_new').checked=true" class="zonebadge" style="background-color:<%=fwm.zone.get_color()%>">
-                       <em><%:unspecified -or- create:%>&#160;</em>
-                       <input type="text"<%=attr("name", cbid .. ".newzone") .. ifattr(not selected, "value", luci.http.formvalue(cbid .. ".newzone") or self.default)%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" />
-               </div>
-       </li>
-       <% end %>
-</ul>
+               <% if self.widget ~= "checkbox" and not self.nocreate then %>
+               <li style="padding:0.5em">
+                       <input class="cbi-input-radio" data-update="click change" type="radio"<%=attr("id", cbid .. "_new") .. attr("name", cbid) .. attr("value", "-") .. ifattr(not selected, "checked", "checked")%> /> &#160;
+                       <label<%=attr("for", cbid .. "_new")%>></label>
+                       <div onclick="document.getElementById('<%=cbid%>_new').checked=true" class="zonebadge" style="background-color:<%=fwm.zone.get_color()%>">
+                               <em><%:unspecified -or- create:%>&#160;</em>
+                               <input type="text"<%=attr("name", cbid .. ".newzone") .. ifattr(not selected, "value", luci.http.formvalue(cbid .. ".newzone") or self.default)%> onfocus="document.getElementById('<%=cbid%>_new').checked=true" />
+                       </div>
+               </li>
+               <% end %>
+       </ul>
+</span>
 
 <%+cbi/valuefooter%>
index 2cb2108..33f6a67 100644 (file)
@@ -258,7 +258,6 @@ function iface_status(ifaces)
                                        type       = device:type(),
                                        ifname     = device:name(),
                                        macaddr    = device:mac(),
-                                       macaddr    = device:mac(),
                                        is_up      = device:is_up(),
                                        rx_bytes   = device:rx_bytes(),
                                        tx_bytes   = device:tx_bytes(),
index 2acda0b..66d9942 100644 (file)
@@ -2,6 +2,7 @@
 -- Licensed to the public under the Apache License 2.0.
 
 local ipc = require "luci.ip"
+local sys = require "luci.sys"
 local o
 require "luci.util"
 
@@ -311,10 +312,10 @@ end
 
 hostid = s:option(Value, "hostid", translate("<abbr title=\"Internet Protocol Version 6\">IPv6</abbr>-Suffix (hex)"))
 
-ipc.neighbors({ family = 4 }, function(n)
-       if n.mac and n.dest then
-               ip:value(n.dest:string())
-               mac:value(n.mac, "%s (%s)" %{ n.mac, n.dest:string() })
+sys.net.host_hints(function(m, v4, v6, name)
+       if m and v4 then
+               ip:value(v4)
+               mac:value(m, "%s (%s)" %{ m, name or v4 })
        end
 end)
 
index fafacf3..46945af 100644 (file)
@@ -3,6 +3,7 @@
 -- Licensed to the public under the Apache License 2.0.
 
 local ipc = require "luci.ip"
+local sys = require "luci.sys"
 
 m = Map("dhcp", translate("Hostnames"))
 
@@ -19,9 +20,11 @@ ip = s:option(Value, "ip", translate("IP address"))
 ip.datatype = "ipaddr"
 ip.rmempty  = true
 
-ipc.neighbors({ }, function(n)
-       if n.mac and n.dest and not n.dest:is6linklocal() then
-               ip:value(n.dest:string(), "%s (%s)" %{ n.dest:string(), n.mac })
+sys.net.host_hints(function(mac, v4, v6, name)
+       v6 = v6 and ipc.IPv6(v6)
+
+       if v4 or (v6 and not v6:is6linklocal()) then
+               ip:value(tostring(v4 or v6), "%s (%s)" %{ tostring(v4 or v6), name or mac })
        end
 end)
 
index 4fc71ce..8e7a3b0 100644 (file)
@@ -16,6 +16,7 @@ local has_firewall = fs.access("/etc/config/firewall")
 m = Map("network", translate("Interfaces") .. " - " .. arg[1]:upper(), translate("On this page you can configure the network interfaces. You can bridge several interfaces by ticking the \"bridge interfaces\" field and enter the names of several network interfaces separated by spaces. You can also use <abbr title=\"Virtual Local Area Network\">VLAN</abbr> notation <samp>INTERFACE.VLANNR</samp> (<abbr title=\"for example\">e.g.</abbr>: <samp>eth0.1</samp>)."))
 m.redirect = luci.dispatcher.build_url("admin", "network", "network")
 m:chain("wireless")
+m:chain("luci")
 
 if has_firewall then
        m:chain("firewall")
@@ -27,18 +28,52 @@ fw.init(m.uci)
 
 local net = nw:get_network(arg[1])
 
+local function set_ifstate(name, option, value)
+       local found = false
+
+       m.uci:foreach("luci", "ifstate", function (s)
+               if s.interface == name then
+                       m.uci:set("luci", s[".name"], option, value)
+                       found = true
+                       return false
+               end
+       end)
+
+       if not found then
+               local sid = m.uci:add("luci", "ifstate")
+               m.uci:set("luci", sid, "interface", name)
+               m.uci:set("luci", sid, option, value)
+       end
+
+       m.uci:save("luci")
+end
+
+local function get_ifstate(name, option)
+       local val
+
+       m.uci:foreach("luci", "ifstate", function (s)
+               if s.interface == name then
+                       val = m.uci:get("luci", s[".name"], option)
+                       return false
+               end
+       end)
+
+       return val
+end
+
 local function backup_ifnames(is_bridge)
-       if not net:is_floating() and not m:get(net:name(), "_orig_ifname") then
+       if not net:is_floating() and not get_ifstate(net:name(), "ifname") then
                local ifcs = net:get_interfaces() or { net:get_interface() }
                if ifcs then
                        local _, ifn
                        local ifns = { }
                        for _, ifn in ipairs(ifcs) do
-                               ifns[#ifns+1] = ifn:name()
+                               local wif = ifn:get_wifinet()
+                               ifns[#ifns+1] = wif and wif:id() or ifn:name()
                        end
                        if #ifns > 0 then
-                               m:set(net:name(), "_orig_ifname", table.concat(ifns, " "))
-                               m:set(net:name(), "_orig_bridge", tostring(net:is_bridge()))
+                               set_ifstate(net:name(), "ifname", table.concat(ifns, " "))
+                               set_ifstate(net:name(), "bridge", tostring(net:is_bridge()))
                        end
                end
        end
@@ -84,10 +119,10 @@ if m:formvalue("cbid.network.%s._switch" % net:name()) then
                elseif net:is_floating() and not proto:is_floating() then
                        -- if we have backup data, then re-add all orphaned interfaces
                        -- from it and restore the bridge choice
-                       local br = (m:get(net:name(), "_orig_bridge") == "true")
+                       local br = (get_ifstate(net:name(), "bridge") == "true")
                        local ifn
                        local ifns = { }
-                       for ifn in ut.imatch(m:get(net:name(), "_orig_ifname")) do
+                       for ifn in ut.imatch(get_ifstate(net:name(), "ifname")) do
                                ifn = nw:get_interface(ifn)
                                if ifn and not ifn:get_network() then
                                        proto:add_interface(ifn)
@@ -114,9 +149,7 @@ if m:formvalue("cbid.network.%s._switch" % net:name()) then
                for k, v in pairs(m:get(net:name())) do
                        if k:sub(1,1) ~= "." and
                           k ~= "type" and
-                          k ~= "ifname" and
-                          k ~= "_orig_ifname" and
-                          k ~= "_orig_bridge"
+                          k ~= "ifname"
                        then
                                m:del(net:name(), k)
                        end
index 5f2c074..f474c71 100644 (file)
@@ -53,7 +53,7 @@
                                <tr class="cbi-section-table-row cbi-rowstyle-<%=(style and 1 or 2)%>">
                                        <td class="cbi-value-field"><%=v.dest%></td>
                                        <td class="cbi-value-field"><%=v.mac%></td>
-                                       <td class="cbi-value-field"><%=v.dev%></td>
+                                       <td class="cbi-value-field"><%=luci.tools.webadmin.iface_get_network(v.dev) or '(' .. v.dev .. ')'%></td>
                                </tr>
                                <%
                                                        style = not style
index 3e3f65d..b32ef78 100644 (file)
@@ -54,6 +54,9 @@
                                </div>
                        </form>
                </div>
+               <% if reset_avail then %>
+               <div class="alert-message warning"><%:Custom files (certificates, scripts) may remain on the system. To prevent this, perform a factory-reset first.%></div>
+               <% end %>
        </fieldset>
 
        <br />
index 9a1c1fe..bcc26cd 100644 (file)
@@ -3,7 +3,7 @@
 -- Licensed to the public under the Apache License 2.0.
 
 local uci = require "luci.model.uci".cursor()
-local ipc = require "luci.ip"
+local sys = require "luci.sys"
 local wa  = require "luci.tools.webadmin"
 local fs  = require "nixio.fs"
 
@@ -87,12 +87,11 @@ name = s2:option(Value, "name", translate("Hostname"))
 mac = s2:option(Value, "mac", translate("<abbr title=\"Media Access Control\">MAC</abbr>-Address"))
 ip = s2:option(Value, "ip", translate("<abbr title=\"Internet Protocol Version 4\">IPv4</abbr>-Address"))
 
-ipc.neighbors({ family = 4 }, function(n)
-       if n.mac and n.dest then
-               ip:value(n.dest:string())
-               mac:value(n.mac, "%s (%s)" %{ n.mac, n.dest:string() })
+sys.host_hints(function(m, v4, v6, name)
+       if m and v4 then
+               ip:value(v4)
+               mac:value(m, "%s (%s)" %{ m, name or v4 })
        end
 end)
 
 return m
-