GSMLG.MAC

Fast, compile-time MAC address vendor lookup library for Elixir

GSMLG.MAC provides efficient MAC (Media Access Control) address utilities including vendor/manufacturer lookup based on the IEEE OUI (Organizationally Unique Identifier) database. The library uses Wireshark's manufacturer database and compiles it into efficient pattern-matching code at compile time for zero-cost lookups.

Features

Installation

Add gsmlg_mac to your list of dependencies in mix.exs:

def deps do
[
{:gsmlg_mac, "~> 0.1.0"}
]
end

Database Setup

The library requires Wireshark's manufacturer database file. Download it:

# From your project root
mkdir -p apps/gsmlg_mac/priv
curl -sSLf https://gitlab.com/wireshark/wireshark/-/raw/master/manuf \
-o apps/gsmlg_mac/priv/manuf.txt

Note: The database is compiled at compile-time. After updating manuf.txt, you must recompile:

mix deps.clean gsmlg_mac --build
mix compile

Quick Start

# Lookup vendor by MAC address
iex> GSMLG.MAC.lookup_vendor("00:00:0A:BB:28:FC")
{:ok, "OmronTat", "Omron Tateisi Electronics Co."}
# Validate MAC address format
iex> GSMLG.MAC.validate("00:1A:2B:3C:4D:5E")
true
# Normalize MAC to standard format
iex> GSMLG.MAC.normalize("00-1a-2b-3c-4d-5e")
{:ok, "00:1A:2B:3C:4D:5E"}
# Format MAC with different separators
iex> GSMLG.MAC.format("001a2b3c4d5e", :hyphens)
{:ok, "00-1A-2B-3C-4D-5E"}
# Extract OUI (first 24 bits)
iex> GSMLG.MAC.parse_oui("00:1A:2B:3C:4D:5E")
{:ok, "00:1A:2B"}
# Generate random MAC address
iex> GSMLG.MAC.random()
"A3:4F:12:8B:C9:7E"

Usage

Vendor Lookup

Look up the manufacturer/vendor of a MAC address using the IEEE OUI database:

# Standard colon format
GSMLG.MAC.lookup_vendor("00:00:0A:BB:28:FC")
#=> {:ok, "OmronTat", "Omron Tateisi Electronics Co."}
# Hyphen format
GSMLG.MAC.lookup_vendor("00-50-56-C0-00-08")
#=> {:ok, "VMware", "VMware, Inc."}
# Dot format (Cisco)
GSMLG.MAC.lookup_vendor("0050.56C0.0008")
#=> {:ok, "VMware", "VMware, Inc."}
# No separators
GSMLG.MAC.lookup_vendor("005056C00008")
#=> {:ok, "VMware", "VMware, Inc."}
# Unknown vendor
GSMLG.MAC.lookup_vendor("FF:FF:FF:FF:FF:FF")
#=> :error
# Invalid format
GSMLG.MAC.lookup_vendor("invalid")
#=> :error

Returns:

The lookup chooses the longest matching prefix, including /24, /28, and /36 entries sharing the same OUI. When a more specific prefix does not match, it falls back to a broader entry if one exists. The vendor database contains over 28,000+ entries from the IEEE registration authority and Wireshark project.

Operator-provided Wireshark manufacturer database contents can be compiled and queried without replacing the bundled database:

table = GSMLG.MAC.Compiler.build_lookup_table(File.read!("/etc/manuf.txt"))
GSMLG.MAC.Compiler.count_entries(table)
GSMLG.MAC.Vendor.lookup("00:11:22:33:40:00", table)

Tables with a single prefix width per OUI retain their existing format. Mixed widths use {:mixed, [{width, prefix_map}, ...]} buckets ordered longest first. Repeated identical prefixes keep the last record, as before.

MAC Address Validation

Check if a string is a valid MAC address format:

# Valid formats
GSMLG.MAC.validate("00:1A:2B:3C:4D:5E") #=> true
GSMLG.MAC.validate("00-1A-2B-3C-4D-5E") #=> true
GSMLG.MAC.validate("001A.2B3C.4D5E") #=> true
GSMLG.MAC.validate("001A2B3C4D5E") #=> true
# Invalid formats
GSMLG.MAC.validate("00:1A:2B:3C:4D") #=> false (too short)
GSMLG.MAC.validate("00:1A:2B:GG:4D:5E") #=> false (invalid hex)
GSMLG.MAC.validate("not-a-mac") #=> false
GSMLG.MAC.validate("") #=> false

Accepts any standard MAC address format with 48 bits (6 bytes).

MAC Address Normalization

Convert any MAC format to the standard uppercase colon-separated format:

# From hyphens
GSMLG.MAC.normalize("00-1a-2b-3c-4d-5e")
#=> {:ok, "00:1A:2B:3C:4D:5E"}
# From dots (Cisco format)
GSMLG.MAC.normalize("001a.2b3c.4d5e")
#=> {:ok, "00:1A:2B:3C:4D:5E"}
# From no separators
GSMLG.MAC.normalize("001a2b3c4d5e")
#=> {:ok, "00:1A:2B:3C:4D:5E"}
# Already normalized (converts to uppercase)
GSMLG.MAC.normalize("00:1a:2b:3c:4d:5e")
#=> {:ok, "00:1A:2B:3C:4D:5E"}
# Mixed formats (cleans up)
GSMLG.MAC.normalize("00:1A-2B.3C4D:5E")
#=> {:ok, "00:1A:2B:3C:4D:5E"}
# Invalid MAC
GSMLG.MAC.normalize("invalid")
#=> {:error, :invalid_mac}

Normalization is useful for storing MACs in a database with consistent formatting.

MAC Address Formatting

Format a MAC address with different separator styles:

mac = "001A2B3C4D5E"
# Colon-separated (standard)
GSMLG.MAC.format(mac, :colons)
#=> {:ok, "00:1A:2B:3C:4D:5E"}
# Hyphen-separated
GSMLG.MAC.format(mac, :hyphens)
#=> {:ok, "00-1A-2B-3C-4D-5E"}
# Dot-separated, grouped by 2 (Cisco format)
GSMLG.MAC.format(mac, :dots)
#=> {:ok, "001A.2B3C.4D5E"}
# No separators
GSMLG.MAC.format(mac, :none)
#=> {:ok, "001A2B3C4D5E"}
# Cisco format (dots, grouped by 4 hex digits)
GSMLG.MAC.format(mac, :cisco)
#=> {:ok, "001A.2B3C.4D5E"}
# Invalid format style
GSMLG.MAC.format(mac, :unknown)
#=> {:error, :invalid_format}
# Invalid MAC
GSMLG.MAC.format("invalid", :colons)
#=> {:error, :invalid_mac}

Format options:

OUI Parsing

Extract the OUI (Organizationally Unique Identifier) - the first 24 bits that identify the vendor:

# Standard format
GSMLG.MAC.parse_oui("00:1A:2B:3C:4D:5E")
#=> {:ok, "00:1A:2B"}
# Any format works
GSMLG.MAC.parse_oui("00-1A-2B-3C-4D-5E")
#=> {:ok, "00:1A:2B"}
GSMLG.MAC.parse_oui("001a2b3c4d5e")
#=> {:ok, "00:1A:2B"}
# Invalid MAC
GSMLG.MAC.parse_oui("invalid")
#=> {:error, :invalid_mac}

The OUI is always returned in uppercase colon-separated format.

Random MAC Generation

Generate random MAC addresses for testing:

# Fully random MAC
GSMLG.MAC.random()
#=> "A3:4F:12:8B:C9:7E"
# Random MAC with specific OUI
GSMLG.MAC.random("00:1A:2B")
#=> "00:1A:2B:7C:3E:91"
# Random MAC with OUI from vendor
GSMLG.MAC.random("00-50-56") # VMware OUI
#=> "00:50:56:A2:B8:3F"
# Invalid OUI
GSMLG.MAC.random("invalid")
#=> {:error, :invalid_oui}

Useful for generating test data or temporary MAC addresses.

Telemetry

GSMLG.MAC emits telemetry events when integrated with GSMLG.Telemetry. To enable telemetry, add the dependency:

def deps do
[
{:gsmlg_mac, "~> 0.1.0"},
{:gsmlg_telemetry, "~> 0.1.0"}
]
end

Events

[:gsmlg, :mac, :lookup]

Emitted when performing vendor lookups.

Metadata:

Measurements:

Example handler:

:telemetry.attach(
"mac-lookup-handler",
[:gsmlg, :mac, :lookup],
fn event, measurements, metadata, _config ->
IO.puts("MAC lookup: #{metadata.mac} -> #{metadata.found}")
end,
nil
)

[:gsmlg, :mac, :operation]

Emitted for utility operations (validate, normalize, format, etc.).

Metadata:

Measurements:

Supported MAC Formats

GSMLG.MAC automatically handles multiple MAC address formats:

Format Example Description
Colon-separated 00:1A:2B:3C:4D:5E Most common format (IEEE standard)
Hyphen-separated 00-1A-2B-3C-4D-5E Windows style
Dot-separated 001A.2B3C.4D5E Cisco style (4 hex digits per group)
No separators 001A2B3C4D5E Compact format
Mixed case 00:1a:2B:3c:4D:5e Accepts any case, normalizes to uppercase

All formats are automatically recognized by all functions. The library normalizes internally.

Architecture & Performance

Compile-Time Database Compilation

GSMLG.MAC uses a unique compile-time approach for maximum performance:

  1. Build Time: The manuf.txt database is parsed and compiled into Elixir module attributes
  2. Compile Time: The lookup table becomes pattern-matching code in the BEAM bytecode
  3. Runtime: Lookups are simple map lookups - effectively zero overhead

Performance characteristics:

This is significantly faster than:

Trade-offs

Advantages:

Disadvantages:

Best for:

Not ideal for:

Updating the Database

The MAC vendor database changes as new OUIs are assigned. To update:

Manual Update

# Download latest database
curl -sSLf https://gitlab.com/wireshark/wireshark/-/raw/master/manuf \
-o apps/gsmlg_mac/priv/manuf.txt
# Recompile the application
mix deps.clean gsmlg_mac --build
mix compile
# Verify entry count (should be 28,000+)
iex> GSMLG.MAC.Vendor.entries()
28542

Automated Update (CI/CD)

Add to your deployment pipeline:

#!/bin/bash
# update_mac_db.sh
echo "Updating MAC vendor database..."
curl -sSLf https://gitlab.com/wireshark/wireshark/-/raw/master/manuf \
-o apps/gsmlg_mac/priv/manuf.txt
echo "Recompiling with new database..."
mix deps.clean gsmlg_mac --build
mix compile
echo "Database updated successfully!"

Recommendation: Update quarterly or when you need specific new vendors.

Common Use Cases

1. Network Device Inventory

defmodule NetworkInventory do
def scan_devices(mac_addresses) do
Enum.map(mac_addresses, fn mac ->
case GSMLG.MAC.lookup_vendor(mac) do
{:ok, short, full} ->
%{mac: mac, vendor: short, manufacturer: full}
:error ->
%{mac: mac, vendor: "Unknown", manufacturer: "Unknown"}
end
end)
end
end
NetworkInventory.scan_devices([
"00:50:56:C0:00:08",
"00:1A:2B:3C:4D:5E",
"A4:5E:60:D8:9C:12"
])

2. MAC Address Validation in Ecto Schema

defmodule Device do
use Ecto.Schema
import Ecto.Changeset
schema "devices" do
field :mac_address, :string
field :vendor, :string
end
def changeset(device, attrs) do
device
|> cast(attrs, [:mac_address])
|> validate_required([:mac_address])
|> validate_mac_address()
|> populate_vendor()
end
defp validate_mac_address(changeset) do
validate_change(changeset, :mac_address, fn :mac_address, mac ->
if GSMLG.MAC.validate(mac) do
[]
else
[mac_address: "is not a valid MAC address"]
end
end)
end
defp populate_vendor(changeset) do
case get_change(changeset, :mac_address) do
nil -> changeset
mac ->
case GSMLG.MAC.lookup_vendor(mac) do
{:ok, _short, full} -> put_change(changeset, :vendor, full)
:error -> changeset
end
end
end
end

3. MAC Address Normalization for Storage

defmodule MACStore do
def store_device(mac, metadata) do
with {:ok, normalized_mac} <- GSMLG.MAC.normalize(mac),
{:ok, oui} <- GSMLG.MAC.parse_oui(normalized_mac) do
%{
mac: normalized_mac,
oui: oui,
vendor: lookup_vendor_safe(normalized_mac),
metadata: metadata,
inserted_at: DateTime.utc_now()
}
|> save_to_database()
else
{:error, _} -> {:error, :invalid_mac}
end
end
defp lookup_vendor_safe(mac) do
case GSMLG.MAC.lookup_vendor(mac) do
{:ok, _short, full} -> full
:error -> nil
end
end
end

4. Network Security Monitoring

defmodule SecurityMonitor do
@known_vendors ["Cisco", "VMware", "Dell", "HP"]
def check_rogue_device(mac) do
case GSMLG.MAC.lookup_vendor(mac) do
{:ok, vendor, _full} ->
if vendor in @known_vendors do
{:ok, :authorized}
else
{:warning, :unknown_vendor, vendor}
end
:error ->
{:alert, :unrecognized_mac}
end
end
end

5. API Endpoint (Phoenix)

defmodule MyAppWeb.MACController do
use MyAppWeb, :controller
def lookup(conn, %{"mac" => mac}) do
case GSMLG.MAC.lookup_vendor(mac) do
{:ok, short, full} ->
json(conn, %{
mac: mac,
vendor_short: short,
vendor_full: full,
success: true
})
:error ->
conn
|> put_status(:not_found)
|> json(%{mac: mac, error: "Vendor not found", success: false})
end
end
def validate(conn, %{"mac" => mac}) do
json(conn, %{
mac: mac,
valid: GSMLG.MAC.validate(mac)
})
end
end

Troubleshooting

Database file not found

Error: (File.Error) could not read file "...priv/manuf.txt": no such file or directory

Solution: Download the database file:

mkdir -p apps/gsmlg_mac/priv
curl -sSLf https://gitlab.com/wireshark/wireshark/-/raw/master/manuf \
-o apps/gsmlg_mac/priv/manuf.txt
mix deps.clean gsmlg_mac --build
mix compile

Vendor not found for valid MAC

Issue: lookup_vendor/1 returns :error for a legitimate MAC address.

Causes:

  1. OUI not in database (new vendor, private OUI)
  2. Outdated database
  3. MAC is randomly generated or spoofed

Solutions:

Compilation is slow

Issue: Application takes longer to compile after adding gsmlg_mac.

Explanation: The library compiles ~28,000 vendor entries into pattern-matching code at compile time. This is a one-time cost.

Solutions:

MAC format not recognized

Issue: Valid MAC not being parsed correctly.

Solution: GSMLG.MAC accepts standard formats. Try normalizing first:

mac = "unusual:format:here"
case GSMLG.MAC.normalize(mac) do
{:ok, normalized} -> GSMLG.MAC.lookup_vendor(normalized)
{:error, _} -> # Handle invalid format
end

Comparison with Alternatives

Approach Speed Memory Updates Dependencies
GSMLG.MAC ★★★★★ Medium Recompile None
Runtime parsing ★★☆☆☆ Low Easy None
ETS lookup ★★★★☆ Medium Runtime None
External API ★☆☆☆☆ Low Always current HTTP client
SQLite DB ★★★☆☆ Low Easy SQLite

Why choose GSMLG.MAC:

Consider alternatives if:

Contributing

To contribute or report issues:

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Submit a pull request

License

MIT License - see LICENSE file for details.

Resources

Credits

Vendor database sourced from the Wireshark project, which aggregates IEEE OUI assignments and additional manufacturer information.