Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions homeassistant/components/dnsip/__init__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,83 @@
"""The DNS IP integration."""

import asyncio
from dataclasses import dataclass

import aiodns
from aiodns.error import DNSError

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PORT
from homeassistant.core import _LOGGER, HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady

from .const import (
CONF_HOSTNAME,
CONF_IPV4,
CONF_IPV6,
CONF_PORT_IPV6,
CONF_RESOLVER,
CONF_RESOLVER_IPV6,
DEFAULT_PORT,
PLATFORMS,
)


from .const import CONF_PORT_IPV6, DEFAULT_PORT, PLATFORMS
@dataclass
class DnsIPRuntimeData:
"""Runtime data for DNS IP integration."""

resolver_ipv4: aiodns.DNSResolver
resolver_ipv6: aiodns.DNSResolver

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:

type DnsIPConfigEntry = ConfigEntry[DnsIPRuntimeData]


async def async_setup_entry(hass: HomeAssistant, entry: DnsIPConfigEntry) -> bool:
"""Set up DNS IP from a config entry."""

nameserver_ipv4 = entry.options[CONF_RESOLVER]
nameserver_ipv6 = entry.options[CONF_RESOLVER_IPV6]
port_ipv4 = entry.options[CONF_PORT]
port_ipv6 = entry.options[CONF_PORT_IPV6]

resolver_ipv4 = aiodns.DNSResolver(
nameservers=[nameserver_ipv4], tcp_port=port_ipv4, udp_port=port_ipv4
)
resolver_ipv6 = aiodns.DNSResolver(
nameservers=[nameserver_ipv6], tcp_port=port_ipv6, udp_port=port_ipv6
)

hostname = entry.data[CONF_HOSTNAME]
try:
async with asyncio.timeout(10):
if entry.data[CONF_IPV4]:
await resolver_ipv4.query(hostname, "A")
elif entry.data[CONF_IPV6]:
await resolver_ipv6.query(hostname, "AAAA")
except (TimeoutError, DNSError) as err:
await resolver_ipv4.close()
await resolver_ipv6.close()
raise ConfigEntryNotReady(f"DNS lookup failed for {hostname}: {err}") from err
Comment on lines +45 to +62
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in next push


Comment on lines +53 to +63
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unsure if this is required. Can add this change if this is needed

Comment on lines +53 to +63
entry.runtime_data = DnsIPRuntimeData(
resolver_ipv4=resolver_ipv4,
resolver_ipv6=resolver_ipv6,
)

await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True


async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: DnsIPConfigEntry) -> bool:
"""Unload DNS IP config entry."""

return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
await entry.runtime_data.resolver_ipv4.close()
await entry.runtime_data.resolver_ipv6.close()
return unload_ok


async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/dnsip/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/dnsip",
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "bronze",
"requirements": ["aiodns==4.0.0"]
}
75 changes: 75 additions & 0 deletions homeassistant/components/dnsip/quality_scale.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
rules:
action-setup:
status: exempt
comment: Integration does not register custom actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: Integration does not register custom actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: Entities do not subscribe to events; the sensor polls via async_update.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
Comment on lines +22 to +25
action-exceptions:
status: exempt
comment: Integration does not register custom actions.
config-entry-unloading: done
docs-configuration-parameters: todo
docs-installation-parameters: todo
entity-unavailable: todo
integration-owner: done
log-when-unavailable: todo
parallel-updates: todo
reauthentication-flow:
status: exempt
comment: DNS resolution does not require authentication.
test-coverage: todo
devices: todo
diagnostics: todo
discovery-update-info:
status: exempt
comment: DNS resolvers do not support discovery.
discovery:
status: exempt
comment: DNS resolvers do not support discovery.
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices:
status: exempt
comment: Integration does not represent physical devices.
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices:
status: exempt
comment: Integration does not represent physical devices.
entity-category: todo
entity-device-class: todo
entity-disabled-by-default: todo
entity-translations: todo
exception-translations: todo
icon-translations: todo
reconfiguration-flow: todo
repair-issues: todo
stale-devices:
status: exempt
comment: Integration does not represent physical devices.
async-dependency: done
inject-websession:
status: exempt
comment: Integration uses aiodns directly; no shared HTTP websession applies.
strict-typing: todo
50 changes: 23 additions & 27 deletions homeassistant/components/dnsip/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,16 @@
from aiodns.error import DNSError

from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_NAME, CONF_PORT
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback

from . import DnsIPConfigEntry
from .const import (
CONF_HOSTNAME,
CONF_IPV4,
CONF_IPV6,
CONF_PORT_IPV6,
CONF_RESOLVER,
CONF_RESOLVER_IPV6,
DOMAIN,
Expand All @@ -46,24 +45,35 @@ def sort_ips(ips: list, querytype: Literal["A", "AAAA"]) -> list:

async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
entry: DnsIPConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the dnsip sensor entry."""

hostname = entry.data[CONF_HOSTNAME]
name = entry.data[CONF_NAME]

nameserver_ipv4 = entry.options[CONF_RESOLVER]
nameserver_ipv6 = entry.options[CONF_RESOLVER_IPV6]
port_ipv4 = entry.options[CONF_PORT]
port_ipv6 = entry.options[CONF_PORT_IPV6]

entities = []
if entry.data[CONF_IPV4]:
entities.append(WanIpSensor(name, hostname, nameserver_ipv4, False, port_ipv4))
entities.append(
WanIpSensor(
name,
hostname,
entry.options[CONF_RESOLVER],
False,
entry.runtime_data.resolver_ipv4,
)
)
if entry.data[CONF_IPV6]:
entities.append(WanIpSensor(name, hostname, nameserver_ipv6, True, port_ipv6))
entities.append(
WanIpSensor(
name,
hostname,
entry.options[CONF_RESOLVER_IPV6],
True,
entry.runtime_data.resolver_ipv6,
)
)

async_add_entities(entities, update_before_add=True)

Expand All @@ -75,22 +85,19 @@ class WanIpSensor(SensorEntity):
_attr_translation_key = "dnsip"
_unrecorded_attributes = frozenset({"resolver", "querytype", "ip_addresses"})

resolver: aiodns.DNSResolver

def __init__(
self,
name: str,
hostname: str,
nameserver: str,
ipv6: bool,
port: int,
resolver: aiodns.DNSResolver,
) -> None:
"""Initialize the DNS IP sensor."""
self._attr_name = "IPv6" if ipv6 else None
self._attr_unique_id = f"{hostname}_{ipv6}"
self.hostname = hostname
self.port = port
self.nameserver = nameserver
self.resolver = resolver
self.querytype: Literal["A", "AAAA"] = "AAAA" if ipv6 else "A"
self._retries = DEFAULT_RETRIES
self._attr_extra_state_attributes = {
Expand All @@ -104,28 +111,17 @@ def __init__(
model=aiodns.__version__,
name=name,
)
self.create_dns_resolver()

def create_dns_resolver(self) -> None:
"""Create the DNS resolver."""
self.resolver = aiodns.DNSResolver(
nameservers=[self.nameserver], tcp_port=self.port, udp_port=self.port
)

async def async_update(self) -> None:
"""Get the current DNS IP address for hostname."""
if self.resolver._closed: # noqa: SLF001
self.create_dns_resolver()
response = None
try:
async with asyncio.timeout(10):
response = await self.resolver.query(self.hostname, self.querytype)
except TimeoutError as err:
_LOGGER.debug("Timeout while resolving host: %s", err)
await self.resolver.close()
except DNSError as err:
_LOGGER.warning("Exception while resolving host: %s", err)
await self.resolver.close()

if response:
sorted_ips = sort_ips(
Expand Down
23 changes: 18 additions & 5 deletions homeassistant/components/dnsip/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,18 @@
"step": {
"user": {
"data": {
"hostname": "The hostname for which to perform the DNS query",
"port": "Port for IPV4 lookup",
"port_ipv6": "Port for IPV6 lookup",
"resolver": "Resolver for IPV4 lookup",
"resolver_ipv6": "Resolver for IPV6 lookup"
"hostname": "Hostname",
"port": "IPv4 port",
"port_ipv6": "IPv6 port",
"resolver": "IPv4 resolver",
"resolver_ipv6": "IPv6 resolver"
},
"data_description": {
"hostname": "The hostname for which to perform the DNS query.",
"port": "Port used for the IPv4 lookup.",
"port_ipv6": "Port used for the IPv6 lookup.",
"resolver": "Resolver used for the IPv4 lookup.",
"resolver_ipv6": "Resolver used for the IPv6 lookup."
}
}
}
Expand Down Expand Up @@ -50,6 +57,12 @@
"port_ipv6": "[%key:component::dnsip::config::step::user::data::port_ipv6%]",
"resolver": "[%key:component::dnsip::config::step::user::data::resolver%]",
"resolver_ipv6": "[%key:component::dnsip::config::step::user::data::resolver_ipv6%]"
},
"data_description": {
"port": "[%key:component::dnsip::config::step::user::data_description::port%]",
"port_ipv6": "[%key:component::dnsip::config::step::user::data_description::port_ipv6%]",
"resolver": "[%key:component::dnsip::config::step::user::data_description::resolver%]",
"resolver_ipv6": "[%key:component::dnsip::config::step::user::data_description::resolver_ipv6%]"
}
}
}
Expand Down
2 changes: 0 additions & 2 deletions script/hassfest/quality_scale.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,6 @@ class Rule:
"dlink",
"dlna_dmr",
"dlna_dms",
"dnsip",
"dominos",
"doods",
"doorbird",
Expand Down Expand Up @@ -1253,7 +1252,6 @@ class Rule:
"dlink",
"dlna_dmr",
"dlna_dms",
"dnsip",
"dominos",
"doods",
"doorbird",
Expand Down
Loading
Loading