| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -39,6 +39,7 @@ def build(setup_kwargs: Any) -> None: | |||
| 39 | 39 | "src/zeroconf/_services/info.py", | |
| 40 | 40 | "src/zeroconf/_services/registry.py", | |
| 41 | 41 | "src/zeroconf/_updates.py", | |
| 42 | + "src/zeroconf/_utils/ipaddress.py", | ||
| 42 | 43 | "src/zeroconf/_utils/time.py", | |
| 43 | 44 | ], | |
| 44 | 45 | compiler_directives={"language_level": "3"}, # Python 3 | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -6,11 +6,15 @@ from .._dns cimport DNSAddress, DNSNsec, DNSPointer, DNSRecord, DNSService, DNST | |||
| 6 | 6 | from .._protocol.outgoing cimport DNSOutgoing | |
| 7 | 7 | from .._record_update cimport RecordUpdate | |
| 8 | 8 | from .._updates cimport RecordUpdateListener | |
| 9 | + from .._utils.ipaddress cimport ( | ||
| 10 | + get_ip_address_object_from_record, | ||
| 11 | + ip_bytes_and_scope_to_address, | ||
| 12 | + str_without_scope_id, | ||
| 13 | + ) | ||
| 9 | 14 | from .._utils.time cimport current_time_millis | |
| 10 | 15 | ||
| 11 | 16 | ||
| 12 | 17 | cdef object _resolve_all_futures_to_none | |
| 13 | - cdef object _cached_ip_addresses_wrapper | ||
| 14 | 18 | ||
| 15 | 19 | cdef object _TYPE_SRV | |
| 16 | 20 | cdef object _TYPE_TXT | |
@@ -33,13 +37,7 @@ cdef cython.set _ADDRESS_RECORD_TYPES | |||
| 33 | 37 | ||
| 34 | 38 | cdef bint TYPE_CHECKING | |
| 35 | 39 | cdef bint IPADDRESS_SUPPORTS_SCOPE_ID | |
| 36 | - | ||
| 37 | - cdef _get_ip_address_object_from_record(DNSAddress record) | ||
| 38 | - | ||
| 39 | - @cython.locals(address_str=str) | ||
| 40 | - cdef _str_without_scope_id(object addr) | ||
| 41 | - | ||
| 42 | - cdef _ip_bytes_and_scope_to_address(object addr, object scope_id) | ||
| 40 | + cdef object cached_ip_addresses | ||
| 43 | 41 | ||
| 44 | 42 | cdef class ServiceInfo(RecordUpdateListener): | |
| 45 | 43 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -23,8 +23,7 @@ | |||
| 23 | 23 | import asyncio | |
| 24 | 24 | import random | |
| 25 | 25 | import sys | |
| 26 | - from functools import lru_cache | ||
| 27 | - from ipaddress import IPv4Address, IPv6Address, _BaseAddress, ip_address | ||
| 26 | + from ipaddress import IPv4Address, IPv6Address, _BaseAddress | ||
| 28 | 27 | from typing import TYPE_CHECKING, Dict, List, Optional, Set, Union, cast | |
| 29 | 28 | ||
| 30 | 29 | from .._dns import ( | |
@@ -47,6 +46,12 @@ | |||
| 47 | 46 | run_coro_with_timeout, | |
| 48 | 47 | wait_for_future_set_or_timeout, | |
| 49 | 48 | ) | |
| 49 | + from .._utils.ipaddress import ( | ||
| 50 | + cached_ip_addresses, | ||
| 51 | + get_ip_address_object_from_record, | ||
| 52 | + ip_bytes_and_scope_to_address, | ||
| 53 | + str_without_scope_id, | ||
| 54 | + ) | ||
| 50 | 55 | from .._utils.name import service_type_name | |
| 51 | 56 | from .._utils.net import IPVersion, _encode_address | |
| 52 | 57 | from .._utils.time import current_time_millis | |
@@ -67,6 +72,8 @@ | |||
| 67 | 72 | _TYPE_TXT, | |
| 68 | 73 | ) | |
| 69 | 74 | ||
| 75 | + IPADDRESS_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0) | ||
| 76 | + | ||
| 70 | 77 | _IPVersion_All_value = IPVersion.All.value | |
| 71 | 78 | _IPVersion_V4Only_value = IPVersion.V4Only.value | |
| 72 | 79 | # https://datatracker.ietf.org/doc/html/rfc6762#section-5.2 | |
@@ -86,7 +93,6 @@ | |||
| 86 | 93 | DNS_QUESTION_TYPE_QU = DNSQuestionType.QU | |
| 87 | 94 | DNS_QUESTION_TYPE_QM = DNSQuestionType.QM | |
| 88 | 95 | ||
| 89 | - IPADDRESS_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0) | ||
| 90 | 96 | ||
| 91 | 97 | if TYPE_CHECKING: | |
| 92 | 98 | from .._core import Zeroconf | |
@@ -102,41 +108,6 @@ def instance_name_from_service_info(info: "ServiceInfo", strict: bool = True) -> | |||
| 102 | 108 | return info.name[: -len(service_name) - 1] | |
| 103 | 109 | ||
| 104 | 110 | ||
| 105 | - @lru_cache(maxsize=512) | ||
| 106 | - def _cached_ip_addresses(address: Union[str, bytes, int]) -> Optional[Union[IPv4Address, IPv6Address]]: | ||
| 107 | - """Cache IP addresses.""" | ||
| 108 | - try: | ||
| 109 | - return ip_address(address) | ||
| 110 | - except ValueError: | ||
| 111 | - return None | ||
| 112 | - | ||
| 113 | - | ||
| 114 | - _cached_ip_addresses_wrapper = _cached_ip_addresses | ||
| 115 | - | ||
| 116 | - | ||
| 117 | - def _get_ip_address_object_from_record(record: DNSAddress) -> Optional[Union[IPv4Address, IPv6Address]]: | ||
| 118 | - """Get the IP address object from the record.""" | ||
| 119 | - if IPADDRESS_SUPPORTS_SCOPE_ID and record.type == _TYPE_AAAA and record.scope_id is not None: | ||
| 120 | - return _ip_bytes_and_scope_to_address(record.address, record.scope_id) | ||
| 121 | - return _cached_ip_addresses_wrapper(record.address) | ||
| 122 | - | ||
| 123 | - | ||
| 124 | - def _ip_bytes_and_scope_to_address(address: bytes_, scope: int_) -> Optional[Union[IPv4Address, IPv6Address]]: | ||
| 125 | - """Convert the bytes and scope to an IP address object.""" | ||
| 126 | - base_address = _cached_ip_addresses_wrapper(address) | ||
| 127 | - if base_address is not None and base_address.is_link_local: | ||
| 128 | - return _cached_ip_addresses_wrapper(f"{base_address}%{scope}") | ||
| 129 | - return base_address | ||
| 130 | - | ||
| 131 | - | ||
| 132 | - def _str_without_scope_id(addr: Union[IPv4Address, IPv6Address]) -> str: | ||
| 133 | - """Return the string representation of the address without the scope id.""" | ||
| 134 | - if IPADDRESS_SUPPORTS_SCOPE_ID and addr.version == 6: | ||
| 135 | - address_str = str(addr) | ||
| 136 | - return address_str.partition('%')[0] | ||
| 137 | - return str(addr) | ||
| 138 | - | ||
| 139 | - | ||
| 140 | 111 | class ServiceInfo(RecordUpdateListener): | |
| 141 | 112 | """Service information. | |
| 142 | 113 | ||
@@ -271,9 +242,9 @@ def addresses(self, value: List[bytes]) -> None: | |||
| 271 | 242 | ||
| 272 | 243 | for address in value: | |
| 273 | 244 | if IPADDRESS_SUPPORTS_SCOPE_ID and len(address) == 16 and self.interface_index is not None: | |
| 274 | - addr = _ip_bytes_and_scope_to_address(address, self.interface_index) | ||
| 245 | + addr = ip_bytes_and_scope_to_address(address, self.interface_index) | ||
| 275 | 246 | else: | |
| 276 | - addr = _cached_ip_addresses_wrapper(address) | ||
| 247 | + addr = cached_ip_addresses(address) | ||
| 277 | 248 | if addr is None: | |
| 278 | 249 | raise TypeError( | |
| 279 | 250 | "Addresses must either be IPv4 or IPv6 strings, bytes, or integers;" | |
@@ -369,7 +340,7 @@ def parsed_addresses(self, version: IPVersion = IPVersion.All) -> List[str]: | |||
| 369 | 340 | This means the first address will always be the most recently added | |
| 370 | 341 | address of the given IP version. | |
| 371 | 342 | """ | |
| 372 | - return [_str_without_scope_id(addr) for addr in self._ip_addresses_by_version_value(version.value)] | ||
| 343 | + return [str_without_scope_id(addr) for addr in self._ip_addresses_by_version_value(version.value)] | ||
| 373 | 344 | ||
| 374 | 345 | def parsed_scoped_addresses(self, version: IPVersion = IPVersion.All) -> List[str]: | |
| 375 | 346 | """Equivalent to parsed_addresses, with the exception that IPv6 Link-Local | |
@@ -446,7 +417,7 @@ def _get_ip_addresses_from_cache_lifo( | |||
| 446 | 417 | for record in self._get_address_records_from_cache_by_type(zc, type): | |
| 447 | 418 | if record.is_expired(now): | |
| 448 | 419 | continue | |
| 449 | - ip_addr = _get_ip_address_object_from_record(record) | ||
| 420 | + ip_addr = get_ip_address_object_from_record(record) | ||
| 450 | 421 | if ip_addr is not None and ip_addr not in address_list: | |
| 451 | 422 | address_list.append(ip_addr) | |
| 452 | 423 | address_list.reverse() # Reverse to get LIFO order | |
@@ -496,7 +467,7 @@ def _process_record_threadsafe(self, zc: 'Zeroconf', record: DNSRecord, now: flo | |||
| 496 | 467 | dns_address_record = record | |
| 497 | 468 | if TYPE_CHECKING: | |
| 498 | 469 | assert isinstance(dns_address_record, DNSAddress) | |
| 499 | - ip_addr = _get_ip_address_object_from_record(dns_address_record) | ||
| 470 | + ip_addr = get_ip_address_object_from_record(dns_address_record) | ||
| 500 | 471 | if ip_addr is None: | |
| 501 | 472 | log.warning( | |
| 502 | 473 | "Encountered invalid address while processing %s: %s", | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,14 @@ | |||
| 1 | + cdef bint TYPE_CHECKING | ||
| 2 | + cdef bint IPADDRESS_SUPPORTS_SCOPE_ID | ||
| 3 | + | ||
| 4 | + from .._dns cimport DNSAddress | ||
| 5 | + | ||
| 6 | + | ||
| 7 | + cpdef get_ip_address_object_from_record(DNSAddress record) | ||
| 8 | + | ||
| 9 | + @cython.locals(address_str=str) | ||
| 10 | + cpdef str_without_scope_id(object addr) | ||
| 11 | + | ||
| 12 | + cpdef ip_bytes_and_scope_to_address(object addr, object scope_id) | ||
| 13 | + | ||
| 14 | + cdef object cached_ip_addresses_wrapper | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,121 @@ | |||
| 1 | + """ Multicast DNS Service Discovery for Python, v0.14-wmcbrine | ||
| 2 | + Copyright 2003 Paul Scott-Murphy, 2014 William McBrine | ||
| 3 | + | ||
| 4 | + This module provides a framework for the use of DNS Service Discovery | ||
| 5 | + using IP multicast. | ||
| 6 | + | ||
| 7 | + This library is free software; you can redistribute it and/or | ||
| 8 | + modify it under the terms of the GNU Lesser General Public | ||
| 9 | + License as published by the Free Software Foundation; either | ||
| 10 | + version 2.1 of the License, or (at your option) any later version. | ||
| 11 | + | ||
| 12 | + This library is distributed in the hope that it will be useful, | ||
| 13 | + but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| 14 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
| 15 | + Lesser General Public License for more details. | ||
| 16 | + | ||
| 17 | + You should have received a copy of the GNU Lesser General Public | ||
| 18 | + License along with this library; if not, write to the Free Software | ||
| 19 | + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 | ||
| 20 | + USA | ||
| 21 | + """ | ||
| 22 | + import sys | ||
| 23 | + from functools import lru_cache | ||
| 24 | + from ipaddress import AddressValueError, IPv4Address, IPv6Address, NetmaskValueError | ||
| 25 | + from typing import Any, Optional, Union | ||
| 26 | + | ||
| 27 | + from .._dns import DNSAddress | ||
| 28 | + from ..const import _TYPE_AAAA | ||
| 29 | + | ||
| 30 | + bytes_ = bytes | ||
| 31 | + int_ = int | ||
| 32 | + IPADDRESS_SUPPORTS_SCOPE_ID = sys.version_info >= (3, 9, 0) | ||
| 33 | + | ||
| 34 | + | ||
| 35 | + class ZeroconfIPv4Address(IPv4Address): | ||
| 36 | + | ||
| 37 | + __slots__ = ("_str", "_is_link_local") | ||
| 38 | + | ||
| 39 | + def __init__(self, *args: Any, **kwargs: Any) -> None: | ||
| 40 | + """Initialize a new IPv4 address.""" | ||
| 41 | + super().__init__(*args, **kwargs) | ||
| 42 | + self._str = super().__str__() | ||
| 43 | + self._is_link_local = super().is_link_local | ||
| 44 | + | ||
| 45 | + def __str__(self) -> str: | ||
| 46 | + """Return the string representation of the IPv4 address.""" | ||
| 47 | + return self._str | ||
| 48 | + | ||
| 49 | + @property | ||
| 50 | + def is_link_local(self) -> bool: | ||
| 51 | + """Return True if this is a link-local address.""" | ||
| 52 | + return self._is_link_local | ||
| 53 | + | ||
| 54 | + | ||
| 55 | + class ZeroconfIPv6Address(IPv6Address): | ||
| 56 | + | ||
| 57 | + __slots__ = ("_str", "_is_link_local") | ||
| 58 | + | ||
| 59 | + def __init__(self, *args: Any, **kwargs: Any) -> None: | ||
| 60 | + """Initialize a new IPv6 address.""" | ||
| 61 | + super().__init__(*args, **kwargs) | ||
| 62 | + self._str = super().__str__() | ||
| 63 | + self._is_link_local = super().is_link_local | ||
| 64 | + | ||
| 65 | + def __str__(self) -> str: | ||
| 66 | + """Return the string representation of the IPv6 address.""" | ||
| 67 | + return self._str | ||
| 68 | + | ||
| 69 | + @property | ||
| 70 | + def is_link_local(self) -> bool: | ||
| 71 | + """Return True if this is a link-local address.""" | ||
| 72 | + return self._is_link_local | ||
| 73 | + | ||
| 74 | + | ||
| 75 | + @lru_cache(maxsize=512) | ||
| 76 | + def _cached_ip_addresses(address: Union[str, bytes, int]) -> Optional[Union[IPv4Address, IPv6Address]]: | ||
| 77 | + """Cache IP addresses.""" | ||
| 78 | + try: | ||
| 79 | + return ZeroconfIPv4Address(address) | ||
| 80 | + except (AddressValueError, NetmaskValueError): | ||
| 81 | + pass | ||
| 82 | + | ||
| 83 | + try: | ||
| 84 | + return ZeroconfIPv6Address(address) | ||
| 85 | + except (AddressValueError, NetmaskValueError): | ||
| 86 | + return None | ||
| 87 | + | ||
| 88 | + | ||
| 89 | + cached_ip_addresses_wrapper = _cached_ip_addresses | ||
| 90 | + cached_ip_addresses = cached_ip_addresses_wrapper | ||
| 91 | + | ||
| 92 | + | ||
| 93 | + def get_ip_address_object_from_record(record: DNSAddress) -> Optional[Union[IPv4Address, IPv6Address]]: | ||
| 94 | + """Get the IP address object from the record.""" | ||
| 95 | + if IPADDRESS_SUPPORTS_SCOPE_ID and record.type == _TYPE_AAAA and record.scope_id is not None: | ||
| 96 | + return ip_bytes_and_scope_to_address(record.address, record.scope_id) | ||
| 97 | + return cached_ip_addresses_wrapper(record.address) | ||
| 98 | + | ||
| 99 | + | ||
| 100 | + def ip_bytes_and_scope_to_address(address: bytes_, scope: int_) -> Optional[Union[IPv4Address, IPv6Address]]: | ||
| 101 | + """Convert the bytes and scope to an IP address object.""" | ||
| 102 | + base_address = cached_ip_addresses_wrapper(address) | ||
| 103 | + if base_address is not None and base_address.is_link_local: | ||
| 104 | + return cached_ip_addresses_wrapper(f"{base_address}%{scope}") | ||
| 105 | + return base_address | ||
| 106 | + | ||
| 107 | + | ||
| 108 | + def str_without_scope_id(addr: Union[IPv4Address, IPv6Address]) -> str: | ||
| 109 | + """Return the string representation of the address without the scope id.""" | ||
| 110 | + if IPADDRESS_SUPPORTS_SCOPE_ID and addr.version == 6: | ||
| 111 | + address_str = str(addr) | ||
| 112 | + return address_str.partition('%')[0] | ||
| 113 | + return str(addr) | ||
| 114 | + | ||
| 115 | + | ||
| 116 | + __all__ = ( | ||
| 117 | + "cached_ip_addresses", | ||
| 118 | + "get_ip_address_object_from_record", | ||
| 119 | + "ip_bytes_and_scope_to_address", | ||
| 120 | + "str_without_scope_id", | ||
| 121 | + ) | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,24 @@ | |||
| 1 | + #!/usr/bin/env python | ||
| 2 | + | ||
| 3 | + """Unit tests for zeroconf._utils.ipaddress.""" | ||
| 4 | + | ||
| 5 | + from zeroconf._utils import ipaddress | ||
| 6 | + | ||
| 7 | + | ||
| 8 | + def test_cached_ip_addresses_wrapper(): | ||
| 9 | + """Test the cached_ip_addresses_wrapper.""" | ||
| 10 | + assert ipaddress.cached_ip_addresses('') is None | ||
| 11 | + assert ipaddress.cached_ip_addresses('foo') is None | ||
| 12 | + assert ( | ||
| 13 | + str(ipaddress.cached_ip_addresses(b'&\x06(\x00\x02 \x00\x01\x02H\x18\x93%\xc8\x19F')) | ||
| 14 | + == '2606:2800:220:1:248:1893:25c8:1946' | ||
| 15 | + ) | ||
| 16 | + assert ipaddress.cached_ip_addresses('::1') == ipaddress.IPv6Address('::1') | ||
| 17 | + | ||
| 18 | + ipv4 = ipaddress.cached_ip_addresses('169.254.0.0') | ||
| 19 | + assert ipv4 is not None | ||
| 20 | + assert ipv4.is_link_local is True | ||
| 21 | + | ||
| 22 | + ipv6 = ipaddress.cached_ip_addresses('fe80::1') | ||
| 23 | + assert ipv6 is not None | ||
| 24 | + assert ipv6.is_link_local is True | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments