native_mdns_scanner
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:
files:
Configuration: Explicitly includes the library in the published package- Flutter Assets: For Flutter projects, library included as asset
- 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:
- Flutter Projects: Library included as asset in
pubspec.yaml
- Pure Dart: Library found via package dependency resolution
- Development: Local
native/
directory takes priority
The library search order:
- Package dependency path (highest priority for published packages)
- Local development path (
native/libmdns_ffi.dylib
) - Current directory and common build locations
- 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();
Using startPeriodicScanJsonWithDone (recommended new usage)
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 typestartPeriodicScan(String serviceType, {int queryIntervalMs, int totalDurationMs})
- Start periodic scanningstopScan()
- Stop all active scansscanMultipleServices(List<String> serviceTypes, {Duration timeout})
- Scan multiple services simultaneouslyscanMultipleServicesWithPeriodic(List<String> serviceTypes, {Duration timeout, Duration queryInterval})
- Periodic multi-service scanningisScanning()
- Check if any scan is activefoundDevices
- Get list of discovered devicesgetDevicesByServiceType()
- Get devices grouped by service typedispose()
- Clean up resources
DeviceInfo Class
Represents a discovered mDNS service.
Properties
String name
- Service nameString ip
- IP addressint port
- Port numberString serviceType
- Service type (e.g., '_googlecast._tcp')Map<String, String> txtRecords
- TXT record key-value pairsDateTime foundAt
- Discovery timestampint queryNumber
- Which query discovered this device
TimingAnalyzer Class
Utility for analyzing discovery timing patterns.
Methods
static void analyzeTimings(List<DeviceInfo> devices)
- Print detailed timing analysisstatic Map<String, dynamic> getStatistics(List<DeviceInfo> devices)
- Get timing statisticsstatic 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
- Clone the repository:
git clone https://github.com/changyy/ffi-mdns-macos-dart.git
cd ffi-mdns-macos-dart
- Build the native library:
cd native
./build.sh
- Install Dart dependencies:
dart pub get
- 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
- Library not found: Make sure
libmdns_ffi.dylib
is in thenative/
directory - Permission denied: On macOS, you might need to allow network access in System Preferences
- 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
- Fork the repository
- Create your feature branch:
git checkout -b feature/amazing-feature
- Commit your changes:
git commit -m 'Add amazing feature'
- Push to the branch:
git push origin feature/amazing-feature
- 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
Related Projects
- 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.