text: // Copyright 2017 The Fuchsia Authors. All rights reserved.
  // Use of this source code is governed by a BSD-style license that can be
  // found in the LICENSE file.
  
  #include "src/connectivity/network/mdns/service/mdns_interface_transceiver_v6.h"
  
  #include <arpa/inet.h>
  #include <errno.h>
  #include <lib/syslog/cpp/macros.h>
  #include <sys/socket.h>
  
  #include "src/connectivity/network/mdns/service/mdns_addresses.h"
  
  namespace mdns {
  
  MdnsInterfaceTransceiverV6::MdnsInterfaceTransceiverV6(inet::IpAddress address,
                                                         const std::string& name, uint32_t index,
                                                         Media media)
      : MdnsInterfaceTransceiver(address, name, index, media) {}
  
  MdnsInterfaceTransceiverV6::~MdnsInterfaceTransceiverV6() {}
  
  int MdnsInterfaceTransceiverV6::SetOptionDisableMulticastLoop() {
    int param = 0;
    int result =
        setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &param, sizeof(param));
    if (result < 0) {
      if (errno == ENOPROTOOPT) {
        FX_LOGS(WARNING) << "fxbug.dev/20688 IPV6_MULTICAST_LOOP not supported "
                            "(ENOPROTOOPT), continuing anyway";
        result = 0;
      } else {
        FX_LOGS(ERROR) << "Failed to set socket option IPV6_MULTICAST_LOOP, " << strerror(errno);
      }
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::SetOptionJoinMulticastGroup() {
    ipv6_mreq param;
    param.ipv6mr_multiaddr = addresses().v6_multicast().as_sockaddr_in6().sin6_addr;
    param.ipv6mr_interface = index();
    int result = setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_JOIN_GROUP, &param, sizeof(param));
    if (result < 0) {
      if (errno == ENODEV) {
        FX_LOGS(WARNING) << "fxbug.dev/21173 IPV6_JOIN_GROUP returned ENODEV, mDNS will "
                            "not communicate via IPV6";
      } else {
        FX_LOGS(ERROR) << "Failed to set socket option IPV6_JOIN_GROUP, " << strerror(errno);
      }
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::SetOptionOutboundInterface() {
    uint32_t index = this->index();
    int result =
        setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_MULTICAST_IF, &index, sizeof(index));
    if (result < 0) {
      if (errno == EADDRNOTAVAIL) {
        // This is expected when the interface is removed as we try to use it. We still return
        // result < 0, because we don't want to use this interface.
        FX_LOGS(WARNING) << "Failed to set socket option IPV6_MULTICAST_IF, " << strerror(errno);
      } else {
        FX_LOGS(ERROR) << "Failed to set socket option IPV6_MULTICAST_IF, " << strerror(errno);
      }
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::SetOptionUnicastTtl() {
    int param = kTimeToLive_;
    int result =
        setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_UNICAST_HOPS, &param, sizeof(param));
    if (result < 0) {
      if (errno == ENOPROTOOPT) {
        // TODO(fxbug.dev/41357): remove the bug reference when the bug is fixed.
        FX_LOGS(WARNING)
            << "fxbug.dev/41357: IPV6_UNICAST_HOPS not supported (ENOPROTOOPT), continuing anyway";
        result = 0;
      } else {
        FX_LOGS(ERROR) << "Failed to set socket option IPV6_UNICAST_HOPS, " << strerror(errno);
      }
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::SetOptionMulticastTtl() {
    int param = kTimeToLive_;
    int result =
        setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_MULTICAST_HOPS, &param, sizeof(param));
    if (result < 0) {
      if (errno == ENOPROTOOPT) {
        FX_LOGS(WARNING) << "IPV6_MULTICAST_HOPS not supported (ENOPROTOOPT), continuing anyway";
        result = 0;
      } else {
        FX_LOGS(ERROR) << "Failed to set socket option IPV6_MULTICAST_HOPS, " << strerror(errno);
      }
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::SetOptionFamilySpecific() {
    // Set hop limit.
    int param = 1;
    int result = setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_HOPLIMIT, &param, sizeof(param));
    if (result < 0) {
      if (errno == ENOPROTOOPT) {
        // TODO(fxbug.dev/41358): remove the bug reference when the bug is fixed.
        FX_LOGS(WARNING)
            << "fxbug.dev/41358: IPV6_HOPLIMIT not supported (ENOPROTOOPT), continuing anyway";
        result = 0;
      } else {
        FX_LOGS(ERROR) << "Failed to set socket option IPV6_HOPLIMIT, " << strerror(errno);
      }
      return result;
    }
  
    // Receive V6 packets only.
    param = 1;
    result = setsockopt(socket_fd().get(), IPPROTO_IPV6, IPV6_V6ONLY, &param, sizeof(param));
    if (result < 0) {
      FX_LOGS(ERROR) << "Failed to set socket option IPV6_V6ONLY, " << strerror(errno);
      return false;
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::Bind() {
    int result =
        bind(socket_fd().get(), addresses().v6_bind().as_sockaddr(), addresses().v6_bind().socklen());
    if (result < 0) {
      FX_LOGS(ERROR) << "Failed to bind socket to V6 address, " << strerror(errno);
    }
  
    return result;
  }
  
  int MdnsInterfaceTransceiverV6::SendTo(const void* buffer, size_t size,
                                         const inet::SocketAddress& address) {
    if (address == addresses().v4_multicast()) {
      // |v4_multicast| indicates multicast, meaning V6 multicast in this case.
      return sendto(socket_fd().get(), buffer, size, 0, addresses().v6_multicast().as_sockaddr(),
                    addresses().v6_multicast().socklen());
    }
  
    return sendto(socket_fd().get(), buffer, size, 0, address.as_sockaddr(), address.socklen());
  }
  
  }  // namespace mdns
  