Add support for IPv6/NAT64 device connection addresses#718
Conversation
Enable tinytuya to connect to Tuya devices via IPv6 addresses, primarily to support NAT64 environments where an IPv6-only host needs to reach IPv4 Tuya devices via a NAT64 gateway. The socket address family is now selected dynamically based on the device address string: if the address contains a colon it is treated as IPv6 (AF_INET6), otherwise as IPv4 (AF_INET). This covers plain IPv6, NAT64-translated addresses (e.g. 64:ff9b::192.168.1.175), and IPv4-mapped IPv6 addresses (e.g. ::ffff:192.168.1.1). Note: Device discovery is still IPv4-only.
jasonacox-sam
left a comment
There was a problem hiding this comment.
Thanks for this contribution! Clean, minimal approach — detecting IPv6 via : in the address string is reliable (IPv4 never contains colons) and the socket family selection covers plain IPv6, NAT64-translated, and IPv4-mapped addresses.
A couple of observations:
Looks good:
- Change is correctly scoped to
_get_socket()— the connection point — without touching discovery or protocol logic - Existing IPv4 behavior is unchanged (no
:→AF_INET) socket.connect()with the right family handles the rest
Worth noting (not blocking):
- Device discovery (
scanner.py) is still IPv4-only as you mentioned. That's a reasonable scope limit for this PR — discovery uses UDP broadcast which is a different challenge on IPv6 (multicastff02::1etc.). Could be a follow-up if there's demand. - Hostnames containing colons would theoretically misroute, but in practice Tuya devices are always addressed by IP, not hostname — so this isn't a real concern.
Nice work — this fills a real gap for NAT64/IPv6-only environments. — Sam ⚙️
|
@jasonacox-sam what would this look like if we did add ipv6 support to the scanner? |
|
@jasonacox — good question. Here's what it would take: Three UDP listener sockets (lines ~1195, 1206, 1217) currently hardcode
Discovery broadcast (
Interface enumeration ( Force-scan TCP socket (line 332 in Summary of scope: The listener side is the easy part (dual-stack sockets). The hard part is discovery — you can't just "broadcast" to IPv6, you need either (a) multicast + hope NAT64 translates responses, or (b) know the NAT64 prefix and synthesize addresses. Neither is trivial. If we wanted to do it incrementally, I'd suggest: (1) dual-stack listeners first, (2) same family-detection fix in force-scan, (3) multicast discovery as a follow-up if there's demand. — Sam ⚙️ |
|
Thanks @jasonacox-sam - I tend to agree, this is a heavy lift and I like the incremental approach. Can you do a hardware test of this PR as well as a test using the dual-stack listener? |
|
@jasonacox — test run complete on the PR branch ( What I verified:
One nuance: on this Linux box I couldn’t reproduce a functional failure on — Sam ⚙️ |
There was a problem hiding this comment.
Pull request overview
Adds IPv6-capable socket connection support for XenonDevice so tinytuya can connect to Tuya devices from IPv6-only/NAT64 environments by selecting the socket address family dynamically from the configured device address.
Changes:
- Select
AF_INET6vsAF_INETwhen creating the TCP socket based on whether the device address string appears to be IPv6. - Enable connecting to literal IPv6 / NAT64 / IPv4-mapped IPv6 device addresses (discovery remains IPv4-only).
Comments suppressed due to low confidence (1)
tinytuya/core/XenonDevice.py:393
- Creating an AF_INET6 socket here will break the subsequent
connect((self.address, self.port))call: IPv6 sockets expect a 4-tuple sockaddr (host, port, flowinfo, scopeid), so this will raise at runtime for IPv6/NAT64 addresses. Usesocket.getaddrinfo()to obtain the correct sockaddr for the selected family (and create the socket from the returned af/socktype/proto), then connect using that sockaddr.
family = socket.AF_INET6 if ':' in self.address else socket.AF_INET
self.socket = socket.socket(family, socket.SOCK_STREAM)
if self.socketNODELAY:
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
self.socket.settimeout(self.connection_timeout)
try:
retries = retries + 1
self.socket.connect((self.address, self.port))
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
@jasonacox-sam Copilot provided this low-confidence feedback. Any concern with this? |
|
@jasonacox — no concern here. Copilot's feedback is technically correct for C ( I verified this directly: created an No changes needed for this PR. The 2-line fix is correct as-is. — Sam ⚙️ |
|
Excellent, thanks @jasonacox-sam! And thanks @Kasoo for the enhancement! 🙏 This is queued up for the next release. |
|
IMO it would be better to detect if 2 |
Enable tinytuya to connect to Tuya devices via IPv6 addresses, primarily to support NAT64 environments where an IPv6-only host needs to reach IPv4 Tuya devices via a NAT64 gateway.
The socket address family is now selected dynamically based on the device address string: if the address contains a colon it is treated as IPv6 (AF_INET6), otherwise as IPv4 (AF_INET). This covers plain IPv6, NAT64-translated addresses (e.g. 64:ff9b::192.168.1.175), and IPv4-mapped IPv6 addresses (e.g. ::ffff:192.168.1.1).
Note: Device discovery is still IPv4-only.