GitHub - flubshi/python-zeroconf: A pure python implementation of multicast DNS service discovery
This project's versions follow the following pattern: MAJOR.MINOR.PATCH.
This project is actively maintained.
Before version 0.32, most traffic reduction techniques described in https://datatracker.ietf.org/doc/html/rfc6762#section-7 where not implemented which could lead to excessive network traffic. It is highly recommended that version 0.32 or later is used if this is a concern.
See examples directory for more.
Documentation for breaking changes era on the side of the caution and likely overstates the risk on many of these. If you are not accessing zeroconf internals, you can likely not be concerned with the breaking changes below:
BREAKING CHANGE: zeroconf.asyncio has been renamed zeroconf.aio (#503) @bdraco
The asyncio name could shadow system asyncio in some cases. If zeroconf is in sys.path, this would result in loading zeroconf.asyncio when system asyncio was intended.
BREAKING CHANGE: Update internal version check to match docs (3.6+) (#491) @bdraco
Python version eariler then 3.6 were likely broken with zeroconf already, however the version is now explictly checked.
BREAKING CHANGE: RecordUpdateListener now uses async_update_records instead of update_record (#419, #726) @bdraco
This allows the listener to receive all the records that have been updated in a single transaction such as a packet or cache expiry.
update_record has been deprecated in favor of async_update_records A compatibility shim exists to ensure classes that use RecordUpdateListener as a base class continue to have update_record called, however they should be updated as soon as possible.
A new method async_update_records_complete is now called on each listener when all listeners have completed processing updates and the cache has been updated. This allows ServiceBrowsers to delay calling handlers until they are sure the cache has been updated as its a common pattern to call for ServiceInfo when a ServiceBrowser handler fires.
The async_ prefix was choosen to make it clear that these functions run in the eventloop and should never do blocking I/O. Before 0.32+ these functions ran in a select() loop and should not have been doing any blocking I/O, but it was not clear to implementors that I/O would block the loop.
BREAKING CHANGE: Ensure listeners do not miss initial packets if Engine starts too quickly (#387) @bdraco
When manually creating a zeroconf.Engine object, it is no longer started automatically. It must manually be started by calling .start() on the created object.
The Engine thread is now started after all the listeners have been added to avoid a race condition where packets could be missed at startup.
BREAKING CHANGE: Remove DNSOutgoing.packet backwards compatibility (#569) @bdraco
DNSOutgoing.packet only returned a partial message when the DNSOutgoing contents exceeded _MAX_MSG_ABSOLUTE or _MAX_MSG_TYPICAL This was a legacy function that was replaced with .packets() which always returns a complete payload in #248 As packet() should not be used since it will end up missing data, it has been removed
BREAKING CHANGE: Mark DNSOutgoing write functions as protected (#633) @bdraco
These functions are not intended to be used by external callers and the API is not likely to be stable in the future
BREAKING CHANGE: Prefix cache functions that are non threadsafe with async_ (#724) @bdraco
Adding (zc.cache.add -> zc.cache.async_add_records), removing (zc.cache.remove -> zc.cache.async_remove_records), and expiring the cache (zc.cache.expire -> zc.cache.async_expire) the cache is not threadsafe and must be called from the event loop (previously the Engine select loop before 0.32)
These functions should only be run from the event loop as they are NOT thread safe.
We never expect these functions will be called externally, however it was possible so this is documented as a breaking change. It is highly recommended that external callers do not modify the cache directly.
TRAFFIC REDUCTION: Add support for handling QU questions (#621) @bdraco
Implements RFC 6762 sec 5.4: Questions Requesting Unicast Responses datatracker.ietf.org/doc/html/rfc6762#section-5.4
TRAFFIC REDUCTION: Protect the network against excessive packet flooding (#619) @bdraco
TRAFFIC REDUCTION: Suppress additionals when they are already in the answers section (#617) @bdraco
TRAFFIC REDUCTION: Avoid including additionals when the answer is suppressed by known-answer supression (#614) @bdraco
TRAFFIC REDUCTION: Implement multi-packet known answer supression (#687) @bdraco
Implements datatracker.ietf.org/doc/html/rfc6762#section-7.2
TRAFFIC REDUCTION: Efficiently bucket queries with known answers (#698) @bdraco
TRAFFIC REDUCTION: Implement duplicate question supression (#770) @bdraco
MAJOR BUG: Ensure matching PTR queries are returned with the ANY query (#618) @bdraco
MAJOR BUG: Fix lookup of uppercase names in registry (#597) @bdraco
If the ServiceInfo was registered with an uppercase name and the query was for a lowercase name, it would not be found and vice-versa.
MAJOR BUG: Ensure unicast responses can be sent to any source port (#598) @bdraco
Unicast responses were only being sent if the source port was 53, this prevented responses when testing with dig:
dig -p 5353 @224.0.0.251 media-12.local
The above query will now see a response
MAJOR BUG: Fix queries for AAAA records (#616) @bdraco
Add async_apple_scanner example (#719) @bdraco
Add support for requesting QU questions to ServiceBrowser and ServiceInfo (#787) @bdraco
Ensure the queue is created before adding listeners to ServiceBrowser (#785) @bdraco
The callback from the listener could generate an event that would fire in async context that should have gone to the queue which could result in the consumer running a sync call in the event loop and blocking it.
Add a guard to prevent running ServiceInfo.request in async context (#784) @bdraco
Inline utf8 decoding when processing incoming packets (#782) @bdraco
Drop utf cache from _dns (#781) (later reverted) @bdraco
Switch to using a simple cache instead of lru_cache (#779) (later reverted) @bdraco
Fix Responding to Address Queries (RFC6762 section 6.2) (#777) @bdraco
Fix deadlock on ServiceBrowser shutdown with PyPy (#774) @bdraco
Add a guard against the task list changing when shutting down (#776) @bdraco
Improve performance of parsing DNSIncoming by caching read_utf (#769) (later reverted) @bdraco
Switch to using an asyncio.Event for async_wait (#759) @bdraco
We no longer need to check for thread safety under a asyncio.Condition as the ServiceBrowser and ServiceInfo internals schedule coroutines in the eventloop.
Simplify ServiceBrowser callsbacks (#756) @bdraco
Revert: Fix thread safety in _ServiceBrowser.update_records_complete (#708) (#755) @bdraco
Drop AsyncServiceListener (#754) @bdraco (Never shipped)
Run ServiceBrowser queries in the event loop (#752) @bdraco
Remove unused argument from AsyncZeroconf (#751) @bdraco
Fix warning about Zeroconf._async_notify_all not being awaited in sync shutdown (#750) @bdraco
Update async_service_info_request example to ensure it runs in the right event loop (#749) @bdraco
Run ServiceInfo requests in the event loop (#748) @bdraco
Remove support for notify listeners (#733) @bdraco (Never shipped)
Relocate service browser tests to tests/services/test_browser.py (#745) @bdraco
Relocate ServiceInfo to zeroconf._services.info (#741) @bdraco
Run question answer callbacks from add_listener in the event loop (#740) @bdraco
Calling async_update_records and async_update_records_complete should always happen in the event loop to ensure implementers do not need to worry about thread safety
Remove second level caching from ServiceBrowsers (#737) @bdraco
The ServiceBrowser had its own cache of the last time it saw a service which was reimplementing the DNSCache and presenting a source of truth problem that lead to unexpected queries when the two disagreed.
Breakout ServiceBrowser handler from listener creation (#736) @bdraco
Add coverage for the handler from listener
Add fast cache lookup functions (#732) @bdraco
The majority of our lookups happen in the event loop so there is no need for them to be threadsafe. Now that the codebase is more clear about what needs to be threadsafe and what does not need to be threadsafe we can use the much faster non-threadsafe versions in the places where we are calling from the event loop.
Switch to using DNSRRSet in RecordManager (#735) @bdraco
DNSRRSet is able to do O(1) lookups of records assuming there are no collisions.
Fix server cache to be case-insensitive (#731) @bdraco
If the server name had uppercase chars and any of the matching records were lowercase, the server would not be found
Fix cache handling of records with different TTLs (#729) @bdraco
There should only be one unique record in the cache at a time as having multiple unique records will different TTLs in the cache can result in unexpected behavior since some functions returned all matching records and some fetched from the right side of the list to return the newest record. Intead we now store the records in a dict to ensure that the newest record always replaces the same unique record and we never have a source of truth problem determining the TTL of a record from the cache.
Rename handlers and internals to make it clear what is threadsafe (#726) @bdraco
It was too easy to get confused about what was threadsafe and what was not threadsafe which lead to unexpected failures. Rename functions to make it clear what will be run in the event loop and what is expected to be threadsafe
Fix ServiceInfo with multiple A records (#725) @bdraco
If there were multiple A records for the host, ServiceInfo would always return the last one that was in the incoming packet which was usually not the one that was wanted.
Synchronize time for fate sharing (#718) @bdraco
Cleanup typing in zero._core and document ignores (#714) @bdraco
Cleanup typing in zeroconf._logger (#715) @bdraco
Cleanup typing in zeroconf._utils.net (#713) @bdraco
Cleanup typing in zeroconf._services (#711) @bdraco
Cleanup typing in zeroconf._services.registry (#712) @bdraco
Add setter for DNSQuestion to easily make a QU question (#710) @bdraco
Set stale unique records to expire 1s in the future instead of instant removal (#706) @bdraco
tools.ietf.org/html/rfc6762#section-10.2 Queriers receiving a Multicast DNS response with a TTL of zero SHOULD NOT immediately delete the record from the cache, but instead record a TTL of 1 and then delete the record one second later. In the case of multiple Multicast DNS responders on the network described in Section 6.6 above, if one of the responders shuts down and incorrectly sends goodbye packets for its records, it gives the other cooperating responders one second to send out their own response to "rescue" the records before they expire and are deleted.
Fix thread safety in _ServiceBrowser.update_records_complete (#708) @bdraco
Split DNSOutgoing/DNSIncoming/DNSMessage into zeroconf._protocol (#705) @bdraco
Abstract DNSOutgoing ttl write into _write_ttl (#695) @bdraco
Rollback data in one call instead of poping one byte at a time in DNS Outgoing (#696) @bdraco
Suppress additionals when answer is suppressed (#690) @bdraco
Move setting DNS created and ttl into its own function (#692) @bdraco
Add truncated property to DNSMessage to lookup the TC bit (#686) @bdraco
Check if SO_REUSEPORT exists instead of using an exception catch (#682) @bdraco
Use DNSRRSet for known answer suppression (#680) @bdraco
DNSRRSet uses hash table lookups under the hood which is much faster than the linear searches used by DNSRecord.suppressed_by
Add DNSRRSet class for quick hashtable lookups of records (#678) @bdraco
This class will be used to do fast checks to see if records should be suppressed by a set of answers.
Allow unregistering a service multiple times (#679) @bdraco
Remove unreachable BadTypeInNameException check in _ServiceBrowser (#677) @bdraco
Update async_browser.py example to use AsyncZeroconfServiceTypes (#665) @bdraco
Add an AsyncZeroconfServiceTypes to mirror ZeroconfServiceTypes to zeroconf.aio (#658) @bdraco
Remove all calls to the executor in AsyncZeroconf (#653) @bdraco
Set __all__ in zeroconf.aio to ensure private functions do now show in the docs (#652) @bdraco
Ensure interface_index_to_ip6_address skips ipv4 adapters (#651) @bdraco
Add async_unregister_all_services to AsyncZeroconf (#649) @bdraco
Ensure services are removed from the registry when calling unregister_all_services (#644) @bdraco
There was a race condition where a query could be answered for a service in the registry while goodbye packets which could result a fresh record being broadcast after the goodbye if a query came in at just the right time. To avoid this, we now remove the services from the registry right after we generate the goodbye packet
Use ServiceInfo.key/ServiceInfo.server_key instead of lowering in ServiceRegistry (#647) @bdraco
Ensure the ServiceInfo.key gets updated when the name is changed externally (#645) @bdraco
Ensure AsyncZeroconf.async_close can be called multiple times like Zeroconf.close (#638) @bdraco
Ensure eventloop shutdown is threadsafe (#636) @bdraco
Return early in the shutdown/close process (#632) @bdraco
Remove unreachable cache check for DNSAddresses (#629) @bdraco
The ServiceBrowser would check to see if a DNSAddress was already in the cache and return early to avoid sending updates when the address already was held in the cache. This check was not needed since there is already a check a few lines before as self.zc.cache.get(record) which effectively does the same thing. This lead to the check never being covered in the tests and 2 cache lookups when only one was needed.
Add test for wait_condition_or_timeout_times_out util (#630) @bdraco
Return early on invalid data received (#628) @bdraco
Improve coverage for handling invalid incoming data
Add test to ensure ServiceBrowser sees port change as an update (#625) @bdraco
Fix random test failures due to monkey patching not being undone between tests (#626) @bdraco
Switch patching to use unitest.mock.patch to ensure the patch is reverted when the test is completed
Ensure zeroconf can be loaded when the system disables IPv6 (#624) @bdraco
Eliminate aio sender thread (#622) @bdraco
Replace select loop with asyncio loop (#504) @bdraco
Add is_recent property to DNSRecord (#620) @bdraco
RFC 6762 defines recent as not multicast within one quarter of its TTL datatracker.ietf.org/doc/html/rfc6762#section-5.4
Breakout the query response handler into its own class (#615) @bdraco
Add the ability for ServiceInfo.dns_addresses to filter by address type (#612) @bdraco
Make DNSRecords hashable (#611) @bdraco
Allows storing them in a set for de-duplication
Needed to be able to check for duplicates to solve #604
Ensure the QU bit is set for probe queries (#609) @bdraco
The bit should be set per datatracker.ietf.org/doc/html/rfc6762#section-8.1
Log destination when sending packets (#606) @bdraco
Fix docs version to match readme (cpython 3.6+) (#602) @bdraco
Add ZeroconfServiceTypes to zeroconf.__all__ (#601) @bdraco
This class is in the readme, but is not exported by default
Add id_ param to allow setting the id in the DNSOutgoing constructor (#599) @bdraco
Add unicast property to DNSQuestion to determine if the QU bit is set (#593) @bdraco
Reduce branching in DNSOutgoing.add_answer_at_time (#592) @bdraco
Breakout DNSCache into zeroconf.cache (#568) @bdraco
Removed protected imports from zeroconf namespace (#567) @bdraco
Fix invalid typing in ServiceInfo._set_text (#554) @bdraco
Move QueryHandler and RecordManager handlers into zeroconf.handlers (#551) @bdraco
Move ServiceListener to zeroconf.services (#550) @bdraco
Move the ServiceRegistry into its own module (#549) @bdraco
Move ServiceStateChange to zeroconf.services (#548) @bdraco
Relocate core functions into zeroconf.core (#547) @bdraco
Breakout service classes into zeroconf.services (#544) @bdraco
Move service_type_name to zeroconf.utils.name (#543) @bdraco
Relocate DNS classes to zeroconf.dns (#541) @bdraco
Update zeroconf.aio import locations (#539) @bdraco
Move int2byte to zeroconf.utils.struct (#540) @bdraco
Breakout network utils into zeroconf.utils.net (#537) @bdraco
Move time utility functions into zeroconf.utils.time (#536) @bdraco
Avoid making DNSOutgoing aware of the Zeroconf object (#535) @bdraco
Move logger into zeroconf.logger (#533) @bdraco
Move exceptions into zeroconf.exceptions (#532) @bdraco
Move constants into const.py (#531) @bdraco
Move asyncio utils into zeroconf.utils.aio (#530) @bdraco
Move ipversion auto detection code into its own function (#524) @bdraco
Breaking change: Update python compatibility as PyPy3 7.2 is required (#523) @bdraco
Remove broad exception catch from RecordManager.remove_listener (#517) @bdraco
Small cleanups to RecordManager.add_listener (#516) @bdraco
Move RecordUpdateListener management into RecordManager (#514) @bdraco
Break out record updating into RecordManager (#512) @bdraco
Remove uneeded wait in the Engine thread (#511) @bdraco
Extract code for handling queries into QueryHandler (#507) @bdraco
Set the TC bit for query packets where the known answers span multiple packets (#494) @bdraco
Ensure packets are properly seperated when exceeding maximum size (#498) @bdraco
Ensure that questions that exceed the max packet size are moved to the next packet. This fixes DNSQuestions being sent in multiple packets in violation of: datatracker.ietf.org/doc/html/rfc6762#section-7.2
Ensure only one resource record is sent when a record exceeds _MAX_MSG_TYPICAL datatracker.ietf.org/doc/html/rfc6762#section-17
Make a base class for DNSIncoming and DNSOutgoing (#497) @bdraco
Remove unused __ne__ code from Python 2 era (#492) @bdraco
Lint before testing in the CI (#488) @bdraco
Add AsyncServiceBrowser example (#487) @bdraco
Move threading daemon property into ServiceBrowser class (#486) @bdraco
Enable test_integration_with_listener_class test on PyPy (#485) @bdraco
AsyncServiceBrowser must recheck for handlers to call when holding condition (#483)
There was a short race condition window where the AsyncServiceBrowser could add to _handlers_to_call in the Engine thread, have the condition notify_all called, but since the AsyncServiceBrowser was not yet holding the condition it would not know to stop waiting and process the handlers to call.
Relocate ServiceBrowser wait time calculation to seperate function (#484) @bdraco
Eliminate the need to duplicate code between the ServiceBrowser and AsyncServiceBrowser to calculate the wait time.
Switch from using an asyncio.Event to asyncio.Condition for waiting (#482) @bdraco
ServiceBrowser must recheck for handlers to call when holding condition (#477) @bdraco
There was a short race condition window where the ServiceBrowser could add to _handlers_to_call in the Engine thread, have the condition notify_all called, but since the ServiceBrowser was not yet holding the condition it would not know to stop waiting and process the handlers to call.
Provide a helper function to convert milliseconds to seconds (#481) @bdraco
Fix AsyncServiceInfo.async_request not waiting long enough (#480) @bdraco
Add support for updating multiple records at once to ServiceInfo (#474) @bdraco
Narrow exception catch in DNSAddress.__repr__ to only expected exceptions (#473) @bdraco
Add test coverage to ensure ServiceInfo rejects expired records (#468) @bdraco
Reduce branching in service_type_name (#472) @bdraco
Fix flakey test_update_record (#470) @bdraco
Reduce branching in Zeroconf.handle_response (#467) @bdraco
Ensure PTR questions asked in uppercase are answered (#465) @bdraco
Clear cache between ServiceTypesQuery tests (#466) @bdraco
Break apart Zeroconf.handle_query to reduce branching (#462) @bdraco
Support for context managers in Zeroconf and AsyncZeroconf (#284) @shenek
Use constant for service type enumeration (#461) @bdraco
Reduce branching in Zeroconf.handle_response (#459) @bdraco
Reduce branching in Zeroconf.handle_query (#460) @bdraco
Enable pylint (#438) @bdraco
Trap OSError directly in Zeroconf.send instead of checking isinstance (#453) @bdraco
Disable protected-access on the ServiceBrowser usage of _handlers_lock (#452) @bdraco
Mark functions with too many branches in need of refactoring (#455) @bdraco
Disable pylint no-self-use check on abstract methods (#451) @bdraco
Use unique name in test_async_service_browser test (#450) @bdraco
Disable no-member check for WSAEINVAL false positive (#454) @bdraco
Mark methods used by asyncio without self use (#447) @bdraco
Extract _get_queue from zeroconf.asyncio._AsyncSender (#444) @bdraco
Fix redefining argument with the local name 'record' in ServiceInfo.update_record (#448) @bdraco
Remove unneeded-not in new_socket (#445) @bdraco
Disable broad except checks in places we still catch broad exceptions (#443) @bdraco
Merge _TYPE_CNAME and _TYPE_PTR comparison in DNSIncoming.read_others (#442) @bdraco
Convert unnecessary use of a comprehension to a list (#441) @bdraco
Remove unused now argument from ServiceInfo._process_record (#440) @bdraco
Disable pylint too-many-branches for functions that need refactoring (#439) @bdraco
Cleanup unused variables (#437) @bdraco
Cleanup unnecessary else after returns (#436) @bdraco
Add zeroconf.asyncio to the docs (#434) @bdraco
Fix warning when generating sphinx docs (#432) @bdraco
Implement an AsyncServiceBrowser to compliment the sync ServiceBrowser (#429) @bdraco
Seperate non-thread specific code from ServiceBrowser into _ServiceBrowserBase (#428) @bdraco
Remove is_type_unique as it is unused (#426)
Avoid checking the registry when answering requests for _services._dns-sd._udp.local. (#425) @bdraco
_services._dns-sd._udp.local. is a special case and should never be in the registry
Remove unused argument from ServiceInfo.dns_addresses (#423) @bdraco
Add methods to generate DNSRecords from ServiceInfo (#422) @bdraco
Seperate logic for consuming records in ServiceInfo (#421) @bdraco
Seperate query generation for ServiceBrowser (#420) @bdraco
Add async_request example with browse (#415) @bdraco
Add async_register_service/async_unregister_service example (#414) @bdraco
Add async_get_service_info to AsyncZeroconf and async_request to AsyncServiceInfo (#408) @bdraco
Add support for registering notify listeners (#409) @bdraco
Allow passing in a sync Zeroconf instance to AsyncZeroconf (#406) @bdraco
Use a dedicated thread for sending outgoing packets with asyncio (#404) @bdraco
Fix IPv6 setup under MacOS when binding to "" (#392) @bdraco
Ensure ZeroconfServiceTypes.find always cancels the ServiceBrowser (#389) @bdraco
There was a short window where the ServiceBrowser thread could be left running after Zeroconf is closed because the .join() was never waited for when a new Zeroconf object was created
Simplify DNSPointer processing in ServiceBrowser (#386) @bdraco
Ensure the cache is checked for name conflict after final service query with asyncio (#382) @bdraco
Complete ServiceInfo request as soon as all questions are answered (#380) @bdraco
Closes a small race condition where there were no questions to ask because the cache was populated in between checks
Coalesce browser questions scheduled at the same time (#379) @bdraco
Ensure duplicate packets do not trigger duplicate updates (#376) @bdraco
If TXT or SRV records update was already processed and then recieved again, it was possible for a second update to be called back in the ServiceBrowser
Only trigger a ServiceStateChange.Updated event when an ip address is added (#375) @bdraco
Fix RFC6762 Section 10.2 paragraph 2 compliance (#374) @bdraco
Reduce length of ServiceBrowser thread name with many types (#373) @bdraco
Remove Callable quoting (#371) @bdraco
Abstract check to see if a record matches a type the ServiceBrowser wants (#369) @bdraco
Reduce complexity of ServiceBrowser enqueue_callback (#368) @bdraco
Fix empty answers being added in ServiceInfo.request (#367) @bdraco
Ensure ServiceInfo populates all AAAA records (#366) @bdraco
Use get_all_by_details to ensure all records are loaded into addresses.
Only load A/AAAA records from cache once in load_from_cache if there is a SRV record present
Move duplicate code that checked if the ServiceInfo was complete into its own function
Remove black python 3.5 exception block (#365) @bdraco
Small cleanup of ServiceInfo.update_record (#364) @bdraco
Add new cache function get_all_by_details (#363) @bdraco When working with IPv6, multiple AAAA records can exist for a given host. get_by_details would only return the latest record in the cache.
Fix a case where the cache list can change during iteration
Small cleanups to asyncio tests (#362) @bdraco
Improve test coverage for name conflicts (#357) @bdraco
Return task objects created by AsyncZeroconf (#360) @nocarryr
LGPL, see COPYING file for details.