| /* | |
| Licensed to the Apache Software Foundation (ASF) under one | |
| or more contributor license agreements. See the NOTICE file | |
| distributed with this work for additional information | |
| regarding copyright ownership. The ASF licenses this file | |
| to you under the Apache License, Version 2.0 (the | |
| "License"); you may not use this file except in compliance | |
| with the License. You may obtain a copy of the License at | |
| http://www.apache.org/licenses/LICENSE-2.0 | |
| Unless required by applicable law or agreed to in writing, software | |
| distributed under the License is distributed on an "AS IS" BASIS, | |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| See the License for the specific language governing permissions and | |
| limitations under the License. | |
| */ | |
| #pragma once | |
| #include <openssl/ssl.h> | |
| #include "ts/apidefs.h" | |
| #include "ts/ts.h" | |
| #include "swoc/IPEndpoint.h" | |
| #include "cripts/Lulu.hpp" | |
| #include "cripts/Matcher.hpp" | |
| namespace detail | |
| { | |
| class ConnBase; | |
| template <bool IsMutualTLS> class Cert; | |
| } // namespace detail | |
| namespace cripts::Certs | |
| { | |
| using Client = detail::Cert<true>; | |
| using Server = detail::Cert<false>; | |
| } // namespace cripts::Certs | |
| // This is figured out in this way because | |
| // this header has to be available to include | |
| // from cripts scripts that won't have access | |
| // to ink_platform.h. | |
| #if __has_include("linux/tcp.h") | |
| #include "linux/tcp.h" | |
| #define HAS_TCP_INFO 1 | |
| #elif __has_include("netinet/tcp.h") && !defined(__APPLE__) | |
| #include "netinet/tcp.h" | |
| #define HAS_TCP_INFO 1 | |
| #endif | |
| namespace cripts | |
| { | |
| namespace Net | |
| { | |
| extern const Matcher::Range::IP Localhost; | |
| extern const Matcher::Range::IP RFC1918; | |
| } // namespace Net | |
| class IP : public swoc::IPAddr | |
| { | |
| using super_type = swoc::IPAddr; | |
| using self_type = IP; | |
| public: | |
| using super_type::IPAddr; | |
| IP(const self_type &) = delete; | |
| void operator=(const self_type &) = delete; | |
| cripts::string_view GetSV(unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 128); | |
| cripts::string_view | |
| string(unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 128) | |
| { | |
| return GetSV(ipv4_cidr, ipv6_cidr); | |
| } | |
| uint64_t Hasher(unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 128); | |
| bool Sample(double rate, uint32_t seed = 0, unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 128); | |
| // Convert IP to a socket address (sized for both IPv4 and IPv6) | |
| [[nodiscard]] swoc::IPEndpoint Socket() const; | |
| // Geo-IP functionality - can be used with any IP address | |
| [[nodiscard]] cripts::string ASN() const; | |
| [[nodiscard]] cripts::string ASNName() const; | |
| [[nodiscard]] cripts::string Country() const; | |
| [[nodiscard]] cripts::string CountryCode() const; | |
| private: | |
| char _str[INET6_ADDRSTRLEN + 1]; | |
| uint64_t _hash = 0; | |
| uint16_t _sampler = 0; | |
| }; | |
| } // namespace cripts | |
| namespace detail | |
| { | |
| class ConnBase | |
| { | |
| using self_type = ConnBase; | |
| class Dscp | |
| { | |
| using self_type = Dscp; | |
| public: | |
| friend class ConnBase; | |
| Dscp() = default; | |
| void operator=(const self_type &) = delete; | |
| // This is not perfect, but there's currently no ATS Get() mechanism to see a connections | |
| // current DSCP options. | |
| operator integer() const { return _val; } | |
| void | |
| operator=(int val) | |
| { | |
| _ensure_initialized(_owner); | |
| _owner->SetDscp(val); | |
| _val = val; | |
| } | |
| private: | |
| ConnBase *_owner = nullptr; | |
| integer _val = -1; | |
| }; // End class ConnBase::Dscp | |
| class Pacing | |
| { | |
| using self_type = Pacing; | |
| public: | |
| friend class ConnBase; | |
| Pacing() = default; | |
| void operator=(const self_type &) = delete; | |
| // This is not perfect, but there's currently no ATS Get() mechanism to see a connections | |
| // current PACING options. | |
| operator integer() const { return _val; } | |
| void operator=(uint32_t val); | |
| static constexpr uint32_t Off = std::numeric_limits<uint32_t>::max(); | |
| private: | |
| ConnBase *_owner = nullptr; | |
| uint32_t _val = Off; | |
| }; // End class ConnBase::Pacing | |
| class Congestion | |
| { | |
| using self_type = Congestion; | |
| public: | |
| friend class ConnBase; | |
| Congestion() = default; | |
| void operator=(const self_type &) = delete; | |
| self_type & | |
| operator=([[maybe_unused]] cripts::string_view const &str) | |
| { | |
| _ensure_initialized(_owner); | |
| #if defined(TCP_CONGESTION) | |
| int connfd = _owner->FD(); | |
| int res = setsockopt(connfd, IPPROTO_TCP, TCP_CONGESTION, str.data(), str.size()); | |
| // EBADF indicates possible client abort | |
| if ((res < 0) && (errno != EBADF)) { | |
| TSError("[Congestion] Error setting TCP_CONGESTION, errno=%d", errno); | |
| } | |
| #endif | |
| return *this; | |
| } | |
| private: | |
| ConnBase *_owner = nullptr; | |
| }; // End class ConnBase::Congestion | |
| class Mark | |
| { | |
| using self_type = Mark; | |
| public: | |
| friend class ConnBase; | |
| Mark() = default; | |
| void operator=(const self_type &) = delete; | |
| // Same here, no API in ATS to Get() the mark on a VC. | |
| operator integer() const { return _val; } | |
| void | |
| operator=(int val) | |
| { | |
| _ensure_initialized(_owner); | |
| _owner->SetMark(val); | |
| _val = val; | |
| } | |
| private: | |
| ConnBase *_owner = nullptr; | |
| integer _val = -1; | |
| }; // End class ConnBase::Mark | |
| class Geo | |
| { | |
| using self_type = Geo; | |
| public: | |
| friend class ConnBase; | |
| Geo() = default; | |
| void operator=(const self_type &) = delete; | |
| [[nodiscard]] cripts::string ASN() const; | |
| [[nodiscard]] cripts::string ASNName() const; | |
| [[nodiscard]] cripts::string Country() const; | |
| [[nodiscard]] cripts::string CountryCode() const; | |
| private: | |
| ConnBase *_owner = nullptr; | |
| }; // End class ConnBase::Geo | |
| class TcpInfo | |
| { | |
| using self_type = TcpInfo; | |
| public: | |
| friend class ConnBase; | |
| TcpInfo() = default; | |
| TcpInfo(const self_type &) = delete; | |
| void operator=(const self_type &) = delete; | |
| cripts::string_view Log(); | |
| [[nodiscard]] bool | |
| Ready() const | |
| { | |
| return _ready; | |
| } | |
| // ToDo: Add more member accesses? Tthe underlying info makes it hard to make it cross platform | |
| #if HAS_TCP_INFO | |
| integer | |
| rtt() | |
| { | |
| initialize(); | |
| return (_ready ? info.tcpi_rtt : 0); | |
| } | |
| integer | |
| rto() | |
| { | |
| initialize(); | |
| return (_ready ? info.tcpi_rto : 0); | |
| } | |
| integer | |
| snd_cwnd() | |
| { | |
| initialize(); | |
| return (_ready ? info.tcpi_snd_cwnd : 0); | |
| } | |
| integer | |
| retrans() | |
| { | |
| initialize(); | |
| #if defined(__FreeBSD__) | |
| return (_ready ? info.__tcpi_retrans : 0); | |
| #else | |
| return (_ready ? info.tcpi_retrans : 0); | |
| #endif | |
| } | |
| struct tcp_info info; | |
| socklen_t info_len = sizeof(info); | |
| #else | |
| integer | |
| rtt() | |
| { | |
| return 0; | |
| } | |
| integer | |
| rto() | |
| { | |
| return 0; | |
| } | |
| integer | |
| snd_cwnd() | |
| { | |
| return 0; | |
| } | |
| integer | |
| retrans() | |
| { | |
| return 0; | |
| } | |
| #endif | |
| private: | |
| void initialize(); | |
| ConnBase *_owner = nullptr; | |
| bool _ready = false; | |
| cripts::string _logging; // Storage for the logformat of the tcpinfo | |
| }; // End class ConnBase::TcpInfo | |
| class TLS | |
| { | |
| using self_type = TLS; | |
| public: | |
| friend class ConnBase; | |
| TLS() = default; | |
| void operator=(const self_type &) = delete; | |
| operator bool() | |
| { | |
| auto conn = Connection(); | |
| return conn != nullptr; | |
| } | |
| [[nodiscard]] TSSslConnection | |
| Connection() | |
| { | |
| if (_not_tls) [[unlikely]] { | |
| return nullptr; // Avoid repeated attempts | |
| } | |
| _ensure_initialized(_owner); | |
| if (!_tls) { | |
| _tls = TSVConnSslConnectionGet(_owner->_vc); | |
| if (!_tls) [[unlikely]] { | |
| _not_tls = true; | |
| } | |
| } | |
| return _tls; | |
| } | |
| [[nodiscard]] X509 * | |
| GetX509(bool mTLS = false) | |
| { | |
| auto conn = Connection(); | |
| if (mTLS) { | |
| #ifdef OPENSSL_IS_AT_LEAST_OPENSSL3 | |
| return SSL_get1_peer_certificate(reinterpret_cast<::SSL *>(conn)); | |
| #else | |
| return SSL_get_peer_certificate(reinterpret_cast<::SSL *>(conn)); | |
| #endif | |
| } else { | |
| return SSL_get_certificate(reinterpret_cast<::SSL *>(conn)); | |
| } | |
| } | |
| private: | |
| ConnBase *_owner = nullptr; | |
| TSSslConnection _tls = nullptr; | |
| bool _not_tls = false; | |
| }; // End class ConnBase::SSL | |
| public: | |
| ConnBase() { dscp._owner = congestion._owner = tcpinfo._owner = geo._owner = pacing._owner = mark._owner = tls._owner = this; } | |
| virtual ~ConnBase() = default; | |
| ConnBase(const self_type &) = delete; | |
| void operator=(const self_type &) = delete; | |
| [[nodiscard]] virtual int FD() const = 0; // This needs the txnp from the Context | |
| [[nodiscard]] struct sockaddr const * | |
| Socket() | |
| { | |
| _ensure_initialized(this); | |
| return TSNetVConnRemoteAddrGet(_vc); | |
| } | |
| [[nodiscard]] cripts::IP | |
| IP() | |
| { | |
| _ensure_initialized(this); | |
| return cripts::IP{Socket()}; | |
| } | |
| [[nodiscard]] bool | |
| Initialized() const | |
| { | |
| return _initialized; | |
| } | |
| [[nodiscard]] bool | |
| IsInternal() const | |
| { | |
| return TSHttpTxnIsInternal(_state->txnp); | |
| } | |
| [[nodiscard]] bool | |
| IsTLS() | |
| { | |
| _ensure_initialized(this); | |
| return TSVConnIsSsl(_vc); | |
| } | |
| [[nodiscard]] virtual cripts::IP LocalIP() const = 0; | |
| [[nodiscard]] virtual int Count() const = 0; | |
| virtual void SetDscp(int val) const = 0; | |
| virtual void SetMark(int val) const = 0; | |
| // This should only be called from the Context initializers! | |
| void | |
| set_state(cripts::Transaction *state) | |
| { | |
| _state = state; | |
| } | |
| Dscp dscp; | |
| Congestion congestion; | |
| TcpInfo tcpinfo; | |
| Geo geo; | |
| Pacing pacing; | |
| Mark mark; | |
| mutable TLS tls; | |
| cripts::string_view string(unsigned ipv4_cidr = 32, unsigned ipv6_cidr = 128); | |
| cripts::Certs::Client ClientCert(); | |
| cripts::Certs::Server ServerCert(); | |
| protected: | |
| static void | |
| _ensure_initialized(self_type *ptr) | |
| { | |
| if (!ptr->Initialized()) [[unlikely]] { | |
| ptr->_initialize(); | |
| } | |
| } | |
| void virtual _initialize() { _initialized = true; } | |
| cripts::Transaction *_state = nullptr; | |
| struct sockaddr const *_socket = nullptr; | |
| TSVConn _vc = nullptr; | |
| bool _initialized = false; | |
| }; // End class ConnBase | |
| } // namespace detail | |
| namespace cripts | |
| { | |
| namespace Client | |
| { | |
| class Connection : public detail::ConnBase | |
| { | |
| using super_type = detail::ConnBase; | |
| using self_type = Connection; | |
| public: | |
| Connection() = default; | |
| Connection(const self_type &) = delete; | |
| void operator=(const self_type &) = delete; | |
| [[nodiscard]] int FD() const override; | |
| [[nodiscard]] int Count() const override; | |
| static Connection &_get(cripts::Context *context); | |
| void _initialize() override; | |
| void | |
| SetDscp(int val) const override | |
| { | |
| TSHttpTxnClientPacketDscpSet(_state->txnp, val); | |
| } | |
| void | |
| SetMark(int val) const override | |
| { | |
| TSHttpTxnClientPacketMarkSet(_state->txnp, val); | |
| } | |
| [[nodiscard]] cripts::IP | |
| LocalIP() const override | |
| { | |
| return cripts::IP{TSHttpTxnIncomingAddrGet(_state->txnp)}; | |
| } | |
| }; // End class Client::Connection | |
| } // namespace Client | |
| namespace Server | |
| { | |
| class Connection : public detail::ConnBase | |
| { | |
| using super_type = detail::ConnBase; | |
| using self_type = Connection; | |
| public: | |
| Connection() = default; | |
| Connection(const self_type &) = delete; | |
| void operator=(const self_type &) = delete; | |
| [[nodiscard]] int FD() const override; | |
| [[nodiscard]] int Count() const override; | |
| static Connection &_get(cripts::Context *context); | |
| void _initialize() override; | |
| void | |
| SetDscp(int val) const override | |
| { | |
| TSHttpTxnServerPacketDscpSet(_state->txnp, val); | |
| } | |
| void | |
| SetMark(int val) const override | |
| { | |
| TSHttpTxnServerPacketMarkSet(_state->txnp, val); | |
| } | |
| [[nodiscard]] cripts::IP | |
| LocalIP() const override | |
| { | |
| return cripts::IP{TSHttpTxnOutgoingAddrGet(_state->txnp)}; | |
| } | |
| }; // End class Server::Connection | |
| } // namespace Server | |
| } // namespace cripts | |
| // Formatters for {fmt} | |
| namespace fmt | |
| { | |
| template <> struct formatter<cripts::IP> { | |
| constexpr auto | |
| parse(format_parse_context &ctx) -> decltype(ctx.begin()) | |
| { | |
| return ctx.begin(); | |
| } | |
| template <typename FormatContext> | |
| auto | |
| format(cripts::IP &ip, FormatContext &ctx) -> decltype(ctx.out()) | |
| { | |
| return format_to(ctx.out(), "{}", ip.GetSV()); | |
| } | |
| }; | |
| } // namespace fmt |