Documentation
¶
Overview ¶
Package macadress is the official Go client for the macadress.com MAC address and OUI vendor lookup API.
The zero-configuration client talks to the hosted API. A key is only needed for everything beyond the plain-text vendor lookup:
mac := macadress.New("mk_live_xxx") // or macadress.New("") for keyless
name, err := mac.Vendor(ctx, "00:03:93:AB:12:34") // "Apple, Inc.", nil
res, err := mac.Lookup(ctx, "3C:22:FB:12:34:56") // *MacResult
items, err := mac.Batch(ctx, addrs) // []BatchItem
hits, err := mac.SearchVendors(ctx, "Cisco", macadress.WithCountry("US"))
Every call takes a context.Context. Failures are errors: transport problems come back as *TransportError, API error responses as *APIError, and the common cases (400, 401, 429) also match the sentinels ErrInvalidMAC, ErrAuth, ErrRateLimited and ErrQuota via errors.Is.
The client has no dependencies outside the standard library.
Index ¶
- Constants
- Variables
- type APIError
- type AdministrationType
- type BatchItem
- type BlockType
- type Client
- func (c *Client) BaseURL() string
- func (c *Client) Batch(ctx context.Context, macs []string) ([]BatchItem, error)
- func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body any) (*Response, error)
- func (c *Client) Health(ctx context.Context) (bool, error)
- func (c *Client) Lookup(ctx context.Context, mac string) (*MacResult, error)
- func (c *Client) SearchVendors(ctx context.Context, query string, opts ...SearchOption) (*VendorSearchResult, error)
- func (c *Client) Vendor(ctx context.Context, mac string) (string, error)
- type Device
- type DeviceCategory
- type MacResult
- type Meta
- type Option
- type RandomizationConfidence
- type Response
- type SearchOption
- type TransmissionType
- type TransportError
- type VendorBlock
- type VendorSearchResult
Examples ¶
Constants ¶
const MaxBatchSize = 100
MaxBatchSize is the most addresses Batch will send in one request.
const Version = "1.0.0"
Version is the client version, sent in the User-Agent header. Keep it in step with the CHANGELOG heading and the release tag.
Variables ¶
var ( // ErrInvalidMAC is an HTTP 400: the address or batch body did not parse. ErrInvalidMAC = errors.New("macadress: invalid MAC address") // ErrAuth is an HTTP 401: the API key is missing or invalid. ErrAuth = errors.New("macadress: invalid or missing API key") // ErrRateLimited is an HTTP 429 from the per-minute rate limiter. A // quota error also matches this, the same way it does in the other // clients. ErrRateLimited = errors.New("macadress: rate limit exceeded") // ErrQuota is an HTTP 429 where the billing-cycle quota is spent. ErrQuota = errors.New("macadress: quota exceeded") )
Sentinel errors for the well-known API failure modes. Match them with errors.Is; reach for the *APIError with errors.As when you need the status code, request id or Retry-After.
Functions ¶
This section is empty.
Types ¶
type APIError ¶
type APIError struct {
StatusCode int
Message string
RequestID string
// RetryAfter is the Retry-After header as a duration, or 0 if absent.
RetryAfter time.Duration
// Body is the decoded JSON error body, when the API sent one.
Body map[string]any
// contains filtered or unexported fields
}
APIError is returned when the API responds with a non-2xx status, or with a body the client cannot read.
type AdministrationType ¶
type AdministrationType string
AdministrationType is whether the address is universally (IEEE-assigned) or locally administered.
const ( UniversallyAdministered AdministrationType = "universally_administered" LocallyAdministered AdministrationType = "locally_administered" )
type BatchItem ¶
BatchItem is one entry of a Batch response: a full MacResult plus the original input string, and an Err message when that one address could not be resolved.
type BlockType ¶
type BlockType string
BlockType is the IEEE registry an assignment comes from. MA-L is a /24 (the classic OUI), MA-M a /28, MA-S a /36; IAB and CID are legacy blocks. An unrecognised value from a newer API version is kept as-is rather than rejected.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a macadress.com API client. It is safe for concurrent use. Create one with New.
func (*Client) Batch ¶
Batch looks up up to MaxBatchSize addresses in one request. It needs an API key. Results come back in input order; check Failed on each item.
It returns an error without making a request if macs is empty or longer than MaxBatchSize.
func (*Client) Do ¶
func (c *Client) Do(ctx context.Context, method, path string, query url.Values, body any) (*Response, error)
Do performs a raw request against path (for example "/v1/healthz") and returns the response as-is. Nothing is treated as an error for a non-2xx status; only a transport failure returns a non-nil error. If body is non-nil it is sent as JSON.
func (*Client) Health ¶
Health reports whether the API and its database are reachable. It is keyless and not counted against any quota.
A transport failure (unreachable, timeout) returns (false, nil): not reachable means not healthy. A non-nil error is only returned when the request itself could not be built.
func (*Client) Lookup ¶
Lookup returns the full analysis of one address. It needs an API key.
Example ¶
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
macadress "github.com/sapisos/macadress-go"
)
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, `{"organization":"Apple, Inc.","country":"US","block_type":"MA-L"}`)
}))
defer srv.Close()
mac := macadress.New("mk_live_xxx", macadress.WithBaseURL(srv.URL))
res, err := mac.Lookup(context.Background(), "3C:22:FB:12:34:56")
if err != nil {
panic(err)
}
fmt.Printf("%s / %s / %s\n", res.Organization, res.Country, res.BlockType)
}
Output: Apple, Inc. / US / MA-L
Example (RateLimited) ¶
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
macadress "github.com/sapisos/macadress-go"
)
func main() {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Retry-After", "5")
w.WriteHeader(http.StatusTooManyRequests)
_, _ = io.WriteString(w, `{"error":"rate limit exceeded"}`)
}))
defer srv.Close()
mac := macadress.New("mk_live_xxx", macadress.WithBaseURL(srv.URL))
_, err := mac.Lookup(context.Background(), "3C:22:FB:12:34:56")
if errors.Is(err, macadress.ErrRateLimited) {
var apiErr *macadress.APIError
errors.As(err, &apiErr)
fmt.Println("back off for", apiErr.RetryAfter)
}
}
Output: back off for 5s
func (*Client) SearchVendors ¶
func (c *Client) SearchVendors(ctx context.Context, query string, opts ...SearchOption) (*VendorSearchResult, error)
SearchVendors searches the registered vendor/block directory by organization name and/or country. It needs an API key and counts as one call against the plan quota.
func (*Client) Vendor ¶
Vendor returns the registered organization name for a MAC address as plain text. It needs no API key.
It returns an empty string (and a nil error) when the address is valid but has no vendor to report: an unregistered prefix, a private block, or a locally administered (usually privacy-randomized) address. Use Lookup to tell those cases apart.
The address may use ":", "-", "." or space grouping, or be a bare 12-hex-digit string.
Example ¶
package main
import (
"context"
"fmt"
"io"
"net/http"
"net/http/httptest"
macadress "github.com/sapisos/macadress-go"
)
func main() {
// A stand-in for api.macadress.com. In real code, drop WithBaseURL.
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_, _ = io.WriteString(w, "Apple, Inc.")
}))
defer srv.Close()
mac := macadress.New("", macadress.WithBaseURL(srv.URL)) // keyless
name, err := mac.Vendor(context.Background(), "00:03:93:AB:12:34")
if err != nil {
panic(err)
}
fmt.Println(name)
}
Output: Apple, Inc.
type Device ¶
type Device struct {
Category DeviceCategory `json:"category"`
PossibleCategories []DeviceCategory `json:"possible_categories"`
Confidence string `json:"confidence"`
InferenceSource string `json:"inference_source"`
ExactModelKnown bool `json:"exact_model_known"`
Raw map[string]any `json:"-"`
}
Device is the device block of a lookup result. A MAC address alone rarely determines a device type, so Category is "unknown" for most registrations and ExactModelKnown is effectively always false.
type DeviceCategory ¶
type DeviceCategory string
DeviceCategory is the controlled device taxonomy from the device.category field. Unknown is by far the most common value.
const ( DeviceComputer DeviceCategory = "computer" DeviceSmartphone DeviceCategory = "smartphone" DeviceTablet DeviceCategory = "tablet" DeviceRouter DeviceCategory = "router" DeviceSwitch DeviceCategory = "switch" DeviceWirelessAccessPoint DeviceCategory = "wireless_access_point" DeviceFirewall DeviceCategory = "firewall" DeviceServer DeviceCategory = "server" DeviceStorage DeviceCategory = "storage" DevicePrinter DeviceCategory = "printer" DeviceCamera DeviceCategory = "camera" DeviceSmartTV DeviceCategory = "smart_tv" DeviceMediaDevice DeviceCategory = "media_device" DeviceGamingConsole DeviceCategory = "gaming_console" DeviceIoT DeviceCategory = "iot" DeviceEmbeddedDevice DeviceCategory = "embedded_device" DeviceIndustrial DeviceCategory = "industrial" DeviceMedical DeviceCategory = "medical" DeviceAutomotive DeviceCategory = "automotive" DeviceVirtualMachine DeviceCategory = "virtual_machine" DeviceContainer DeviceCategory = "container" DeviceNetworkInterface DeviceCategory = "network_interface" DeviceConsumerElectronics DeviceCategory = "consumer_electronics" DeviceUnknown DeviceCategory = "unknown" )
type MacResult ¶
type MacResult struct {
MAC string `json:"mac"`
Valid bool `json:"valid"`
OUI string `json:"oui"`
Registered bool `json:"registered"`
Organization string `json:"organization"`
VendorAddress string `json:"vendor_address"`
Country string `json:"country"`
BlockType BlockType `json:"block_type"`
MatchedPrefix string `json:"matched_prefix"`
PrefixLength int `json:"prefix_length"`
AddressCapacity int64 `json:"address_capacity"`
RangeStart string `json:"range_start"`
RangeEnd string `json:"range_end"`
TransmissionType TransmissionType `json:"transmission_type"`
AdministrationType AdministrationType `json:"administration_type"`
LocallyAdministered bool `json:"locally_administered"`
SLAPQuadrant string `json:"slap_quadrant"`
EUI64 string `json:"eui64"`
IPv6LinkLocal string `json:"ipv6_link_local"`
PotentiallyRandomized bool `json:"potentially_randomized"`
RandomizationConfidence RandomizationConfidence `json:"randomization_confidence"`
// VendorLookupReliable is false when the organization cannot be trusted
// for this address specifically (a locally administered address, a
// private block).
VendorLookupReliable bool `json:"vendor_lookup_reliable"`
IsZero bool `json:"is_zero"`
IsBroadcast bool `json:"is_broadcast"`
Explanation string `json:"explanation"`
Device Device `json:"device"`
Meta Meta `json:"meta"`
// Raw is the decoded JSON object exactly as the API returned it.
Raw map[string]any `json:"-"`
}
MacResult is the full analysis of one address, from Lookup and from each item of Batch. A result is always returned, registered or not: check Registered rather than expecting an error.
Fields the typed struct does not cover stay reachable through Raw and Get; the API only ever adds fields, so this keeps working against newer response shapes.
type Meta ¶
type Meta struct {
RequestID string `json:"request_id"`
DatabaseVersion string `json:"database_version"`
Cached bool `json:"cached"`
}
Meta carries per-response bookkeeping.
type Option ¶
type Option func(*Client)
Option configures a Client in New.
func WithBaseURL ¶
WithBaseURL points the client at a different API root, for a self-hosted deployment. A trailing slash is trimmed.
func WithHTTPClient ¶
WithHTTPClient supplies the *http.Client to use for requests. Combine it with WithTimeout by passing WithHTTPClient first.
func WithTimeout ¶
WithTimeout sets the whole-request timeout on the client's HTTP client.
func WithUserAgent ¶
WithUserAgent overrides the User-Agent header.
type RandomizationConfidence ¶
type RandomizationConfidence string
RandomizationConfidence is how strongly the address looks like an OS privacy-randomized MAC.
const ( RandomizationNone RandomizationConfidence = "none" RandomizationPossible RandomizationConfidence = "possible" RandomizationLikely RandomizationConfidence = "likely" )
type SearchOption ¶
SearchOption narrows a SearchVendors query.
func WithCountry ¶
func WithCountry(code string) SearchOption
WithCountry filters to an ISO 3166-1 alpha-2 country.
func WithLimit ¶
func WithLimit(n int) SearchOption
WithLimit caps the number of blocks returned for the page.
type TransmissionType ¶
type TransmissionType string
TransmissionType classifies the destination: unicast, multicast or broadcast.
const ( Unicast TransmissionType = "unicast" Multicast TransmissionType = "multicast" Broadcast TransmissionType = "broadcast" )
type TransportError ¶
type TransportError struct {
Err error
}
TransportError wraps a failure to get an HTTP response at all: DNS, connection, TLS, timeout, or a canceled context. The cause is available through errors.Unwrap / errors.Is / errors.As.
func (*TransportError) Error ¶
func (e *TransportError) Error() string
func (*TransportError) Unwrap ¶
func (e *TransportError) Unwrap() error
type VendorBlock ¶
type VendorBlock struct {
PrefixInt int64 `json:"prefix_int"`
MaskBits int `json:"mask_bits"`
BlockType BlockType `json:"block_type"`
Organization string `json:"organization"`
Address string `json:"address"`
Country string `json:"country"`
IsPrivate bool `json:"is_private"`
FirstSeenAt string `json:"first_seen_at"`
LastChangedAt string `json:"last_changed_at"`
}
VendorBlock is one IEEE-assigned MAC/OUI prefix block from SearchVendors.
type VendorSearchResult ¶
type VendorSearchResult struct {
Total int `json:"total"`
Blocks []VendorBlock `json:"blocks"`
Raw map[string]any `json:"-"`
}
VendorSearchResult is the result of SearchVendors: the matching blocks for this page plus the total match count, which ignores the limit.