import 'dart:async';
import 'dart:io';
import 'src/constants.dart';
import 'src/lookup_resolver.dart';
import 'src/native_protocol_client.dart';
import 'src/packet.dart';
import 'src/resource_record.dart';
export 'package:multicast_dns/src/resource_record.dart';
typedef NetworkInterfacesFactory =
Future<Iterable<NetworkInterface>> Function(InternetAddressType type);
typedef RawDatagramSocketFactory =
Future<RawDatagramSocket> Function(
dynamic host,
int port, {
bool reuseAddress,
bool reusePort,
int ttl,
});
class MDnsClient {
MDnsClient({
RawDatagramSocketFactory rawDatagramSocketFactory = RawDatagramSocket.bind,
}) : _rawDatagramSocketFactory = rawDatagramSocketFactory;
bool _starting = false;
bool _started = false;
RawDatagramSocket? _incomingIPv4;
final List<RawDatagramSocket> _ipv6InterfaceSockets = <RawDatagramSocket>[];
final LookupResolver _resolver = LookupResolver();
final ResourceRecordCache _cache = ResourceRecordCache();
final RawDatagramSocketFactory _rawDatagramSocketFactory;
InternetAddress? _mDnsAddress;
int? _mDnsPort;
Future<Iterable<NetworkInterface>> allInterfacesFactory(
InternetAddressType type,
) {
return NetworkInterface.list(
includeLinkLocal: true,
type: type,
includeLoopback: true,
);
}
Future<void> start({
InternetAddress? listenAddress,
NetworkInterfacesFactory? interfacesFactory,
int mDnsPort = mDnsPort,
InternetAddress? mDnsAddress,
Function? onError,
}) async {
listenAddress ??= InternetAddress.anyIPv4;
interfacesFactory ??= allInterfacesFactory;
assert(
listenAddress.address == InternetAddress.anyIPv4.address ||
listenAddress.address == InternetAddress.anyIPv6.address,
);
if (_started || _starting) {
return;
}
_starting = true;
final int selectedMDnsPort = _mDnsPort = mDnsPort;
_mDnsAddress = mDnsAddress;
final RawDatagramSocket incoming = await _rawDatagramSocketFactory(
listenAddress.address,
selectedMDnsPort,
reuseAddress: true,
reusePort: true,
ttl: 255,
);
if (incoming.address != InternetAddress.anyIPv6) {
_incomingIPv4 = incoming;
} else {
_ipv6InterfaceSockets.add(incoming);
}
_mDnsAddress ??= incoming.address.type == InternetAddressType.IPv4
? mDnsAddressIPv4
: mDnsAddressIPv6;
final List<NetworkInterface> interfaces = (await interfacesFactory(
listenAddress.type,
)).toList();
for (final interface in interfaces) {
final InternetAddress targetAddress = interface.addresses[0];
if (targetAddress.type == InternetAddressType.IPv6) {
final RawDatagramSocket socket = await _rawDatagramSocketFactory(
targetAddress,
selectedMDnsPort,
reuseAddress: true,
reusePort: true,
ttl: 255,
);
_ipv6InterfaceSockets.add(socket);
socket.setRawOption(
RawSocketOption.fromInt(
RawSocketOption.levelIPv6,
RawSocketOption.IPv6MulticastInterface,
interface.index,
),
);
}
incoming.joinMulticast(_mDnsAddress!, interface);
}
incoming.listen(
(RawSocketEvent event) => _handleIncoming(event, incoming),
onError: onError,
);
_started = true;
_starting = false;
}
/// Stop the client and close any associated sockets.
void stop() {
if (!_started) {
return;
}
if (_starting) {
throw StateError('Cannot stop mDNS client while it is starting.');
}
_incomingIPv4?.close();
_incomingIPv4 = null;
for (final RawDatagramSocket socket in _ipv6InterfaceSockets) {
socket.close();
}
_ipv6InterfaceSockets.clear();
_resolver.clearPendingRequests();
_started = false;
}
Stream<T> lookup<T extends ResourceRecord>(
ResourceRecordQuery query, {
Duration timeout = const Duration(seconds: 5),
}) {
final int? selectedMDnsPort = _mDnsPort;
if (!_started || selectedMDnsPort == null) {
throw StateError('mDNS client must be started before calling lookup.');
}
final cached = <T>[];
_cache.lookup<T>(
query.fullyQualifiedName,
query.resourceRecordType,
cached,
);
if (cached.isNotEmpty) {
final controller = StreamController<T>();
cached.forEach(controller.add);
controller.close();
return controller.stream;
}
final Stream<T> results = _resolver.addPendingRequest<T>(
query.resourceRecordType,
query.fullyQualifiedName,
timeout,
);
final List<int> packet = query.encode();
if (_mDnsAddress?.type == InternetAddressType.IPv4) {
_incomingIPv4?.send(packet, _mDnsAddress!, selectedMDnsPort);
} else {
for (final RawDatagramSocket socket in _ipv6InterfaceSockets) {
socket.send(packet, _mDnsAddress!, selectedMDnsPort);
}
}
return results;
}
void _handleIncoming(RawSocketEvent event, RawDatagramSocket incoming) {
if (event == RawSocketEvent.read) {
final Datagram? datagram = incoming.receive();
if (datagram == null) {
return;
}
final List<ResourceRecord>? response = decodeMDnsResponse(datagram.data);
if (response != null) {
_cache.updateRecords(response);
_resolver.handleResponse(response);
return;
}
}
}
}