native_mdns_scanner

Pub Version License: MIT

Dart FFI bindings for macOS mDNS/Bonjour services. This library provides native access to multicast DNS functionality on macOS through FFI bindings to Objective-C code.

Features

  • πŸ” Simple mDNS scanning - Discover devices by service type
  • 🎯 Simultaneous multi-service scanning - Scan multiple service types at once
  • πŸ”„ Periodic scanning - Run queries at regular intervals
  • ⏱️ Timing analysis - Analyze discovery patterns and performance
  • πŸ–₯️ CLI tool - Command-line interface for quick testing
  • πŸ“Š Rich device information - IP, port, TXT records, and discovery metadata

Platform Support

  • βœ… macOS (arm64 + x86_64)
  • ❌ iOS (not tested)
  • ❌ Windows (not supported)
  • ❌ Linux (not supported)

Installation

Add this to your pubspec.yaml:

dependencies:
  native_mdns_scanner: ^1.4.0

Or install directly from GitHub:

dependencies:
  native_mdns_scanner:
    git:
      url: https://github.com/changyy/ffi-mdns-macos-dart.git

Library Distribution & Portable Deployment

This package uses a comprehensive portable deployment strategy where the native library (libmdns_ffi.dylib) is bundled with the Dart package using multiple complementary approaches:

πŸ“¦ Deployment Methods:

  1. files: Configuration: Explicitly includes the library in the published package
  2. Flutter Assets: For Flutter projects, library included as asset
  3. Intelligent Path Resolution: Automatic discovery across project types
# In pubspec.yaml - ensures library is always included
files:
  - native/libmdns_ffi.dylib

flutter:
  assets:
    - native/libmdns_ffi.dylib

βœ… Supported Project Types:

  • Flutter Projects: Library included as asset, automatically found
  • Pure Dart Projects: Library bundled with package via files: configuration
  • Command-line Tools: Full portable deployment support
  • Development: Local native/ directory takes priority

Key Benefits:

  • 🎯 Portable: Library travels with your application
  • πŸ”§ Zero Setup: No manual library installation needed
  • πŸ“¦ Bundled: Everything needed is in the package
  • πŸ” Auto-Discovery: Intelligent library path resolution
  • πŸ›‘οΈ Redundant: Multiple deployment methods for maximum compatibility

For Package Users:

# No additional setup needed - just add to pubspec.yaml and run:
dart pub get

# The library will be automatically located in your .dart_tool/ cache

For Development:

# If working with source code, ensure library is built:
cd native && ./build.sh

How It Works:

  1. Flutter Projects: Library included as asset in pubspec.yaml
  2. Pure Dart: Library found via package dependency resolution
  3. Development: Local native/ directory takes priority

The library search order:

  1. Package dependency path (highest priority for published packages)
  2. Local development path (native/libmdns_ffi.dylib)
  3. Current directory and common build locations
  4. System-wide locations (fallback) flutter: assets:
    • native/libmdns_ffi.dylib

## Quick Start

### Basic Usage

```dart
import 'package:native_mdns_scanner/native_mdns_scanner.dart';

void main() async {
  final scanner = NativeMdnsScanner();
  try {
    // Scan for Chromecast devices
    scanner.startScan('_googlecast._tcp');
    // Wait for 10 seconds
    await Future.delayed(Duration(seconds: 10));
    scanner.stopScan();
    // Get results
    final devices = scanner.foundDevices;
    for (final device in devices) {
      print('Found: \\${device.name} at \\${device.ip}:\\${device.port}');
    }
  } finally {
    scanner.dispose();
  }
}

Simultaneous Multi-Service Scanning

final scanner = NativeMdnsScanner();

try {
  final devices = await scanner.scanMultipleServices([
    '_googlecast._tcp',  // Chromecast
    '_airplay._tcp',     // AirPlay
    '_raop._tcp',        // Remote Audio Output Protocol
  ], timeout: Duration(seconds: 15));
  print('Found \\${devices.length} devices');
  // Group by service type
  final devicesByType = scanner.getDevicesByServiceType();
  for (final serviceType in devicesByType.keys) {
    final typeDevices = devicesByType[serviceType]!;
    print('\\${serviceType}: \\${typeDevices.length} devices');
  }
} finally {
  scanner.dispose();
}

Periodic Scanning

final scanner = NativeMdnsScanner();

try {
  final devices = await scanner.scanMultipleServicesWithPeriodic([
    '_googlecast._tcp',
  ],
    timeout: Duration(seconds: 30),      // Total scan time
    queryInterval: Duration(seconds: 5), // Query every 5 seconds
  );
  // Analyze timing patterns
  TimingAnalyzer.analyzeTimings(devices);
} finally {
  scanner.dispose();
}

Event Callback & Custom Scan Duration

You can receive device events in real time using the JSON callback interface. This is useful for UI updates or streaming results as they arrive.

Using startScanJson

final scanner = NativeMdnsScanner();
final foundDevices = <DeviceInfo>[];
scanner.startScanJson('_googlecast._tcp', (json) {
  if (json['type'] == 'device') {
    foundDevices.add(DeviceInfo(
      name: json['name'] ?? '',
      ip: json['ip'] ?? '',
      port: json['port'] ?? 0,
      serviceType: json['type_name'] ?? '',
      txtRecords: Map<String, String>.from(json['txt'] ?? {}),
    ));
    print('Found device: \\${json['name']} at \\${json['ip']}:\\${json['port']}');
  } else if (json['type'] == 'error') {
    print('Error: \\${json['message']}');
  }
}, debug: 2);
// Wait for 10 seconds
await Future.delayed(Duration(seconds: 10));
scanner.stopScan();
final scanner = NativeMdnsScanner();
final foundDevices = <DeviceInfo>[];
await scanner.startPeriodicScanJsonWithDone(
  '_googlecast._tcp',
  (json) {
    if (json['type'] == 'device') {
      foundDevices.add(DeviceInfo(
        name: json['name'] ?? '',
        ip: json['ip'] ?? '',
        port: json['port'] ?? 0,
        serviceType: json['type_name'] ?? '',
        txtRecords: Map<String, String>.from(json['txt'] ?? {}),
        queryNumber: json['queryNumber'] ?? 0,
      ));
      print('Found device: \\${json['name']} (query #\\${json['queryNumber']})');
    } else if (json['type'] == 'error') {
      print('Error: \\${json['message']}');
    }
  },
  queryIntervalMs: 3000, // Query every 3 seconds
  totalDurationMs: 12000, // Run for 12 seconds
  debug: 2,
);

CLI Tool

The package includes a command-line tool for quick testing and debugging:

# Install globally
dart pub global activate native_mdns_scanner

# Or run directly
dart run bin/mdns_cli.dart

CLI Examples

# Simple scan
dart run bin/mdns_cli.dart scan _googlecast._tcp

# Scan multiple services
dart run bin/mdns_cli.dart multi _googlecast._tcp _airplay._tcp _raop._tcp

# Periodic scanning
dart run bin/mdns_cli.dart periodic _googlecast._tcp --interval 5 --duration 30

# Timing analysis
dart run bin/mdns_cli.dart timing _googlecast._tcp _airplay._tcp --timeout 20

CLI Options

  • --timeout <seconds>: Scan timeout (default: 15)
  • --interval <seconds>: Query interval for periodic scan (default: 5)
  • --duration <seconds>: Total duration for periodic scan (default: 30)
  • --help, -h: Show help message

Command-Line Interface (CLI)

The CLI tool supports scanning and timing analysis from the command line:

dart run bin/mdns_cli.dart scan _googlecast._tcp

JSON Output & Silent Mode

  • Use --json to output only valid JSON (one object per line). All non-JSON output is suppressed.
  • When --json is enabled, native log output is also fully suppressed (silent mode) for clean machine-readable output.

Common Service Types

Service Type Description Example Devices
_googlecast._tcp Google Cast Chromecast, Google Home
_airplay._tcp Apple AirPlay Apple TV, AirPort Express
_raop._tcp Remote Audio Output AirPort Express, HomePod
_http._tcp HTTP services Web servers, cameras
_ssh._tcp SSH servers Raspberry Pi, servers
_printer._tcp Network printers HP, Canon printers
_ipp._tcp Internet Printing Modern network printers

API Reference

MdnsFfi Class

The main class for mDNS operations is now recommended to be used as NativeMdnsScanner (a type alias for MdnsFfi).

Methods

  • startScan(String serviceType) - Start scanning for a service type
  • startPeriodicScan(String serviceType, {int queryIntervalMs, int totalDurationMs}) - Start periodic scanning
  • stopScan() - Stop all active scans
  • scanMultipleServices(List<String> serviceTypes, {Duration timeout}) - Scan multiple services simultaneously
  • scanMultipleServicesWithPeriodic(List<String> serviceTypes, {Duration timeout, Duration queryInterval}) - Periodic multi-service scanning
  • isScanning() - Check if any scan is active
  • foundDevices - Get list of discovered devices
  • getDevicesByServiceType() - Get devices grouped by service type
  • dispose() - Clean up resources

DeviceInfo Class

Represents a discovered mDNS service.

Properties

  • String name - Service name
  • String ip - IP address
  • int port - Port number
  • String serviceType - Service type (e.g., '_googlecast._tcp')
  • Map<String, String> txtRecords - TXT record key-value pairs
  • DateTime foundAt - Discovery timestamp
  • int queryNumber - Which query discovered this device

TimingAnalyzer Class

Utility for analyzing discovery timing patterns.

Methods

  • static void analyzeTimings(List<DeviceInfo> devices) - Print detailed timing analysis
  • static Map<String, dynamic> getStatistics(List<DeviceInfo> devices) - Get timing statistics
  • static String formatTime(DateTime time) - Format time as HH:mm:ss.SSS

Building from Source

Prerequisites

  • macOS (for building the native library)
  • Xcode command line tools
  • Dart SDK 3.0+

Build Steps

  1. Clone the repository:
git clone https://github.com/changyy/ffi-mdns-macos-dart.git
cd ffi-mdns-macos-dart
  1. Build the native library:
cd native
./build.sh
  1. Install Dart dependencies:
dart pub get
  1. Run examples:
dart run example/native_mdns_scanner_example.dart
dart run example/timing_test_example.dart

Project Structure

ffi-mdns-macos-dart/
β”œβ”€β”€ lib/
β”‚   β”œβ”€β”€ native_mdns_scanner.dart           # Main export
β”‚   └── src/
β”‚       β”œβ”€β”€ mdns_bindings.dart             # FFI bindings
β”‚       β”œβ”€β”€ device_info.dart               # Device model
β”‚       └── timing_analyzer.dart           # Timing utilities
β”œβ”€β”€ native/
β”‚   β”œβ”€β”€ mdns_ffi.h                         # C header
β”‚   β”œβ”€β”€ mdns_ffi.m                         # Objective-C implementation
β”‚   β”œβ”€β”€ build.sh                           # Build script
β”‚   └── libmdns_ffi.dylib                  # Compiled library
β”œβ”€β”€ example/
β”‚   β”œβ”€β”€ native_mdns_scanner_example.dart   # Basic usage example
β”‚   └── timing_test_example.dart           # Timing analysis example
β”œβ”€β”€ bin/
β”‚   └── mdns_cli.dart                      # CLI tool
└── test/
    └── mdns_test.dart                     # Unit tests

Performance Notes

  • Simultaneous scanning: This library can scan multiple service types simultaneously, unlike many other mDNS libraries that scan sequentially
  • Event processing: Uses a timer-based approach to process mDNS events efficiently
  • Memory management: Properly manages native resources and prevents memory leaks

Troubleshooting

Common Issues

  1. Library not found: Make sure libmdns_ffi.dylib is in the native/ directory
  2. Permission denied: On macOS, you might need to allow network access in System Preferences
  3. No devices found: Some devices may not respond immediately; try increasing the timeout

Debug Mode

Enable verbose logging by setting debug flags in your code:

// This will print detailed discovery information
final scanner = NativeMdnsScanner(debugLevel: 2); // 0=quiet, 1=error/result, 2=normal, 3=verbose
// Logs are automatically printed to console

Contributing

  1. Fork the repository
  2. Create your feature branch: git checkout -b feature/amazing-feature
  3. Commit your changes: git commit -m 'Add amazing feature'
  4. Push to the branch: git push origin feature/amazing-feature
  5. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Acknowledgments

  • Built using Dart FFI for native interoperability
  • Uses macOS Bonjour/mDNS APIs through Objective-C
  • Inspired by the need for simultaneous multi-service mDNS scanning
  • multicast_dns - Pure Dart mDNS implementation
  • bonsoir - Cross-platform service discovery
  • nsd - Network Service Discovery plugin

Libraries

native_mdns_scanner
Native mDNS/Bonjour service scanner for macOS.