docs: def create_multicast_socket(address, port):
      """
      Creates a multicast socket according to the given address and port.
      Handles both IPv4 and IPv6 addresses.
  
      :param address: Multicast address/group
      :param port: Socket port
      :return: A tuple (socket, listening address)
      :raise ValueError: Invalid address or port
      """
      # Get the information about a datagram (UDP) socket, of any family
      try:
          addrs_info = socket.getaddrinfo(
              address, port, socket.AF_UNSPEC, socket.SOCK_DGRAM
          )
      except socket.gaierror:
          raise ValueError(
              "Error retrieving address informations ({0}, {1})".format(
                  address, port
              )
          )
  
      if len(addrs_info) > 1:
          _logger.debug(
              "More than one address information found. Using the first one."
          )
  
      # Get the first entry : (family, socktype, proto, canonname, sockaddr)
      addr_info = addrs_info[0]
  
      # Only accept IPv4/v6 addresses
      if addr_info[0] not in (socket.AF_INET, socket.AF_INET6):
          # Unhandled address family
          raise ValueError("Unhandled socket family : %d" % (addr_info[0]))
  
      # Prepare the socket
      sock = socket.socket(addr_info[0], socket.SOCK_DGRAM, socket.IPPROTO_UDP)
  
      # Reuse address
      sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      if hasattr(socket, "SO_REUSEPORT"):
          # Special for MacOS
          # pylint: disable=E1101
          sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
  
      # Bind the socket
      if sock.family == socket.AF_INET:
          # IPv4 binding
          sock.bind(("0.0.0.0", port))
  
      else:
          # IPv6 Binding
          sock.bind(("::", port))
  
      # Prepare the mreq structure to join the group
      # addrinfo[4] = (addr,port)
      mreq = make_mreq(sock.family, addr_info[4][0])
  
      # Join the group
      if sock.family == socket.AF_INET:
          # IPv4
          sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
  
          # Allow multicast packets to get back on this host
          sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 1)
  
      elif sock.family == socket.AF_INET6:
          # IPv6
          sock.setsockopt(ipproto_ipv6(), socket.IPV6_JOIN_GROUP, mreq)
  
          # Allow multicast packets to get back on this host
          sock.setsockopt(ipproto_ipv6(), socket.IPV6_MULTICAST_LOOP, 1)
  
      return sock, addr_info[4][0]