diff --git a/src/apps/intel/intel_app.lua b/src/apps/intel/intel_app.lua index 92f97d00fd..c85c2e5530 100644 --- a/src/apps/intel/intel_app.lua +++ b/src/apps/intel/intel_app.lua @@ -73,7 +73,7 @@ function Intel82599:new (arg) counter.set(self.stats.counters.type, 0x1000) -- Hardware interface counter.set(self.stats.counters.dtime, C.get_unix_time()) counter.set(self.stats.counters.mtu, self.dev.mtu) - counter.set(self.stats.counters.speed, 10000000) -- 10 Gbits + counter.set(self.stats.counters.speed, 10000000000) -- 10 Gbits counter.set(self.stats.counters.status, 2) -- down if not conf.vmdq and conf.macaddr then counter.set(self.stats.counters.macaddr, diff --git a/src/apps/ipsec/esp.lua b/src/apps/ipsec/esp.lua index 9c2121320a..f468b883ad 100644 --- a/src/apps/ipsec/esp.lua +++ b/src/apps/ipsec/esp.lua @@ -5,9 +5,15 @@ module(..., package.seeall) local esp = require("lib.ipsec.esp") +local counter = require("core.counter") +local C = require("ffi").C AES128gcm = {} +local provided_counters = { + 'type', 'dtime', 'txerrors', 'rxerrors' +} + function AES128gcm:new (arg) local conf = arg and config.parse_app_arg(arg) or {} local self = {} @@ -22,6 +28,12 @@ function AES128gcm:new (arg) keymat = conf.key:sub(1, 32), salt = conf.key:sub(33, 40), window_size = conf.replay_window} + self.counters = {} + for _, name in ipairs(provided_counters) do + self.counters[name] = counter.open(name) + end + counter.set(self.counters.type, 0x1001) -- Virtual interface + counter.set(self.counters.dtime, C.get_unix_time()) return setmetatable(self, {__index = AES128gcm}) end @@ -31,15 +43,28 @@ function AES128gcm:push () local output = self.output.encapsulated for _=1,link.nreadable(input) do local p = link.receive(input) - if self.encrypt:encapsulate(p) then link.transmit(output, p) - else packet.free(p) end + if self.encrypt:encapsulate(p) then + link.transmit(output, p) + else + packet.free(p) + counter.add(self.counters.txerrors) + end end -- Decapsulation path local input = self.input.encapsulated local output = self.output.decapsulated for _=1,link.nreadable(input) do local p = link.receive(input) - if self.decrypt:decapsulate(p) then link.transmit(output, p) - else packet.free(p) end + if self.decrypt:decapsulate(p) then + link.transmit(output, p) + else + packet.free(p) + counter.add(self.counters.rxerrors) + end end end + +function AES128gcm:stop () + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end +end diff --git a/src/apps/ipv6/README.md b/src/apps/ipv6/README.md index 2ecdec57f1..e34836e4d0 100644 --- a/src/apps/ipv6/README.md +++ b/src/apps/ipv6/README.md @@ -56,6 +56,30 @@ milliseconds. Default is 1,000ms. *Optional*. Number of neighbor solicitation retransmissions. Default is unlimited retransmissions. +### Special Counters + +— Key **ns_checksum_errors** + +Neighbor solicitation requests dropped due to invalid ICMP checksum. + +— Key **ns_target_address_errors** + +Neighbor solicitation requests dropped due to invalid target address. + +— Key **na_duplicate_errors** + +Neighbor advertisement requests dropped because next-hop is already resolved. + +— Key **na_target_address_errors** + +Neighbor advertisement requests dropped due to invalid target address. + +— Key **nd_protocol_errors** + +Neighbor discovery requests dropped due to protocol errors (invalid IPv6 +hop-limit or invalid neighbor solicitation request options). + + ## SimpleKeyedTunnel (apps.keyed_ipv6_tunnel.tunnel) The `SimpleKeyedTunnel` app implements "a simple L2 Ethernet over IPv6 @@ -115,3 +139,26 @@ the L2TPv3 header will be overwritten with this value. *Optional*. Destination MAC as a string. Not required if overwritten by an app such as `nd_light`. + + +### Special Counters + +— Key **length_errors** + +Ingress packets dropped due to invalid length (packet too short). + +— Key **protocol_errors** + +Ingress packets dropped due to unrecognized IPv6 protocol ID. + +— Key **cookie_errors** + +Ingress packets dropped due to wrong cookie value. + +— Key **remote_address_errors** + +Ingress packets dropped due to wrong remote IPv6 endpoint address. + +— Key **local_address_errors** + +Ingress packets dropped due to wrong local IPv6 endpoint address. diff --git a/src/apps/ipv6/nd_light.lua b/src/apps/ipv6/nd_light.lua index 152e55455e..2429f42782 100644 --- a/src/apps/ipv6/nd_light.lua +++ b/src/apps/ipv6/nd_light.lua @@ -36,6 +36,7 @@ local app = require("core.app") local link = require("core.link") local config = require("core.config") local packet = require("core.packet") +local counter = require("core.counter") local datagram = require("lib.protocol.datagram") local ethernet = require("lib.protocol.ethernet") local ipv6 = require("lib.protocol.ipv6") @@ -78,6 +79,13 @@ local function check_ip_address(ip, desc) return ip end +local provided_counters = { + 'type', 'dtime', 'status', 'rxerrors', 'txerrors', 'txdrop', + 'ns_checksum_errors', 'ns_target_address_errors', + 'na_duplicate_errors', 'na_target_address_errors', + 'nd_protocol_errors' +} + function nd_light:new (arg) local arg = arg and config.parse_app_arg(arg) or {} --copy the args to avoid changing the arg table so that it stays reusable. @@ -201,6 +209,16 @@ function nd_light:new (arg) mem = ffi.new("uint8_t *[1]") } o._logger = lib.logger_new({ module = 'nd_light' }) + + -- Create counters + o.counters = {} + for _, name in ipairs(provided_counters) do + o.counters[name] = counter.open(name) + end + counter.set(o.counters.type, 0x1001) -- Virtual interface + counter.set(o.counters.dtime, C.get_unix_time()) + counter.set(o.counters.status, 2) -- Link down + return o end @@ -209,13 +227,16 @@ local function ns (self, dgram, eth, ipv6, icmp) local mem, length = self._cache.mem mem[0], length = dgram:payload() if not icmp:checksum_check(mem[0], length, ipv6) then - self._logger:log("bad icmp checksum") + counter.add(self.counters.ns_checksum_errors) + counter.add(self.counters.rxerrors) return nil end -- Parse the neighbor solicitation and check if it contains our own -- address as target local ns = dgram:parse_match(nil, self._match_ns) if not ns then + counter.add(self.counters.ns_target_address_errors) + counter.add(self.counters.rxerrors) return nil end -- Ignore options as long as we don't implement a proper neighbor @@ -236,15 +257,21 @@ end -- Process neighbor advertisement local function na (self, dgram, eth, ipv6, icmp) if self._eth_header then + counter.add(self.counters.na_duplicate_errors) + counter.add(self.counters.rxerrors) return nil end local na = dgram:parse_match(nil, self._match_na) if not na then + counter.add(self.counters.na_target_address_errors) + counter.add(self.counters.rxerrors) return nil end local option = na:options(dgram:payload()) if not (#option == 1 and option[1]:type() == 2) then -- Invalid NS, ignore + counter.add(self.counters.nd_protocol_errors) + counter.add(self.counters.rxerrors) return nil end self._eth_header = ethernet:new({ src = self._config.local_mac, @@ -252,6 +279,7 @@ local function na (self, dgram, eth, ipv6, icmp) type = 0x86dd }) self._logger:log(string.format("Resolved next-hop %s to %s", ipv6:ntop(self._config.next_hop), ethernet:ntop(option[1]:option():addr()))) + counter.set(self.counters.status, 1) -- Link up return nil end @@ -265,6 +293,8 @@ local function from_south (self, p) local eth, ipv6, icmp = unpack(dgram:stack()) if ipv6:hop_limit() ~= 255 then -- Avoid off-link spoofing as per RFC + counter.add(self.counters.nd_protocol_errors) + counter.add(self.counters.rxerrors) return nil end local result @@ -311,13 +341,17 @@ function nd_light:push () -- Drop packets until ND for the next-hop -- has completed. packet.free(link.receive(l_in)) + counter.add(self.counters.txdrop) else local p = cache.p p[0] = link.receive(l_in) if p[0].length >= self._eth_header:sizeof() then self._eth_header:copy(p[0].data) link.transmit(l_out, p[0]) - else packet.free(p[0]) end + else + packet.free(p[0]) + counter.add(self.counters.txerrors) + end end end end @@ -328,6 +362,8 @@ function nd_light:stop () self._next_hop.packet = nil packet.free(self._sna.packet) self._sna.packet = nil + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end function selftest () diff --git a/src/apps/keyed_ipv6_tunnel/tunnel.lua b/src/apps/keyed_ipv6_tunnel/tunnel.lua index 9e394cfcfc..e29951fc10 100644 --- a/src/apps/keyed_ipv6_tunnel/tunnel.lua +++ b/src/apps/keyed_ipv6_tunnel/tunnel.lua @@ -16,6 +16,7 @@ local link = require("core.link") local lib = require("core.lib") local packet = require("core.packet") local config = require("core.config") +local counter = require("core.counter") local macaddress = require("lib.macaddress") @@ -102,6 +103,12 @@ end SimpleKeyedTunnel = {} +local provided_counters = { + 'type', 'dtime', 'rxerrors', + 'length_errors', 'protocol_errors', 'cookie_errors', + 'remote_address_errors', 'local_address_errors' +} + function SimpleKeyedTunnel:new (arg) local conf = arg and config.parse_app_arg(arg) or {} -- required fields: @@ -163,12 +170,20 @@ function SimpleKeyedTunnel:new (arg) header[HOP_LIMIT_OFFSET] = conf.hop_limit end + local counters = {} + for _, name in ipairs(provided_counters) do + counters[name] = counter.open(name) + end + counter.set(counters.type, 0x1001) -- Virtual interface + counter.set(counters.dtime, C.get_unix_time()) + local o = { header = header, remote_address = remote_address, local_address = local_address, - remote_cookie = remote_cookie[0] + remote_cookie = remote_cookie[0], + counters = counters } return setmetatable(o, {__index = SimpleKeyedTunnel}) @@ -198,15 +213,18 @@ function SimpleKeyedTunnel:push() local drop = true repeat if p.length < HEADER_SIZE then + counter.add(self.counters.length_errors) break end local next_header = ffi.cast(next_header_ctype, p.data + NEXT_HEADER_OFFSET) if next_header[0] ~= L2TPV3_NEXT_HEADER then + counter.add(self.counters.protocol_errors) break end local cookie = ffi.cast(pcookie_ctype, p.data + COOKIE_OFFSET) if cookie[0] ~= self.remote_cookie then + counter.add(self.counters.cookie_errors) break end @@ -214,6 +232,7 @@ function SimpleKeyedTunnel:push() if remote_address[0] ~= self.remote_address[0] or remote_address[1] ~= self.remote_address[1] then + counter.add(self.counters.remote_address_errors) break end @@ -221,6 +240,7 @@ function SimpleKeyedTunnel:push() if local_address[0] ~= self.local_address[0] or local_address[1] ~= self.local_address[1] then + counter.add(self.counters.local_address_errors) break end @@ -228,6 +248,7 @@ function SimpleKeyedTunnel:push() until true if drop then + counter.add(self.counters.rxerrors) -- discard packet packet.free(p) else @@ -237,6 +258,11 @@ function SimpleKeyedTunnel:push() end end +function SimpleKeyedTunnel:stop () + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end +end + -- prepare header template to be used by all apps prepare_header_template() diff --git a/src/apps/packet_filter/README.md b/src/apps/packet_filter/README.md index 084b646b87..e94afcc4c7 100644 --- a/src/apps/packet_filter/README.md +++ b/src/apps/packet_filter/README.md @@ -33,3 +33,9 @@ expression. *rule* will be tracked in the specified state table and any packet that belongs to a tracked connection in the specified state table will be let pass. + +## Special Counters + +— Key **sessions_established** + +Total number of sessions established. diff --git a/src/apps/packet_filter/pcap_filter.lua b/src/apps/packet_filter/pcap_filter.lua index 6ee4eabb52..43e1a3917d 100644 --- a/src/apps/packet_filter/pcap_filter.lua +++ b/src/apps/packet_filter/pcap_filter.lua @@ -7,12 +7,18 @@ local link = require("core.link") local lib = require("core.lib") local packet = require("core.packet") local config = require("core.config") +local counter = require("core.counter") local conntrack = require("apps.packet_filter.conntrack") +local C = require("ffi").C local pf = require("pf") -- pflua PcapFilter = {} +local provided_counters = { + 'dtime', 'type', 'rxerrors', 'sessions_established' +} + -- PcapFilter is an app that drops all packets that don't match a -- specified filter expression. -- @@ -33,6 +39,13 @@ function PcapFilter:new (conf) state_table = conf.state_table or false } if conf.state_table then conntrack.define(conf.state_table) end + -- Create counters + o.counters = {} + for _, name in ipairs(provided_counters) do + o.counters[name] = counter.open(name) + end + counter.set(o.counters.type, 0x1001) -- Virtual interface + counter.set(o.counters.dtime, C.get_unix_time()) return setmetatable(o, { __index = PcapFilter }) end @@ -47,14 +60,23 @@ function PcapFilter:push () if spec and spec:check(self.state_table) then link.transmit(o, p) elseif self.accept_fn(p.data, p.length) then - if spec then spec:track(self.state_table) end + if spec then + spec:track(self.state_table) + counter.add(self.counters.sessions_established) + end link.transmit(o, p) else packet.free(p) + counter.add(self.counters.rxerrors) end end end +function PcapFilter:stop () + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end +end + -- Testing local pcap = require("apps.pcap.pcap") diff --git a/src/apps/rate_limiter/rate_limiter.lua b/src/apps/rate_limiter/rate_limiter.lua index 98c1cd9347..421e5ec3e0 100644 --- a/src/apps/rate_limiter/rate_limiter.lua +++ b/src/apps/rate_limiter/rate_limiter.lua @@ -7,6 +7,7 @@ local link = require("core.link") local config = require("core.config") local packet = require("core.packet") local timer = require("core.timer") +local counter = require("core.counter") local basic_apps = require("apps.basic.basic_apps") local ffi = require("ffi") local C = ffi.C @@ -22,6 +23,10 @@ local floor, min = math.floor, math.min RateLimiter = {} +local provided_counters = { + 'type', 'dtime', 'txdrop' +} + -- Source produces synthetic packets of such size local PACKET_SIZE = 60 @@ -30,11 +35,18 @@ function RateLimiter:new (arg) assert(conf.rate) assert(conf.bucket_capacity) conf.initial_capacity = conf.initial_capacity or conf.bucket_capacity + local counters = {} + for _, name in ipairs(provided_counters) do + counters[name] = counter.open(name) + end + counter.set(counters.type, 0x1001) -- Virtual interface + counter.set(counters.dtime, C.get_unix_time()) local o = { rate = conf.rate, bucket_capacity = conf.bucket_capacity, - bucket_content = conf.initial_capacity + bucket_content = conf.initial_capacity, + counters = counters } return setmetatable(o, {__index=RateLimiter}) end @@ -81,11 +93,17 @@ function RateLimiter:push () link.transmit(o, p) else -- discard packet + counter.add(self.counters.txdrop) packet.free(p) end end end +function RateLimiter:stop () + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end +end + local function compute_effective_rate (rl, rate, snapshot) local elapsed_time = (tonumber(C.get_time_ns()) - snapshot.time) / 1e9 diff --git a/src/apps/socket/raw.lua b/src/apps/socket/raw.lua index 24ad578d5d..6cf60c7e33 100644 --- a/src/apps/socket/raw.lua +++ b/src/apps/socket/raw.lua @@ -7,6 +7,8 @@ local h = require("syscall.helpers") local bit = require("bit") local link = require("core.link") local packet = require("core.packet") +local counter = require("core.counter") +local ethernet = require("lib.protocol.ethernet") local ffi = require("ffi") local C = ffi.C @@ -21,6 +23,12 @@ local c, t = S.c, S.types.t RawSocket = {} +local provided_counters = { + 'type', 'dtime', + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', + 'txbytes', 'txpackets', 'txmcast', 'txbcast' +} + function RawSocket:new (ifname) assert(ifname) local index, err = S.util.if_nametoindex(ifname) @@ -40,7 +48,16 @@ function RawSocket:new (ifname) sock:close() error(err) end - return setmetatable({sock = sock}, {__index = RawSocket}) + local counters = {} + for _, name in ipairs(provided_counters) do + counters[name] = counter.open(name) + end + counter.set(counters.type, 0x1001) -- Virtual interface + counter.set(counters.dtime, C.get_unix_time()) + return setmetatable({sock = sock, + rx_p = packet.allocate(), + counters = counters}, + {__index = RawSocket}) end function RawSocket:pull () @@ -61,10 +78,18 @@ function RawSocket:can_receive () end function RawSocket:receive () - local buffer = ffi.new("uint8_t[?]", C.PACKET_PAYLOAD_SIZE) - local sz, err = S.read(self.sock, buffer, C.PACKET_PAYLOAD_SIZE) - assert(sz, err) - return packet.from_pointer(buffer, sz) + local p = self.rx_p + local sz = assert(S.read(self.sock, p.data, packet.max_payload)) + p.length = sz + counter.add(self.counters.rxbytes, sz) + counter.add(self.counters.rxpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.rxmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.rxbcast) + end + return packet.clone(p) end function RawSocket:push () @@ -73,6 +98,14 @@ function RawSocket:push () while not link.empty(l) and self:can_transmit() do local p = link.receive(l) self:transmit(p) + counter.add(self.counters.txbytes, p.length) + counter.add(self.counters.txpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.txmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.txbcast) + end packet.free(p) end end @@ -94,6 +127,9 @@ end function RawSocket:stop() self.sock:close() + packet.free(self.rx_p) + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end function selftest () diff --git a/src/apps/tap/tap.lua b/src/apps/tap/tap.lua index 497399d11a..9444e3f5a9 100644 --- a/src/apps/tap/tap.lua +++ b/src/apps/tap/tap.lua @@ -5,6 +5,8 @@ module(..., package.seeall) local S = require("syscall") local link = require("core.link") local packet = require("core.packet") +local counter = require("core.counter") +local ethernet = require("lib.protocol.ethernet") local ffi = require("ffi") local C = ffi.C local const = require("syscall.linux.constants") @@ -14,6 +16,12 @@ local t = S.types.t Tap = { } +local provided_counters = { + 'type', 'dtime', + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', + 'txbytes', 'txpackets', 'txmcast', 'txbcast' +} + function Tap:new (name) assert(name, "missing tap interface name") @@ -27,8 +35,14 @@ function Tap:new (name) sock:close() error("Error opening /dev/net/tun: " .. tostring(err)) end - - return setmetatable({sock = sock, name = name}, {__index = Tap}) + local counters = {} + for _, name in ipairs(provided_counters) do + counters[name] = counter.open(name) + end + counter.set(counters.type, 0x1001) -- Virtual interface + counter.set(counters.dtime, C.get_unix_time()) + return setmetatable({sock = sock, name = name, counters = counters}, + {__index = Tap}) end function Tap:pull () @@ -49,6 +63,14 @@ function Tap:pull () end p.length = len link.transmit(l, p) + counter.add(self.counters.rxbytes, len) + counter.add(self.counters.rxpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.rxmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.rxbcast) + end end end @@ -66,6 +88,14 @@ function Tap:push () if len ~= p.length and err.errno == const.E.AGAIN then return end + counter.add(self.counters.txbytes, len) + counter.add(self.counters.txpackets) + if ethernet:is_mcast(p.data) then + counter.add(self.counters.txmcast) + end + if ethernet:is_bcast(p.data) then + counter.add(self.counters.txbcast) + end -- The write completed so dequeue it from the link and free the packet link.receive(l) packet.free(p) @@ -74,6 +104,8 @@ end function Tap:stop() self.sock:close() + -- delete counters + for name, _ in pairs(self.counters) do counter.delete(name) end end function selftest() diff --git a/src/apps/vhost/vhost_user.lua b/src/apps/vhost/vhost_user.lua index 22f605d1b2..98d4f66d5f 100644 --- a/src/apps/vhost/vhost_user.lua +++ b/src/apps/vhost/vhost_user.lua @@ -31,8 +31,8 @@ VhostUser = {} local provided_counters = { 'type', 'dtime', - 'rxbytes', 'rxpackets', 'rxmcast', 'rxdrop', - 'txbytes', 'txpackets', 'txmcast' + 'rxbytes', 'rxpackets', 'rxmcast', 'rxbcast', 'rxdrop', + 'txbytes', 'txpackets', 'txmcast', 'txbcast' } function VhostUser:new (args) @@ -107,13 +107,23 @@ end function VhostUser:tx_callback (p) counter.add(self.counters.txbytes, packet.length(p)) counter.add(self.counters.txpackets) - counter.add(self.counters.txmcast, ethernet:n_mcast(packet.data(p))) + if ethernet:is_mcast(packet.data(p)) then + counter.add(self.counters.txmcast) + end + if ethernet:is_bcast(packet.data(p)) then + counter.add(self.counters.txbcast) + end end function VhostUser:rx_callback (p) counter.add(self.counters.rxbytes, packet.length(p)) counter.add(self.counters.rxpackets) - counter.add(self.counters.rxmcast, ethernet:n_mcast(packet.data(p))) + if ethernet:is_mcast(packet.data(p)) then + counter.add(self.counters.rxmcast) + end + if ethernet:is_bcast(packet.data(p)) then + counter.add(self.counters.rxbcast) + end end function VhostUser:rxdrop_callback (p) diff --git a/src/core/packet.lua b/src/core/packet.lua index 6c4ed5311b..143bfdf920 100644 --- a/src/core/packet.lua +++ b/src/core/packet.lua @@ -17,7 +17,7 @@ local packet_t = ffi.typeof("struct packet") local packet_ptr_t = ffi.typeof("struct packet *") local packet_size = ffi.sizeof(packet_t) local header_size = 8 -local max_payload = tonumber(C.PACKET_PAYLOAD_SIZE) +max_payload = tonumber(C.PACKET_PAYLOAD_SIZE) -- Freelist containing empty packets ready for use. diff --git a/src/lib/protocol/README.md b/src/lib/protocol/README.md index 6cf8ce19ea..8c237dcf6f 100644 --- a/src/lib/protocol/README.md +++ b/src/lib/protocol/README.md @@ -102,10 +102,9 @@ Returns the string representation of *mac* address. Returns a true value if *mac* address denotes a [Multicast address](https://en.wikipedia.org/wiki/Multicast_address#Ethernet). -— Function **ethernet:n_mcast** *mac* +— Function **ethernet:is_bcast** *mac* -Returns 1 if *mac* address denotes a [Multicast address](https://en.wikipedia.org/wiki/Multicast_address#Ethernet) -and 0 otherwise. +Returns a true value if *mac* address denotes a [Broadcast address](https://en.wikipedia.org/wiki/Broadcast_address#Ethernet). — Function **ethernet:ipv6_mcast** *ip* diff --git a/src/lib/protocol/ethernet.lua b/src/lib/protocol/ethernet.lua index b5abed8d31..c874f35acb 100644 --- a/src/lib/protocol/ethernet.lua +++ b/src/lib/protocol/ethernet.lua @@ -76,14 +76,15 @@ function ethernet:ipv6_mcast(ip) return result end --- Return 1 if MAC address has its group bit set and 0 otherwise -function ethernet:n_mcast (addr) - return band(addr[0], 0x01) -end - -- Check whether a MAC address has its group bit set function ethernet:is_mcast (addr) - return ethernet:n_mcast(addr) ~= 0 + return band(addr[0], 0x01) ~= 0 +end + +local bcast_address = ethernet:pton("FF:FF:FF:FF:FF:FF") +-- Check whether a MAC address is the broadcast address +function ethernet:is_bcast (addr) + return C.memcmp(addr, bcast_address, 6) == 0 end -- Instance methods