Skip to content

Improve ipv6 detection#40

Merged
remcohaszing merged 8 commits into
mainfrom
detect-ipv6
Jun 12, 2026
Merged

Improve ipv6 detection#40
remcohaszing merged 8 commits into
mainfrom
detect-ipv6

Conversation

@remcohaszing

Copy link
Copy Markdown
Owner

Previously, ipv6 detection relied on the IP address. It didn’t work for domain names. The new implementation uses socket.getaddrinfo() to determine the address family.

@FrogTheFrog This is based on your suggestion. Would you like to review these changes?

Notably, I decided not to loop over all address families found. I just picked the first one.

Closes #34

@FrogTheFrog FrogTheFrog left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I agree with your approach to not iterate over all of the addresses.

I still do it in my project, but it has some issues, like some "the interface is unavailable" exceptions on some interfaces and that I have to handle 101 explicitly:

 except OSError as err:
    acceptable_errors = [101]
    if err.errno in acceptable_errors:
        logger.warning(f"WOL failed: {str(err)}")
    else:
        raise

Handling if properly would require some extra functionality or a different approach even. Maybe instead of allowing address_family is None, add a new function to return all addresses that people can iterate over, call send_magic_packet and catch exceptions.

Comment thread wakeonlan/__init__.py Outdated
address_family = family
break
else:
address_family = socket.AF_INET

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What is the benefit of assigning some fallback instead of raising an exception here?

If the address_family cannot be resolved by getaddrinfo, it is very likely that we would also fail down below. At best it would raise something, but it could just silently fail (which is worse IMO). However, if we raise an exception here, then the user could at least somehow handle it.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Honestly, this was just to satisfy the type checker. Maybe raising an error is better indeed.

@FrogTheFrog

Copy link
Copy Markdown

Review done 👍

Previously, ipv6 detection relied on the IP address. It didn’t work for
domain names. The new implementation uses `socket.getaddrinfo()` to
determine the address family.
@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.00%. Comparing base (892f722) to head (b2453c7).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files
@@             Coverage Diff             @@
##             main       #40      +/-   ##
===========================================
+ Coverage   96.15%   100.00%   +3.84%     
===========================================
  Files           1         1              
  Lines          52        46       -6     
  Branches       11         9       -2     
===========================================
- Hits           50        46       -4     
+ Misses          2         0       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@remcohaszing

Copy link
Copy Markdown
Owner Author

I pushed a new approach. This iterates over all the addr infos. If there aren’t any, it raises an exception. If none of the addresses succeeds, it raises the first exception. Is this better?

from https://man7.org/linux/man-pages/man3/getaddrinfo.3.html:

Normally, the application should try using the addresses in the order in which they are returned.

It feels a bit weird to me that getaddrinfo is called without the interface.

@FrogTheFrog FrogTheFrog left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Another round done

Comment thread wakeonlan/__init__.py Outdated

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

No longer used

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Good catch. I considered leaving it out, but I decided to re-add support for specifying the address family. It’s not needed for typical use cases anymore though.

Comment thread wakeonlan/__init__.py Outdated
except ValueError:
return False
address_infos = socket.getaddrinfo(
ip_address, port, type=socket.SOCK_DGRAM, proto=socket.IPPROTO_UDP

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

What does proto=socket.IPPROTO_UDP do in this use-case?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

It’s pointless. Nice catch!

Comment thread wakeonlan/__init__.py Outdated
raise Exception(f'Could not resolve {ip_address}')

error: typing.Optional[socket.gaierror] = None
for family, type, proto, canonname, addr in address_infos:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since you're iterating over these already I would recommend to introduce a "error_callback = None" parameter for this function and just try to create a socket for each address, because:

  1. What if the last address is the one that will actually do WOL? We would be breaking on first.
  2. What if first raises an exception, but the 2nd succeeds for real - wakes a PC. You would be raising an exception, but the function has worked as expected.

Maybe something like this? No ambiguity for error handling - if the user is unhappy they can use callback.

    for family, type, proto, canonname, addr in address_infos:
        try:
            with socket.socket(family, type, proto) as sock:
                if interface is not None:
                    sock.bind((interface, 0))
                sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
                sock.connect(addr)
                for packet in packets:
                    sock.send(packet)
        except Exception as e:
            if error_callback:
                if error_callback(e):
                    break
           else:
                raise error

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

I don’t really want to bother the user with exception handling for this. Normally the library handles this. For example, have you ever dealt with resolving a hostname to an IP address when making an HTTP request? I don’t think I ever have.

Socket creation happens in a new public function named `create_socket`.
The preferred address family can be specified again. The `proto`
argument for `getaddrinfo()` is redundant and has been removed.

Also, if the connection now fails for some reason, the last error is
raised instead of the first.
@remcohaszing remcohaszing merged commit 736ddcb into main Jun 12, 2026
12 checks passed
@remcohaszing remcohaszing deleted the detect-ipv6 branch June 12, 2026 12:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

IPv6 usage is confusing

2 participants