| #if HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC |
| |
| #include "preferredaddress.h" |
| #include <env-inl.h> |
| #include <ngtcp2/ngtcp2.h> |
| #include <node_errors.h> |
| #include <node_sockaddr-inl.h> |
| #include <util-inl.h> |
| #include <uv.h> |
| #include <v8.h> |
| |
| namespace node { |
| |
| using v8::Just; |
| using v8::Local; |
| using v8::Maybe; |
| using v8::Nothing; |
| using v8::Uint32; |
| using v8::Value; |
| |
| namespace quic { |
| |
| namespace { |
| template <int FAMILY> |
| std::optional<const PreferredAddress::AddressInfo> get_address_info( |
| const ngtcp2_preferred_addr& paddr) { |
| if constexpr (FAMILY == AF_INET) { |
| if (!paddr.ipv4_present) return std::nullopt; |
| PreferredAddress::AddressInfo address; |
| address.family = FAMILY; |
| address.port = paddr.ipv4.sin_port; |
| if (uv_inet_ntop( |
| FAMILY, &paddr.ipv4.sin_addr, address.host, sizeof(address.host)) == |
| 0) { |
| address.address = address.host; |
| } |
| return address; |
| } else { |
| if (!paddr.ipv6_present) return std::nullopt; |
| PreferredAddress::AddressInfo address; |
| address.family = FAMILY; |
| address.port = paddr.ipv6.sin6_port; |
| if (uv_inet_ntop(FAMILY, |
| &paddr.ipv6.sin6_addr, |
| address.host, |
| sizeof(address.host)) == 0) { |
| address.address = address.host; |
| } |
| return address; |
| } |
| } |
| |
| template <int FAMILY> |
| void copy_to_transport_params(ngtcp2_transport_params* params, |
| const sockaddr* addr) { |
| params->preferred_addr_present = true; |
| if constexpr (FAMILY == AF_INET) { |
| const sockaddr_in* src = reinterpret_cast<const sockaddr_in*>(addr); |
| params->preferred_addr.ipv4.sin_port = SocketAddress::GetPort(addr); |
| memcpy(¶ms->preferred_addr.ipv4.sin_addr, |
| &src->sin_addr, |
| sizeof(params->preferred_addr.ipv4.sin_addr)); |
| } else { |
| DCHECK_EQ(FAMILY, AF_INET6); |
| const sockaddr_in6* src = reinterpret_cast<const sockaddr_in6*>(addr); |
| params->preferred_addr.ipv6.sin6_port = SocketAddress::GetPort(addr); |
| memcpy(¶ms->preferred_addr.ipv6.sin6_addr, |
| &src->sin6_addr, |
| sizeof(params->preferred_addr.ipv4.sin_addr)); |
| } |
| UNREACHABLE(); |
| } |
| |
| bool resolve(const PreferredAddress::AddressInfo& address, |
| uv_getaddrinfo_t* req) { |
| addrinfo hints{}; |
| hints.ai_flags = AI_NUMERICHOST | AI_NUMERICSERV; |
| hints.ai_family = address.family; |
| hints.ai_socktype = SOCK_DGRAM; |
| |
| // ngtcp2 requires the selection of the preferred address |
| // to be synchronous, which means we have to do a sync resolve |
| // using uv_getaddrinfo here. |
| return uv_getaddrinfo(nullptr, |
| req, |
| nullptr, |
| address.host, |
| // TODO(@jasnell): The to_string here is not really |
| // the most performant way of converting the uint16_t |
| // port into a string. Depending on execution count, |
| // the potential cost here could be mitigated with a |
| // more efficient conversion. For now, however, this |
| // works. |
| std::to_string(address.port).c_str(), |
| &hints) == 0 && |
| req->addrinfo != nullptr; |
| } |
| } // namespace |
| |
| Maybe<PreferredAddress::Policy> PreferredAddress::GetPolicy( |
| Environment* env, Local<Value> value) { |
| CHECK(value->IsUint32()); |
| uint32_t val = 0; |
| if (value->Uint32Value(env->context()).To(&val)) { |
| switch (val) { |
| case QUIC_PREFERRED_ADDRESS_USE: |
| return Just(Policy::USE_PREFERRED_ADDRESS); |
| case QUIC_PREFERRED_ADDRESS_IGNORE: |
| return Just(Policy::IGNORE_PREFERRED_ADDRESS); |
| } |
| } |
| THROW_ERR_INVALID_ARG_VALUE( |
| env, "%d is not a valid preferred address policy", val); |
| return Nothing<Policy>(); |
| } |
| |
| PreferredAddress::PreferredAddress(ngtcp2_path* dest, |
| const ngtcp2_preferred_addr* paddr) |
| : dest_(dest), paddr_(paddr) { |
| DCHECK_NOT_NULL(paddr); |
| DCHECK_NOT_NULL(dest); |
| } |
| |
| std::optional<const PreferredAddress::AddressInfo> PreferredAddress::ipv4() |
| const { |
| return get_address_info<AF_INET>(*paddr_); |
| } |
| |
| std::optional<const PreferredAddress::AddressInfo> PreferredAddress::ipv6() |
| const { |
| return get_address_info<AF_INET6>(*paddr_); |
| } |
| |
| void PreferredAddress::Use(const AddressInfo& address) { |
| uv_getaddrinfo_t req; |
| auto on_exit = OnScopeLeave([&] { |
| if (req.addrinfo != nullptr) uv_freeaddrinfo(req.addrinfo); |
| }); |
| |
| if (resolve(address, &req)) { |
| DCHECK_NOT_NULL(req.addrinfo); |
| dest_->remote.addrlen = req.addrinfo->ai_addrlen; |
| memcpy(dest_->remote.addr, req.addrinfo->ai_addr, req.addrinfo->ai_addrlen); |
| } |
| } |
| |
| void PreferredAddress::Set(ngtcp2_transport_params* params, |
| const sockaddr* addr) { |
| DCHECK_NOT_NULL(params); |
| DCHECK_NOT_NULL(addr); |
| switch (addr->sa_family) { |
| case AF_INET: |
| return copy_to_transport_params<AF_INET>(params, addr); |
| case AF_INET6: |
| return copy_to_transport_params<AF_INET6>(params, addr); |
| } |
| // Any other value is just ignored. |
| } |
| |
| Maybe<PreferredAddress::Policy> PreferredAddress::tryGetPolicy( |
| Environment* env, Local<Value> value) { |
| if (value->IsUndefined()) { |
| return Just(PreferredAddress::Policy::USE_PREFERRED_ADDRESS); |
| } |
| if (value->IsUint32()) { |
| auto val = value.As<Uint32>()->Value(); |
| if (val == static_cast<uint32_t>(Policy::IGNORE_PREFERRED_ADDRESS)) |
| return Just(Policy::IGNORE_PREFERRED_ADDRESS); |
| if (val == static_cast<uint32_t>(Policy::USE_PREFERRED_ADDRESS)) |
| return Just(Policy::USE_PREFERRED_ADDRESS); |
| } |
| THROW_ERR_INVALID_ARG_VALUE(env, "invalid preferred address policy"); |
| return Nothing<PreferredAddress::Policy>(); |
| } |
| |
| void PreferredAddress::Initialize(Environment* env, |
| v8::Local<v8::Object> target) { |
| NODE_DEFINE_CONSTANT(target, QUIC_PREFERRED_ADDRESS_IGNORE); |
| NODE_DEFINE_CONSTANT(target, QUIC_PREFERRED_ADDRESS_USE); |
| NODE_DEFINE_CONSTANT(target, DEFAULT_PREFERRED_ADDRESS_POLICY); |
| } |
| |
| } // namespace quic |
| } // namespace node |
| |
| #endif // HAVE_OPENSSL && NODE_OPENSSL_HAS_QUIC |