@@ -458,15 +458,34 @@ def create_sockets( |
458 | 458 | if multicast_addresses and unicast: |
459 | 459 | raise ValueError("multicast_addresses is incompatible with unicast=True") |
460 | 460 |
|
| 461 | + # Reject IP-version-incompatible entries up front so callers get a clear |
| 462 | + # error instead of a confusing adapter-lookup or socket-syscall failure. |
| 463 | + if multicast_addresses: |
| 464 | + if ip_version == IPVersion.V4Only: |
| 465 | + for entry in multicast_addresses: |
| 466 | + if isinstance(entry, (int, tuple)) or ( |
| 467 | + isinstance(entry, str) and ipaddress.ip_address(entry).version == 6 |
| 468 | + ): |
| 469 | + raise ValueError("multicast_addresses contains IPv6 entries but ip_version is V4Only") |
| 470 | + elif ip_version == IPVersion.V6Only: |
| 471 | + for entry in multicast_addresses: |
| 472 | + if isinstance(entry, str) and ipaddress.ip_address(entry).version == 4: |
| 473 | + raise ValueError("multicast_addresses contains IPv4 entries but ip_version is V6Only") |
| 474 | + |
461 | 475 | if unicast: |
462 | 476 | listen_socket = None |
463 | 477 | else: |
464 | 478 | listen_socket = new_socket(bind_addr=("",), ip_version=ip_version, apple_p2p=apple_p2p) |
465 | 479 |
|
466 | 480 | normalized_interfaces = normalize_interface_choice(interfaces, ip_version) |
467 | | - extra_multicast_members = ( |
468 | | - normalize_interface_choice(list(multicast_addresses), ip_version) if multicast_addresses else [] |
469 | | - ) |
| 481 | + if multicast_addresses: |
| 482 | + extra_multicast_members = normalize_interface_choice(list(multicast_addresses), ip_version) |
| 483 | + # Strip entries already covered by ``interfaces`` so add_multicast_member |
| 484 | + # is not called twice for the same membership. |
| 485 | + interface_set = set(normalized_interfaces) |
| 486 | + extra_multicast_members = [m for m in extra_multicast_members if m not in interface_set] |
| 487 | + else: |
| 488 | + extra_multicast_members = [] |
470 | 489 |
|
471 | 490 | # If we are using InterfaceChoice.Default with only IPv4 or only IPv6, we can use |
472 | 491 | # a single socket to listen and respond. |
@@ -510,17 +529,29 @@ def can_send_to(ipv6_socket: bool, address: str) -> bool: |
510 | 529 | return ":" in address if ipv6_socket else ":" not in address |
511 | 530 |
|
512 | 531 |
|
513 | | -def autodetect_ip_version(interfaces: InterfacesType) -> IPVersion: |
| 532 | +def autodetect_ip_version( |
| 533 | + interfaces: InterfacesType, |
| 534 | + multicast_addresses: Sequence[str | int | tuple[tuple[str, int, int], int]] | None = None, |
| 535 | +) -> IPVersion: |
514 | 536 | """Auto detect the IP version when it is not provided.""" |
| 537 | + has_v6 = False |
| 538 | + has_v4 = False |
515 | 539 | if isinstance(interfaces, list): |
516 | 540 | has_v6 = any( |
517 | 541 | isinstance(i, int) or (isinstance(i, str) and ipaddress.ip_address(i).version == 6) |
518 | 542 | for i in interfaces |
519 | 543 | ) |
520 | 544 | has_v4 = any(isinstance(i, str) and ipaddress.ip_address(i).version == 4 for i in interfaces) |
521 | | - if has_v4 and has_v6: |
522 | | - return IPVersion.All |
523 | | - if has_v6: |
524 | | - return IPVersion.V6Only |
525 | | - |
| 545 | + if multicast_addresses: |
| 546 | + has_v6 = has_v6 or any( |
| 547 | + isinstance(i, (int, tuple)) or (isinstance(i, str) and ipaddress.ip_address(i).version == 6) |
| 548 | + for i in multicast_addresses |
| 549 | + ) |
| 550 | + has_v4 = has_v4 or any( |
| 551 | + isinstance(i, str) and ipaddress.ip_address(i).version == 4 for i in multicast_addresses |
| 552 | + ) |
| 553 | + if has_v4 and has_v6: |
| 554 | + return IPVersion.All |
| 555 | + if has_v6: |
| 556 | + return IPVersion.V6Only |
526 | 557 | return IPVersion.V4Only |