Skip to content

1ight181/mdnsresolver

Repository files navigation

mDNS Resolver for gRPC

Русская версия

Overview

This package implements a custom gRPC Name Resolver for the mdns:// scheme, using mDNS (Multicast DNS) via the github.com/hashicorp/mdns library.

It is designed for dynamic service discovery within a local network without relying on centralized service discovery systems (such as Consul, etcd, or Kubernetes DNS).

The implementation follows architectural patterns used in official gRPC resolvers (dns, passthrough) and typical mDNS solutions in the Go ecosystem.


Features

  • IPv4 and IPv6 support
  • Address deduplication
  • Instance name filtering
  • Interface-specific resolution
  • Streaming delivery of address updates
  • Optimized handling of ResolveNow() calls

Target format

By default, the resolver expects an endpoint in the following format:

mdns:///my-instance._myservice._tcp.local.

Parsing follows this structure:

Part Example Description
instance my-instance service instance name
service _myservice service name
proto _tcp transport protocol
domain local. mDNS domain

The resulting service type is constructed as:

<service>.<proto>

Configuration

BuilderOptions

type BuilderOptions struct {
    ResolverIfaces        []net.Interface
    ShouldResolveIpv6     bool
    ShouldReportError     bool
    ShouldDedupeAddresses bool
    ParseFunc             func(endpoint string) (instanceName, service, proto, domain string, err error)
}

Parameter details

ResolverIfaces

List of network interfaces used for mDNS lookup.

  • if empty → all interfaces are used (net.Interfaces())

ShouldResolveIpv6

Enables inclusion of IPv6 addresses from mDNS responses:

entry.AddrV6IPAddr

ShouldReportError

If true, errors from:

  • mdns.Query
  • clientConn.UpdateState

are propagated via:

clientConn.ReportError(err)

ShouldDedupeAddresses

Enables comparison between previous and current address sets.
If there are no changes, the gRPC state is not updated.


ParseFunc

Allows replacing the default mDNS endpoint parser.
The default parser uses the following regular expression:

^([^.]+)\.(_[^.]+)\.(_[^.]+)\.((?:[^.]+\.)+)$

Resolver behavior

1. Initialization

On package import:

resolver.Register(&mdnsBuilder{})

Registers the scheme:

mdns

2. Build()

When calling grpc.NewClient:

  1. The scheme (mdns) is validated
  2. The endpoint is parsed
  3. Network interfaces are determined
  4. A context with cancel is created
  5. mdnsResolver.Start() is launched

3. Lookup loop

For each interface:

mdns.Query(&mdns.QueryParam{
    Service: r.targetInfo.serviceType,
    Domain:  r.targetInfo.domain,
})

4. Result filtering

  • Filter by instance name

  • Skip nil entries


5. Address construction

Supported:

IPv4

AddrV4.String()

IPv6 (optional)

AddrV6IPAddr.IP.String()

Format:

ip:port

6. gRPC state update

clientConn.UpdateState(resolver.State{Addresses: addrs})

Deduplication

If ShouldDedupeAddresses is enabled, the resolver compares:

  • list length
  • presence of all addresses in the previous set

If no changes are detected, UpdateState is not called.


Streaming model

The resolver uses two goroutines:

1. lookup()

  • listens on resolveNowChan
  • triggers mDNS queries

2. watcher()

  • reads results from entries
  • builds gRPC state
  • sends UpdateState

This model is optimized to coalesce multiple rapid ResolveNow calls from gRPC.


Errors

Defined errors:

ErrUnsupportedScheme
ErrEmptyEndpoint
ErrInvalidFormat
ErrInvalidService

Usage example

mdnsBuilder := resolver.NewBuilder(resolver.BuilderOptions{
    ShouldResolveIpv6:     false,
    ShouldReportError:     true,
    ShouldDedupeAddresses: true,
})

conn, err := grpc.NewClient(
    "mdns:///my-instance._myservice._tcp.local.",
    grpc.WithResolvers(mdnsBuilder),
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)
if err != nil {
    log.Fatal(err)
}
defer conn.Close()

client := NewYourServiceClient(conn)

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

_, err = client.YourMethod(ctx, &YourRequest{})
if err != nil {
    log.Fatal(err)
}

Implementation details

1. Native gRPC integration

Uses standard interfaces:

  • resolver.Builder
  • resolver.Resolver
  • resolver.ClientConn

2. Event-driven model

  • ResolveNow → triggers lookup
  • watcher → pushes updates

3. Limitations

  • Works only in local networks with mDNS
  • Depends on multicast availability

When to use

Suitable for:

  • local development
  • IoT networks
  • P2P services
  • zeroconf infrastructures

License

MIT License — Copyright (c) 2026 1ight181

Community & Support

Found a bug? — GitHub Issues Email: danil.odinzov181@gmail.com

About

A lightweight mDNS resolver for gRPC with deduplication of ResolveNow requests and a streaming model for address updates.

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages