Generated from Manticore v1.1.6 • 388 packages. View on pkg.go.dev

llmnr

import "github.com/TheManticoreProject/Manticore/network/llmnr"

Package llmnr implements a Link-Local Multicast Name Resolution (LLMNR) client and the message types defined by RFC 4795. LLMNR reuses the DNS message format (RFC 1035) to resolve single-label, link-local names over the multicast group 224.0.0.252 (or FF02::1:3) on UDP/TCP port 5355.

The Client type (see client.go) exposes the low-level Query primitive, which sends a single question and returns the raw *message.Message it was answered with. This file layers the high-level resolver API most callers actually want on top of it: Resolve, ResolveA and ResolveAAAA turn a name into a slice of net.IP, and LookupRecords returns the typed resource records answering a name for a given type. Each helper reuses the typed RDATA accessors on resourcerecord.ResourceRecord (AsA/AsAAAA/…) to decode answers rather than walking the wire format by hand.

No-answer semantics: when a responder replies but its answer section carries no record matching the queried name and type, the resolver helpers return an empty (non-nil) slice and a nil error, i.e. “resolved to nothing” is not an error. A genuine failure to obtain any response (the query timing out because no host on the link owns the name, or the caller’s context being cancelled) is surfaced as the error returned by Client.Query.

Usage example:

client, err := llmnr.NewClient()
if err != nil {
    log.Fatalf("failed to create client: %v", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

ips, err := client.Resolve(ctx, "wpad")
if err != nil {
    log.Fatalf("resolve failed: %v", err)
}
for _, ip := range ips {
    fmt.Println(ip)
}

Index

type Client

Client represents an LLMNR client that can send queries and receive responses.

The Client struct provides methods to create a new client, send queries, and close the client connection. It manages a UDP connection and uses a sync.Map to keep track of ongoing queries.

Fields:

  • conn: A pointer to the UDP connection used for sending and receiving LLMNR messages.
  • timeout: The duration to wait for a response before timing out.
  • queries: A sync.Map that maps query IDs to channels for receiving responses.
  • closeOnce: Ensures the client is closed only once.
  • closed: A channel that is closed when the client is closed.
  • dest: The destination address queries are sent to. It defaults to the LLMNR IPv4 multicast group (224.0.0.252:5355) so that normal usage is unchanged; it is overridable to allow queries to be directed at a specific responder.

Usage example:

client, err := NewClient()
if err != nil {
    log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, err := client.Query(ctx, "example.local", TypeA)
if err != nil {
    log.Fatalf("Query failed: %v", err)
}
fmt.Printf("Received response: %v\n", resp)
type Client struct {
    Conn      *net.UDPConn
    Timeout   time.Duration
    Queries   sync.Map
    CloseOnce sync.Once
    Closed    chan struct{}
    // contains filtered or unexported fields
}

func NewClient

func NewClient() (*Client, error)

NewClient creates a new LLMNR client with a UDP connection.

The function initializes a UDP connection for the client to use for sending and receiving LLMNR messages. It sets a default timeout duration for queries and starts a read loop to handle incoming responses.

Returns:

  • A pointer to the newly created Client.
  • An error if the UDP connection could not be created.

Usage example:

client, err := NewClient()
if err != nil {
    log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

resp, err := client.Query(ctx, "example.local", TypeA)
if err != nil {
    log.Fatalf("Query failed: %v", err)
}
fmt.Printf("Received response: %v\n", resp)

func NewClientForInterface

func NewClientForInterface(family, ifaceName string) (*Client, error)

NewClientForInterface creates a new LLMNR client bound to a specific address family and (optionally) a specific network interface.

It is the entry point for querying over IPv6 multicast, which the default IPv4-only NewClient cannot reach. family selects the socket family and multicast group: “udp4” sends to the IPv4 group 224.0.0.252 (matching NewClient) and “udp6” sends to the IPv6 link-local group FF02::1:3. ifaceName, when non-empty, names the interface the queries are sent out of.

An interface is effectively required for “udp6”: FF02::1:3 is a link-local (scope 2) multicast address, so the datagram must carry a zone identifying the link it is sent on. On a multi-homed host the interface also overrides the kernel’s default multicast egress interface, which is otherwise not necessarily the link carrying the LLMNR traffic of interest. For “udp4” the interface is optional and, when supplied, selects the outgoing multicast interface.

The returned client exposes exactly the same Query and resolver API as NewClient; a client created with family “udp6” resolves over IPv6 (e.g. ResolveAAAA leaves over the IPv6 group), while responses are still validated as coming from a plausible on-link/link-local/loopback source.

Usage example:

client, err := NewClientForInterface("udp6", "eth0")
if err != nil {
    log.Fatalf("Failed to create client: %v", err)
}
defer client.Close()

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

ips, err := client.ResolveAAAA(ctx, "host")
if err != nil {
    log.Fatalf("Query failed: %v", err)
}
fmt.Printf("Resolved: %v\n", ips)

func (*Client) Close

func (c *Client) Close() error

Close closes the client connection

func (*Client) LookupRecords

func (c *Client) LookupRecords(ctx context.Context, name string, qtype llmnr_type.Type) ([]resourcerecord.ResourceRecord, error)

LookupRecords sends an LLMNR query for name and qtype and returns the answer records that actually answer it: only records whose owner name matches name (compared case-insensitively, as DNS names are case-insensitive) and whose type equals qtype are returned. This filtering discards any unrelated records a responder may have bundled into the answer section.

The returned slice is always non-nil; a response that carries no matching record yields an empty slice and a nil error (see the package-level no-answer semantics). An error is returned only when the underlying query fails to obtain a response (timeout or context cancellation).

func (*Client) Query

func (c *Client) Query(ctx context.Context, name string, qtype llmnr_type.Type) (*message.Message, error)

Query sends an LLMNR query and waits for a response

func (*Client) Resolve

func (c *Client) Resolve(ctx context.Context, name string) ([]net.IP, error)

Resolve resolves name to all of its IPv4 and IPv6 addresses by issuing both a Type A and a Type AAAA query concurrently and concatenating their results (A addresses first, then AAAA). The two queries run in parallel so the combined lookup is not slower than a single one.

Because a host commonly owns only one address family, Resolve is deliberately lenient: it returns the addresses from whichever query succeeded and only propagates an error when both queries failed, in which case the A query’s error is returned. The returned slice is always non-nil; it is empty when the name resolved to no addresses at all.

func (*Client) ResolveA

func (c *Client) ResolveA(ctx context.Context, name string) ([]net.IP, error)

ResolveA resolves name to its IPv4 addresses by issuing a Type A LLMNR query and decoding every matching A answer with resourcerecord.AsA. The returned slice is always non-nil and holds one net.IP per A record answering name (an empty slice when the responder returned none). An error is returned only when the query itself fails (timeout or context cancellation); a malformed A record is skipped rather than failing the whole resolution.

func (*Client) ResolveAAAA

func (c *Client) ResolveAAAA(ctx context.Context, name string) ([]net.IP, error)

ResolveAAAA resolves name to its IPv6 addresses by issuing a Type AAAA LLMNR query and decoding every matching AAAA answer with resourcerecord.AsAAAA. The returned slice is always non-nil and holds one net.IP per AAAA record answering name (an empty slice when the responder returned none). An error is returned only when the query itself fails (timeout or context cancellation); a malformed AAAA record is skipped rather than failing the whole resolution.

Subpackages