Skip to content

Commit

Permalink
feat: add support for sending to a specific addr and port with `S…
Browse files Browse the repository at this point in the history
…erviceInfo.async_request` and `ServiceInfo.request` (#1192)
  • Loading branch information
bdraco committed Jul 2, 2023
1 parent d713a45 commit 405f547
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 4 deletions.
30 changes: 26 additions & 4 deletions src/zeroconf/_services/info.py
Expand Up @@ -55,6 +55,7 @@
_DNS_OTHER_TTL,
_FLAGS_QR_QUERY,
_LISTENER_TIME,
_MDNS_PORT,
_TYPE_A,
_TYPE_AAAA,
_TYPE_NSEC,
Expand Down Expand Up @@ -616,7 +617,12 @@ def _is_complete(self) -> bool:
return bool(self.text is not None and (self._ipv4_addresses or self._ipv6_addresses))

def request(
self, zc: 'Zeroconf', timeout: float, question_type: Optional[DNSQuestionType] = None
self,
zc: 'Zeroconf',
timeout: float,
question_type: Optional[DNSQuestionType] = None,
addr: Optional[str] = None,
port: int = _MDNS_PORT,
) -> bool:
"""Returns true if the service could be discovered on the
network, and updates this object with details discovered.
Expand All @@ -628,13 +634,29 @@ def request(
assert zc.loop is not None and zc.loop.is_running()
if zc.loop == get_running_loop():
raise RuntimeError("Use AsyncServiceInfo.async_request from the event loop")
return bool(run_coro_with_timeout(self.async_request(zc, timeout, question_type), zc.loop, timeout))
return bool(
run_coro_with_timeout(
self.async_request(zc, timeout, question_type, addr, port), zc.loop, timeout
)
)

async def async_request(
self, zc: 'Zeroconf', timeout: float, question_type: Optional[DNSQuestionType] = None
self,
zc: 'Zeroconf',
timeout: float,
question_type: Optional[DNSQuestionType] = None,
addr: Optional[str] = None,
port: int = _MDNS_PORT,
) -> bool:
"""Returns true if the service could be discovered on the
network, and updates this object with details discovered.
This method will be run in the event loop.
Passing addr and port is optional, and will default to the
mDNS multicast address and port. This is useful for directing
requests to a specific host that may be able to respond across
subnets.
"""
if not zc.started:
await zc.async_wait_for_start()
Expand All @@ -658,7 +680,7 @@ async def async_request(
first_request = False
if not out.questions:
return self.load_from_cache(zc)
zc.async_send(out)
zc.async_send(out, addr, port)
next_ = now + delay
delay *= 2
next_ += random.randint(*_AVOID_SYNC_DELAY_RANDOM_INTERVAL)
Expand Down
83 changes: 83 additions & 0 deletions tests/services/test_info.py
Expand Up @@ -956,6 +956,89 @@ async def test_port_changes_are_seen():
await aiozc.async_close()


@pytest.mark.asyncio
async def test_port_changes_are_seen_with_directed_request():
"""Test that port changes are seen by async_request with a directed request."""
type_ = "_http._tcp.local."
registration_name = "multiarec.%s" % type_
desc = {'path': '/~paulsm/'}
aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])
host = "multahost.local."

# New kwarg way
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(
r.DNSNsec(
registration_name,
const._TYPE_NSEC,
const._CLASS_IN | const._CLASS_UNIQUE,
const._DNS_OTHER_TTL,
registration_name,
[const._TYPE_AAAA],
),
0,
)
generated.add_answer_at_time(
r.DNSService(
registration_name,
const._TYPE_SRV,
const._CLASS_IN | const._CLASS_UNIQUE,
10000,
0,
0,
80,
host,
),
0,
)
generated.add_answer_at_time(
r.DNSAddress(
host,
const._TYPE_A,
const._CLASS_IN,
10000,
b'\x7f\x00\x00\x01',
),
0,
)
generated.add_answer_at_time(
r.DNSText(
registration_name,
const._TYPE_TXT,
const._CLASS_IN | const._CLASS_UNIQUE,
10000,
b'\x04ff=0\x04ci=2\x04sf=0\x0bsh=6fLM5A==',
),
0,
)
await aiozc.zeroconf.async_wait_for_start()
await asyncio.sleep(0)
aiozc.zeroconf.handle_response(r.DNSIncoming(generated.packets()[0]))

generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
generated.add_answer_at_time(
r.DNSService(
registration_name,
const._TYPE_SRV,
const._CLASS_IN | const._CLASS_UNIQUE,
10000,
90,
90,
81,
host,
),
0,
)
aiozc.zeroconf.handle_response(r.DNSIncoming(generated.packets()[0]))

info = ServiceInfo(type_, registration_name, 80, 10, 10, desc, host)
await info.async_request(aiozc.zeroconf, timeout=200, addr="127.0.0.1", port=5353)
assert info.port == 81
assert info.priority == 90
assert info.weight == 90
await aiozc.async_close()


@pytest.mark.asyncio
async def test_ipv4_changes_are_seen():
"""Test that ipv4 changes are seen by async_request."""
Expand Down

0 comments on commit 405f547

Please sign in to comment.