| /* |
| 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. |
| */ |
| |
| ////////////////////////////////////////////////////////////////////////////////////////////// |
| // conditions.cc: Implementation of the condition classes |
| // |
| // |
| |
| #include <sys/time.h> |
| #include <unistd.h> |
| #include <arpa/inet.h> |
| #include <cctype> |
| #include <sstream> |
| #include <array> |
| #include <atomic> |
| |
| #include "ts/ts.h" |
| |
| #include "conditions.h" |
| #include "lulu.h" |
| |
| // This is a bit of a hack, to get the more linux specific tcp_info struct ... |
| #if HAVE_STRUCT_LINUX_TCP_INFO |
| #ifndef _LINUX_TCP_H |
| #define _LINUX_TCP_H |
| #endif |
| #elif HAVE_NETINET_IN_H |
| #ifndef _NETINET_TCP_H |
| #define _NETINET_TCP_H |
| #endif |
| #endif |
| |
| // ConditionStatus |
| void |
| ConditionStatus::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(static_cast<TSHttpStatus>(strtol(p.get_arg().c_str(), nullptr, 10))); |
| _matcher = match; |
| |
| require_resources(RSRC_SERVER_RESPONSE_HEADERS); |
| require_resources(RSRC_CLIENT_RESPONSE_HEADERS); |
| require_resources(RSRC_RESPONSE_STATUS); |
| } |
| |
| void |
| ConditionStatus::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_READ_RESPONSE_HDR_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| } |
| |
| bool |
| ConditionStatus::eval(const Resources &res) |
| { |
| TSDebug(PLUGIN_NAME, "Evaluating STATUS()"); |
| return static_cast<MatcherType *>(_matcher)->test(res.resp_status); |
| } |
| |
| void |
| ConditionStatus::append_value(std::string &s, const Resources &res) |
| { |
| s += std::to_string(res.resp_status); |
| TSDebug(PLUGIN_NAME, "Appending STATUS(%d) to evaluation value -> %s", res.resp_status, s.c_str()); |
| } |
| |
| // ConditionMethod |
| void |
| ConditionMethod::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| |
| bool |
| ConditionMethod::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| TSDebug(PLUGIN_NAME, "Evaluating METHOD()"); |
| |
| return static_cast<const MatcherType *>(_matcher)->test(s); |
| } |
| |
| void |
| ConditionMethod::append_value(std::string &s, const Resources &res) |
| { |
| TSMBuffer bufp; |
| TSMLoc hdr_loc; |
| int len; |
| |
| bufp = res.client_bufp; |
| hdr_loc = res.client_hdr_loc; |
| |
| if (bufp && hdr_loc) { |
| const char *value = TSHttpHdrMethodGet(bufp, hdr_loc, &len); |
| TSDebug(PLUGIN_NAME, "Appending METHOD(%s) to evaluation value -> %.*s", _qualifier.c_str(), len, value); |
| s.append(value, len); |
| } |
| } |
| |
| // ConditionRandom: random 0 to (N-1) |
| void |
| ConditionRandom::initialize(Parser &p) |
| { |
| struct timeval tv; |
| Condition::initialize(p); |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| gettimeofday(&tv, nullptr); |
| _seed = getpid() * tv.tv_usec; |
| _max = strtol(_qualifier.c_str(), nullptr, 10); |
| |
| match->set(static_cast<unsigned int>(strtol(p.get_arg().c_str(), nullptr, 10))); |
| _matcher = match; |
| } |
| |
| bool |
| ConditionRandom::eval(const Resources & /* res ATS_UNUSED */) |
| { |
| TSDebug(PLUGIN_NAME, "Evaluating RANDOM()"); |
| return static_cast<const MatcherType *>(_matcher)->test(rand_r(&_seed) % _max); |
| } |
| |
| void |
| ConditionRandom::append_value(std::string &s, const Resources & /* res ATS_UNUSED */) |
| { |
| std::ostringstream oss; |
| |
| oss << rand_r(&_seed) % _max; |
| s += oss.str(); |
| TSDebug(PLUGIN_NAME, "Appending RANDOM(%d) to evaluation value -> %s", _max, s.c_str()); |
| } |
| |
| // ConditionAccess: access(file) |
| void |
| ConditionAccess::initialize(Parser &p) |
| { |
| struct timeval tv; |
| Condition::initialize(p); |
| |
| gettimeofday(&tv, nullptr); |
| |
| _next = tv.tv_sec + 2; |
| _last = !access(_qualifier.c_str(), R_OK); |
| } |
| |
| void |
| ConditionAccess::append_value(std::string &s, const Resources &res) |
| { |
| if (eval(res)) { |
| s += "OK"; |
| } else { |
| s += "NOT OK"; |
| } |
| } |
| |
| bool |
| ConditionAccess::eval(const Resources & /* res ATS_UNUSED */) |
| { |
| struct timeval tv; |
| |
| gettimeofday(&tv, nullptr); |
| if (tv.tv_sec > _next) { |
| // There is a small "race" here, where we could end up calling access() a few times extra. I think |
| // that is OK, and not worth protecting with a lock. |
| bool check = !access(_qualifier.c_str(), R_OK); |
| |
| tv.tv_sec += 2; |
| std::atomic_thread_fence(std::memory_order_seq_cst); |
| _next = tv.tv_sec; // I hope this is an atomic "set"... |
| _last = check; // This sure ought to be |
| } |
| TSDebug(PLUGIN_NAME, "Evaluating ACCESS(%s) -> %d", _qualifier.c_str(), _last); |
| |
| return _last; |
| } |
| |
| // ConditionHeader: request or response header |
| void |
| ConditionHeader::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| |
| require_resources(RSRC_CLIENT_REQUEST_HEADERS); |
| require_resources(RSRC_CLIENT_RESPONSE_HEADERS); |
| require_resources(RSRC_SERVER_REQUEST_HEADERS); |
| require_resources(RSRC_SERVER_RESPONSE_HEADERS); |
| } |
| |
| void |
| ConditionHeader::append_value(std::string &s, const Resources &res) |
| { |
| TSMBuffer bufp; |
| TSMLoc hdr_loc; |
| int len; |
| |
| if (_client) { |
| bufp = res.client_bufp; |
| hdr_loc = res.client_hdr_loc; |
| } else { |
| bufp = res.bufp; |
| hdr_loc = res.hdr_loc; |
| } |
| |
| if (bufp && hdr_loc) { |
| TSMLoc field_loc; |
| |
| field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, _qualifier.c_str(), _qualifier.size()); |
| TSDebug(PLUGIN_NAME, "Getting Header: %s, field_loc: %p", _qualifier.c_str(), field_loc); |
| |
| while (field_loc) { |
| const char *value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, &len); |
| TSMLoc next_field_loc = TSMimeHdrFieldNextDup(bufp, hdr_loc, field_loc); |
| |
| TSDebug(PLUGIN_NAME, "Appending HEADER(%s) to evaluation value -> %.*s", _qualifier.c_str(), len, value); |
| s.append(value, len); |
| // multiple headers with the same name must be semantically the same as one value which is comma separated |
| if (next_field_loc) { |
| s += ','; |
| } |
| TSHandleMLocRelease(bufp, hdr_loc, field_loc); |
| field_loc = next_field_loc; |
| } |
| } |
| } |
| |
| bool |
| ConditionHeader::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| TSDebug(PLUGIN_NAME, "Evaluating HEADER()"); |
| |
| return static_cast<const MatcherType *>(_matcher)->test(s); |
| } |
| |
| // ConditionUrl: request or response header. TODO: This is not finished, at all!!! |
| void |
| ConditionUrl::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| |
| void |
| ConditionUrl::set_qualifier(const std::string &q) |
| { |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{URL:%s}", q.c_str()); |
| _url_qual = parse_url_qualifier(q); |
| } |
| |
| void |
| ConditionUrl::append_value(std::string &s, const Resources &res) |
| { |
| TSMLoc url = nullptr; |
| TSMBuffer bufp = nullptr; |
| |
| if (_type == CLIENT) { |
| // CLIENT always uses the pristine URL |
| TSDebug(PLUGIN_NAME, " Using the pristine url"); |
| if (TSHttpTxnPristineUrlGet(res.txnp, &bufp, &url) != TS_SUCCESS) { |
| TSError("[header_rewrite] Error getting the pristine URL"); |
| return; |
| } |
| } else if (res._rri != nullptr) { |
| // called at the remap hook |
| bufp = res._rri->requestBufp; |
| if (_type == URL) { |
| TSDebug(PLUGIN_NAME, " Using the request url"); |
| url = res._rri->requestUrl; |
| } else if (_type == FROM) { |
| TSDebug(PLUGIN_NAME, " Using the from url"); |
| url = res._rri->mapFromUrl; |
| } else if (_type == TO) { |
| TSDebug(PLUGIN_NAME, " Using the to url"); |
| url = res._rri->mapToUrl; |
| } else { |
| TSError("[header_rewrite] Invalid option value"); |
| return; |
| } |
| } else { |
| if (_type == URL) { |
| bufp = res.bufp; |
| TSMLoc hdr_loc = res.hdr_loc; |
| if (TSHttpHdrUrlGet(bufp, hdr_loc, &url) != TS_SUCCESS) { |
| TSError("[header_rewrite] Error getting the URL"); |
| return; |
| } |
| } else { |
| TSError("[header_rewrite] Rule not supported at this hook"); |
| return; |
| } |
| } |
| |
| int i; |
| const char *q_str; |
| |
| switch (_url_qual) { |
| case URL_QUAL_HOST: |
| q_str = TSUrlHostGet(bufp, url, &i); |
| s.append(q_str, i); |
| TSDebug(PLUGIN_NAME, " Host to match is: %.*s", i, q_str); |
| break; |
| case URL_QUAL_PORT: |
| i = TSUrlPortGet(bufp, url); |
| s.append(std::to_string(i)); |
| TSDebug(PLUGIN_NAME, " Port to match is: %d", i); |
| break; |
| case URL_QUAL_PATH: |
| q_str = TSUrlPathGet(bufp, url, &i); |
| s.append(q_str, i); |
| TSDebug(PLUGIN_NAME, " Path to match is: %.*s", i, q_str); |
| break; |
| case URL_QUAL_QUERY: |
| q_str = TSUrlHttpQueryGet(bufp, url, &i); |
| s.append(q_str, i); |
| TSDebug(PLUGIN_NAME, " Query parameters to match is: %.*s", i, q_str); |
| break; |
| case URL_QUAL_MATRIX: |
| q_str = TSUrlHttpParamsGet(bufp, url, &i); |
| s.append(q_str, i); |
| TSDebug(PLUGIN_NAME, " Matrix parameters to match is: %.*s", i, q_str); |
| break; |
| case URL_QUAL_SCHEME: |
| q_str = TSUrlSchemeGet(bufp, url, &i); |
| s.append(q_str, i); |
| TSDebug(PLUGIN_NAME, " Scheme to match is: %.*s", i, q_str); |
| break; |
| case URL_QUAL_URL: |
| case URL_QUAL_NONE: { |
| // TSUrlStringGet returns an allocated char * we must free |
| char *non_const_q_str = TSUrlStringGet(bufp, url, &i); |
| s.append(non_const_q_str, i); |
| TSDebug(PLUGIN_NAME, " URL to match is: %.*s", i, non_const_q_str); |
| TSfree(non_const_q_str); |
| break; |
| } |
| } |
| } |
| |
| bool |
| ConditionUrl::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| return static_cast<const Matchers<std::string> *>(_matcher)->test(s); |
| } |
| |
| // ConditionDBM: do a lookup against a DBM |
| void |
| ConditionDBM::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| match->set(p.get_arg()); |
| _matcher = match; |
| |
| std::string::size_type pos = _qualifier.find_first_of(','); |
| |
| if (pos != std::string::npos) { |
| _file = _qualifier.substr(0, pos); |
| //_dbm = mdbm_open(_file.c_str(), O_RDONLY, 0, 0, 0); |
| // if (NULL != _dbm) { |
| // TSDebug(PLUGIN_NAME, "Opened DBM file %s", _file.c_str()); |
| // _key.set_value(_qualifier.substr(pos + 1)); |
| // } else { |
| // TSError("Failed to open DBM file: %s", _file.c_str()); |
| // } |
| } else { |
| TSError("[%s] Malformed DBM condition", PLUGIN_NAME); |
| } |
| } |
| |
| void |
| ConditionDBM::append_value(std::string & /* s ATS_UNUSED */, const Resources & /* res ATS_UNUSED */) |
| { |
| // std::string key; |
| |
| // if (!_dbm) { |
| // return; |
| // } |
| |
| // _key.append_value(key, res); |
| // if (key.size() > 0) { |
| // datum k, v; |
| |
| // TSDebug(PLUGIN_NAME, "Looking up DBM(\"%s\")", key.c_str()); |
| // k.dptr = const_cast<char*>(key.c_str()); |
| // k.dsize = key.size(); |
| |
| // TSMutexLock(_mutex); |
| // //v = mdbm_fetch(_dbm, k); |
| // TSMutexUnlock(_mutex); |
| // if (v.dsize > 0) { |
| // TSDebug(PLUGIN_NAME, "Appending DBM(%.*s) to evaluation value -> %.*s", k.dsize, k.dptr, v.dsize, v.dptr); |
| // s.append(v.dptr, v.dsize); |
| // } |
| // } |
| } |
| |
| bool |
| ConditionDBM::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| TSDebug(PLUGIN_NAME, "Evaluating DBM()"); |
| |
| return static_cast<const MatcherType *>(_matcher)->test(s); |
| } |
| |
| // ConditionCookie: request or response header |
| void |
| ConditionCookie::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| |
| require_resources(RSRC_CLIENT_REQUEST_HEADERS); |
| } |
| |
| void |
| ConditionCookie::append_value(std::string &s, const Resources &res) |
| { |
| TSMBuffer bufp = res.client_bufp; |
| TSMLoc hdr_loc = res.client_hdr_loc; |
| TSMLoc field_loc; |
| int error; |
| int cookies_len; |
| int cookie_value_len; |
| const char *cookies; |
| const char *cookie_value; |
| const char *const cookie_name = _qualifier.c_str(); |
| const int cookie_name_len = _qualifier.length(); |
| |
| // Sanity |
| if (bufp == nullptr || hdr_loc == nullptr) { |
| return; |
| } |
| |
| // Find Cookie |
| field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, TS_MIME_FIELD_COOKIE, TS_MIME_LEN_COOKIE); |
| if (field_loc == nullptr) { |
| return; |
| } |
| |
| // Get all cookies |
| cookies = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, -1, &cookies_len); |
| if (cookies == nullptr || cookies_len <= 0) { |
| goto out_release_field; |
| } |
| |
| // Find particular cookie's value |
| error = get_cookie_value(cookies, cookies_len, cookie_name, cookie_name_len, &cookie_value, &cookie_value_len); |
| if (error == TS_ERROR) { |
| goto out_release_field; |
| } |
| |
| TSDebug(PLUGIN_NAME, "Appending COOKIE(%s) to evaluation value -> %.*s", cookie_name, cookie_value_len, cookie_value); |
| s.append(cookie_value, cookie_value_len); |
| |
| // Unwind |
| out_release_field: |
| TSHandleMLocRelease(bufp, hdr_loc, field_loc); |
| } |
| |
| bool |
| ConditionCookie::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| TSDebug(PLUGIN_NAME, "Evaluating COOKIE()"); |
| |
| return static_cast<const MatcherType *>(_matcher)->test(s); |
| } |
| |
| // ConditionInternalTxn: Is the txn internal? |
| bool |
| ConditionInternalTxn::eval(const Resources &res) |
| { |
| bool ret = (0 != TSHttpTxnIsInternal(res.txnp)); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating INTERNAL-TRANSACTION() -> %d", ret); |
| return ret; |
| } |
| |
| void |
| ConditionIp::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| |
| void |
| ConditionIp::set_qualifier(const std::string &q) |
| { |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{IP:%s} qualifier", q.c_str()); |
| |
| if (q == "CLIENT") { |
| _ip_qual = IP_QUAL_CLIENT; |
| } else if (q == "INBOUND") { |
| _ip_qual = IP_QUAL_INBOUND; |
| } else if (q == "SERVER") { |
| _ip_qual = IP_QUAL_SERVER; |
| } else if (q == "OUTBOUND") { |
| _ip_qual = IP_QUAL_OUTBOUND; |
| } else { |
| TSError("[%s] Unknown IP() qualifier: %s", PLUGIN_NAME, q.c_str()); |
| } |
| } |
| |
| bool |
| ConditionIp::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating IP(): %s - rval: %d", s.c_str(), rval); |
| |
| return rval; |
| } |
| |
| void |
| ConditionIp::append_value(std::string &s, const Resources &res) |
| { |
| bool ip_set = false; |
| char ip[INET6_ADDRSTRLEN]; |
| |
| switch (_ip_qual) { |
| case IP_QUAL_CLIENT: |
| ip_set = (nullptr != getIP(TSHttpTxnClientAddrGet(res.txnp), ip)); |
| break; |
| case IP_QUAL_INBOUND: |
| ip_set = (nullptr != getIP(TSHttpTxnIncomingAddrGet(res.txnp), ip)); |
| break; |
| case IP_QUAL_SERVER: |
| ip_set = (nullptr != getIP(TSHttpTxnServerAddrGet(res.txnp), ip)); |
| break; |
| case IP_QUAL_OUTBOUND: |
| ip_set = (nullptr != getIP(TSHttpTxnOutgoingAddrGet(res.txnp), ip)); |
| break; |
| } |
| |
| if (ip_set) { |
| s += ip; |
| } |
| } |
| |
| // ConditionTransactCount |
| void |
| ConditionTransactCount::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| MatcherType *match = new MatcherType(_cond_op); |
| std::string const &arg = p.get_arg(); |
| |
| match->set(strtol(arg.c_str(), nullptr, 10)); |
| _matcher = match; |
| } |
| |
| bool |
| ConditionTransactCount::eval(const Resources &res) |
| { |
| TSHttpSsn ssn = TSHttpTxnSsnGet(res.txnp); |
| |
| if (ssn) { |
| int n = TSHttpSsnTransactionCount(ssn); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating TXN-COUNT()"); |
| return static_cast<MatcherType *>(_matcher)->test(n); |
| } |
| |
| TSDebug(PLUGIN_NAME, "\tNo session found, returning false"); |
| return false; |
| } |
| |
| void |
| ConditionTransactCount::append_value(std::string &s, Resources const &res) |
| { |
| TSHttpSsn ssn = TSHttpTxnSsnGet(res.txnp); |
| |
| if (ssn) { |
| char value[32]; // enough for UINT64_MAX |
| int count = TSHttpSsnTransactionCount(ssn); |
| int length = ink_fast_itoa(count, value, sizeof(value)); |
| |
| if (length > 0) { |
| TSDebug(PLUGIN_NAME, "Appending TXN-COUNT %s to evaluation value %.*s", _qualifier.c_str(), length, value); |
| s.append(value, length); |
| } |
| } |
| } |
| |
| // ConditionNow: time related conditions, such as time since epoch (default), hour, day etc. |
| // Time related functionality for statements. We return an int64_t here, to assure that |
| // gettimeofday() / Epoch does not lose bits. |
| int64_t |
| ConditionNow::get_now_qualified(NowQualifiers qual) const |
| { |
| time_t now; |
| |
| // First short circuit for the Epoch qualifier, since it needs less data |
| time(&now); |
| if (NOW_QUAL_EPOCH == qual) { |
| return static_cast<int64_t>(now); |
| } else { |
| struct tm res; |
| |
| localtime_r(&now, &res); |
| switch (qual) { |
| case NOW_QUAL_YEAR: |
| return static_cast<int64_t>(res.tm_year + 1900); // This makes more sense |
| break; |
| case NOW_QUAL_MONTH: |
| return static_cast<int64_t>(res.tm_mon); |
| break; |
| case NOW_QUAL_DAY: |
| return static_cast<int64_t>(res.tm_mday); |
| break; |
| case NOW_QUAL_HOUR: |
| return static_cast<int64_t>(res.tm_hour); |
| break; |
| case NOW_QUAL_MINUTE: |
| return static_cast<int64_t>(res.tm_min); |
| break; |
| case NOW_QUAL_WEEKDAY: |
| return static_cast<int64_t>(res.tm_wday); |
| break; |
| case NOW_QUAL_YEARDAY: |
| return static_cast<int64_t>(res.tm_yday); |
| break; |
| default: |
| TSReleaseAssert(!"All cases should have been handled"); |
| break; |
| } |
| } |
| return 0; |
| } |
| |
| void |
| ConditionNow::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10))); |
| _matcher = match; |
| } |
| |
| void |
| ConditionNow::set_qualifier(const std::string &q) |
| { |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{NOW:%s} qualifier", q.c_str()); |
| |
| if (q == "EPOCH") { |
| _now_qual = NOW_QUAL_EPOCH; |
| } else if (q == "YEAR") { |
| _now_qual = NOW_QUAL_YEAR; |
| } else if (q == "MONTH") { |
| _now_qual = NOW_QUAL_MONTH; |
| } else if (q == "DAY") { |
| _now_qual = NOW_QUAL_DAY; |
| } else if (q == "HOUR") { |
| _now_qual = NOW_QUAL_HOUR; |
| } else if (q == "MINUTE") { |
| _now_qual = NOW_QUAL_MINUTE; |
| } else if (q == "WEEKDAY") { |
| _now_qual = NOW_QUAL_WEEKDAY; |
| } else if (q == "YEARDAY") { |
| _now_qual = NOW_QUAL_YEARDAY; |
| } else { |
| TSError("[%s] Unknown NOW() qualifier: %s", PLUGIN_NAME, q.c_str()); |
| } |
| } |
| |
| void |
| ConditionNow::append_value(std::string &s, const Resources & /* res ATS_UNUSED */) |
| { |
| std::ostringstream oss; |
| |
| oss << get_now_qualified(_now_qual); |
| s += oss.str(); |
| TSDebug(PLUGIN_NAME, "Appending NOW() to evaluation value -> %s", s.c_str()); |
| } |
| |
| bool |
| ConditionNow::eval(const Resources &res) |
| { |
| int64_t now = get_now_qualified(_now_qual); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating NOW()"); |
| return static_cast<const MatcherType *>(_matcher)->test(now); |
| } |
| |
| std::string |
| ConditionGeo::get_geo_string(const sockaddr *addr) const |
| { |
| TSError("[%s] No Geo library available!", PLUGIN_NAME); |
| return ""; |
| } |
| |
| int64_t |
| ConditionGeo::get_geo_int(const sockaddr *addr) const |
| { |
| TSError("[%s] No Geo library available!", PLUGIN_NAME); |
| return 0; |
| } |
| |
| void |
| ConditionGeo::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| if (is_int_type()) { |
| Matchers<int64_t> *match = new Matchers<int64_t>(_cond_op); |
| |
| match->set(static_cast<int64_t>(strtol(p.get_arg().c_str(), nullptr, 10))); |
| _matcher = match; |
| } else { |
| // The default is to have a string matcher |
| Matchers<std::string> *match = new Matchers<std::string>(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| } |
| |
| void |
| ConditionGeo::set_qualifier(const std::string &q) |
| { |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{GEO:%s} qualifier", q.c_str()); |
| |
| if (q == "COUNTRY") { |
| _geo_qual = GEO_QUAL_COUNTRY; |
| is_int_type(false); |
| } else if (q == "COUNTRY-ISO") { |
| _geo_qual = GEO_QUAL_COUNTRY_ISO; |
| is_int_type(true); |
| } else if (q == "ASN") { |
| _geo_qual = GEO_QUAL_ASN; |
| is_int_type(true); |
| } else if (q == "ASN-NAME") { |
| _geo_qual = GEO_QUAL_ASN_NAME; |
| is_int_type(false); |
| } else { |
| TSError("[%s] Unknown Geo() qualifier: %s", PLUGIN_NAME, q.c_str()); |
| } |
| } |
| |
| void |
| ConditionGeo::append_value(std::string &s, const Resources &res) |
| { |
| std::ostringstream oss; |
| |
| if (is_int_type()) { |
| oss << get_geo_int(TSHttpTxnClientAddrGet(res.txnp)); |
| } else { |
| oss << get_geo_string(TSHttpTxnClientAddrGet(res.txnp)); |
| } |
| s += oss.str(); |
| TSDebug(PLUGIN_NAME, "Appending GEO() to evaluation value -> %s", s.c_str()); |
| } |
| |
| bool |
| ConditionGeo::eval(const Resources &res) |
| { |
| bool ret = false; |
| |
| TSDebug(PLUGIN_NAME, "Evaluating GEO()"); |
| if (is_int_type()) { |
| int64_t geo = get_geo_int(TSHttpTxnClientAddrGet(res.txnp)); |
| |
| ret = static_cast<const Matchers<int64_t> *>(_matcher)->test(geo); |
| } else { |
| std::string s; |
| |
| append_value(s, res); |
| ret = static_cast<const Matchers<std::string> *>(_matcher)->test(s); |
| } |
| |
| return ret; |
| } |
| |
| // ConditionId: Some identifier strings, currently: |
| // PROCESS: The process UUID string |
| // REQUEST: The request (HttpSM::sm_id) counter |
| // UNIQUE: The combination of UUID-sm_id |
| void |
| ConditionId::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| if (_id_qual == ID_QUAL_REQUEST) { |
| Matchers<uint64_t> *match = new Matchers<uint64_t>(_cond_op); |
| |
| match->set(static_cast<uint64_t>(strtol(p.get_arg().c_str(), nullptr, 10))); |
| _matcher = match; |
| } else { |
| // The default is to have a string matcher |
| Matchers<std::string> *match = new Matchers<std::string>(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| } |
| |
| void |
| ConditionId::set_qualifier(const std::string &q) |
| { |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{ID:%s} qualifier", q.c_str()); |
| |
| if (q == "UNIQUE") { |
| _id_qual = ID_QUAL_UNIQUE; |
| } else if (q == "PROCESS") { |
| _id_qual = ID_QUAL_PROCESS; |
| } else if (q == "REQUEST") { |
| _id_qual = ID_QUAL_REQUEST; |
| } else { |
| TSError("[%s] Unknown ID() qualifier: %s", PLUGIN_NAME, q.c_str()); |
| } |
| } |
| |
| void |
| ConditionId::append_value(std::string &s, const Resources &res ATS_UNUSED) |
| { |
| switch (_id_qual) { |
| case ID_QUAL_REQUEST: { |
| std::ostringstream oss; |
| |
| oss << TSHttpTxnIdGet(res.txnp); |
| s += oss.str(); |
| } break; |
| case ID_QUAL_PROCESS: { |
| TSUuid process = TSProcessUuidGet(); |
| |
| if (process) { |
| s += TSUuidStringGet(process); |
| } |
| } break; |
| case ID_QUAL_UNIQUE: { |
| char uuid[TS_CRUUID_STRING_LEN + 1]; |
| |
| if (TS_SUCCESS == TSClientRequestUuidGet(res.txnp, uuid)) { |
| s += uuid; |
| } |
| } break; |
| } |
| TSDebug(PLUGIN_NAME, "Appending ID() to evaluation value -> %s", s.c_str()); |
| } |
| |
| bool |
| ConditionId::eval(const Resources &res) |
| { |
| if (_id_qual == ID_QUAL_REQUEST) { |
| uint64_t id = TSHttpTxnIdGet(res.txnp); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating GEO() -> %" PRIu64, id); |
| return static_cast<const Matchers<uint64_t> *>(_matcher)->test(id); |
| } else { |
| std::string s; |
| |
| append_value(s, res); |
| bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating ID(): %s - rval: %d", s.c_str(), rval); |
| return rval; |
| } |
| } |
| |
| void |
| ConditionCidr::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| |
| void |
| ConditionCidr::set_qualifier(const std::string &q) |
| { |
| bool ok = true; |
| int cidr; |
| char *endp; |
| |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{CIDR:%s} qualifier", q.c_str()); |
| cidr = strtol(q.c_str(), &endp, 10); |
| if (cidr >= 0 && cidr <= 32) { |
| _v4_mask.s_addr = UINT32_MAX >> (32 - cidr); |
| _v4_cidr = cidr; |
| if (endp && (*endp == ',' || *endp == '/' || *endp == ':')) { |
| cidr = strtol(endp + 1, nullptr, 10); |
| if (cidr >= 0 && cidr <= 128) { |
| _v6_cidr = cidr; |
| } else { |
| TSError("[%s] Bad CIDR mask for IPv6: %s", PLUGIN_NAME, q.c_str()); |
| ok = false; |
| } |
| } |
| } else { |
| TSError("[%s] Bad CIDR mask for IPv4: %s", PLUGIN_NAME, q.c_str()); |
| ok = false; |
| } |
| |
| // Update the bit-masks |
| if (ok) { |
| _create_masks(); |
| } |
| } |
| |
| bool |
| ConditionCidr::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| TSDebug(PLUGIN_NAME, "Evaluating CIDR()"); |
| |
| return static_cast<MatcherType *>(_matcher)->test(s); |
| } |
| |
| void |
| ConditionCidr::append_value(std::string &s, const Resources &res) |
| { |
| struct sockaddr const *addr = TSHttpTxnClientAddrGet(res.txnp); |
| |
| if (addr) { |
| switch (addr->sa_family) { |
| case AF_INET: { |
| char resource[INET_ADDRSTRLEN]; |
| struct in_addr ipv4 = reinterpret_cast<const struct sockaddr_in *>(addr)->sin_addr; |
| |
| ipv4.s_addr &= _v4_mask.s_addr; |
| inet_ntop(AF_INET, &ipv4, resource, INET_ADDRSTRLEN); |
| if (resource[0]) { |
| s += resource; |
| } |
| } break; |
| case AF_INET6: { |
| char resource[INET6_ADDRSTRLEN]; |
| struct in6_addr ipv6 = reinterpret_cast<const struct sockaddr_in6 *>(addr)->sin6_addr; |
| |
| if (_v6_zero_bytes > 0) { |
| memset(&ipv6.s6_addr[16 - _v6_zero_bytes], 0, _v6_zero_bytes); |
| } |
| if (_v6_mask != 0xff) { |
| ipv6.s6_addr[16 - _v6_zero_bytes] &= _v6_mask; |
| } |
| inet_ntop(AF_INET6, &ipv6, resource, INET6_ADDRSTRLEN); |
| if (resource[0]) { |
| s += resource; |
| } |
| } break; |
| } |
| } else { |
| s += "0.0.0.0"; // No client addr for some reason ... |
| } |
| } |
| |
| // Little helper function, to create the masks |
| void |
| ConditionCidr::_create_masks() |
| { |
| _v4_mask.s_addr = htonl(UINT32_MAX << (32 - _v4_cidr)); |
| _v6_zero_bytes = (128 - _v6_cidr) / 8; |
| _v6_mask = 0xff >> ((128 - _v6_cidr) % 8); |
| } |
| |
| void |
| ConditionInbound::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| |
| MatcherType *match = new MatcherType(_cond_op); |
| |
| match->set(p.get_arg()); |
| _matcher = match; |
| } |
| |
| void |
| ConditionInbound::set_qualifier(const std::string &q) |
| { |
| Condition::set_qualifier(q); |
| |
| TSDebug(PLUGIN_NAME, "\tParsing %%{%s:%s} qualifier", TAG, q.c_str()); |
| |
| if (q == "LOCAL-ADDR") { |
| _net_qual = NET_QUAL_LOCAL_ADDR; |
| } else if (q == "LOCAL-PORT") { |
| _net_qual = NET_QUAL_LOCAL_PORT; |
| } else if (q == "REMOTE-ADDR") { |
| _net_qual = NET_QUAL_REMOTE_ADDR; |
| } else if (q == "REMOTE-PORT") { |
| _net_qual = NET_QUAL_REMOTE_PORT; |
| } else if (q == "TLS") { |
| _net_qual = NET_QUAL_TLS; |
| } else if (q == "H2") { |
| _net_qual = NET_QUAL_H2; |
| } else if (q == "IPV4") { |
| _net_qual = NET_QUAL_IPV4; |
| } else if (q == "IPV6") { |
| _net_qual = NET_QUAL_IPV6; |
| } else if (q == "IP-FAMILY") { |
| _net_qual = NET_QUAL_IP_FAMILY; |
| } else if (q == "STACK") { |
| _net_qual = NET_QUAL_STACK; |
| } else { |
| TSError("[%s] Unknown %s() qualifier: %s", PLUGIN_NAME, TAG, q.c_str()); |
| } |
| } |
| |
| bool |
| ConditionInbound::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating %s(): %s - rval: %d", TAG, s.c_str(), rval); |
| |
| return rval; |
| } |
| |
| void |
| ConditionInbound::append_value(std::string &s, const Resources &res) |
| { |
| this->append_value(s, res, _net_qual); |
| } |
| |
| void |
| ConditionInbound::append_value(std::string &s, const Resources &res, NetworkSessionQualifiers qual) |
| { |
| const char *zret = nullptr; |
| char text[INET6_ADDRSTRLEN]; |
| |
| switch (qual) { |
| case NET_QUAL_LOCAL_ADDR: { |
| zret = getIP(TSHttpTxnIncomingAddrGet(res.txnp), text); |
| } break; |
| case NET_QUAL_LOCAL_PORT: { |
| uint16_t port = getPort(TSHttpTxnIncomingAddrGet(res.txnp)); |
| snprintf(text, sizeof(text), "%d", port); |
| zret = text; |
| } break; |
| case NET_QUAL_REMOTE_ADDR: { |
| zret = getIP(TSHttpTxnClientAddrGet(res.txnp), text); |
| } break; |
| case NET_QUAL_REMOTE_PORT: { |
| uint16_t port = getPort(TSHttpTxnClientAddrGet(res.txnp)); |
| snprintf(text, sizeof(text), "%d", port); |
| zret = text; |
| } break; |
| case NET_QUAL_TLS: |
| zret = TSHttpTxnClientProtocolStackContains(res.txnp, "tls/"); |
| break; |
| case NET_QUAL_H2: |
| zret = TSHttpTxnClientProtocolStackContains(res.txnp, "h2"); |
| break; |
| case NET_QUAL_IPV4: |
| zret = TSHttpTxnClientProtocolStackContains(res.txnp, "ipv4"); |
| break; |
| case NET_QUAL_IPV6: |
| zret = TSHttpTxnClientProtocolStackContains(res.txnp, "ipv6"); |
| break; |
| case NET_QUAL_IP_FAMILY: |
| zret = TSHttpTxnClientProtocolStackContains(res.txnp, "ip"); |
| break; |
| case NET_QUAL_STACK: { |
| std::array<char const *, 8> tags; |
| int count = 0; |
| size_t len = 0; |
| TSHttpTxnClientProtocolStackGet(res.txnp, tags.size(), tags.data(), &count); |
| for (int i = 0; i < count; ++i) { |
| len += 1 + strlen(tags[i]); |
| } |
| s.reserve(len); |
| for (int i = 0; i < count; ++i) { |
| if (i) { |
| s += ','; |
| } |
| s += tags[i]; |
| } |
| } break; |
| } |
| |
| if (zret) { |
| s += zret; |
| } |
| } |
| |
| ConditionStringLiteral::ConditionStringLiteral(const std::string &v) |
| { |
| TSDebug(PLUGIN_NAME_DBG, "Calling CTOR for ConditionStringLiteral"); |
| _literal = v; |
| } |
| |
| void |
| ConditionStringLiteral::append_value(std::string &s, const Resources &res) |
| { |
| s += _literal; |
| TSDebug(PLUGIN_NAME, "Appending '%s' to evaluation value", _literal.c_str()); |
| } |
| |
| bool |
| ConditionStringLiteral::eval(const Resources &res) |
| { |
| TSDebug(PLUGIN_NAME, "Evaluating StringLiteral"); |
| |
| return static_cast<const MatcherType *>(_matcher)->test(_literal); |
| } |
| |
| // ConditionSessionTransactCount |
| void |
| ConditionSessionTransactCount::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| MatcherType *match = new MatcherType(_cond_op); |
| std::string const &arg = p.get_arg(); |
| |
| match->set(strtol(arg.c_str(), nullptr, 10)); |
| _matcher = match; |
| } |
| |
| bool |
| ConditionSessionTransactCount::eval(const Resources &res) |
| { |
| int const val = TSHttpTxnServerSsnTransactionCount(res.txnp); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating SSN-TXN-COUNT()"); |
| return static_cast<MatcherType *>(_matcher)->test(val); |
| } |
| |
| void |
| ConditionSessionTransactCount::append_value(std::string &s, Resources const &res) |
| { |
| char value[32]; // enough for UINT64_MAX |
| int const count = TSHttpTxnServerSsnTransactionCount(res.txnp); |
| int const length = ink_fast_itoa(count, value, sizeof(value)); |
| |
| if (length > 0) { |
| TSDebug(PLUGIN_NAME, "Appending SSN-TXN-COUNT %s to evaluation value %.*s", _qualifier.c_str(), length, value); |
| s.append(value, length); |
| } |
| } |
| |
| void |
| ConditionTcpInfo::initialize(Parser &p) |
| { |
| Condition::initialize(p); |
| TSDebug(PLUGIN_NAME, "Initializing TCP Info"); |
| MatcherType *match = new MatcherType(_cond_op); |
| std::string const &arg = p.get_arg(); |
| |
| match->set(strtol(arg.c_str(), nullptr, 10)); |
| _matcher = match; |
| } |
| |
| void |
| ConditionTcpInfo::initialize_hooks() |
| { |
| add_allowed_hook(TS_HTTP_TXN_START_HOOK); |
| add_allowed_hook(TS_HTTP_TXN_CLOSE_HOOK); |
| add_allowed_hook(TS_HTTP_SEND_RESPONSE_HDR_HOOK); |
| } |
| |
| bool |
| ConditionTcpInfo::eval(const Resources &res) |
| { |
| std::string s; |
| |
| append_value(s, res); |
| bool rval = static_cast<const Matchers<std::string> *>(_matcher)->test(s); |
| |
| TSDebug(PLUGIN_NAME, "Evaluating TCP-Info: %s - rval: %d", s.c_str(), rval); |
| |
| return rval; |
| } |
| |
| void |
| ConditionTcpInfo::append_value(std::string &s, Resources const &res) |
| { |
| #if defined(TCP_INFO) && defined(HAVE_STRUCT_TCP_INFO) |
| if (TSHttpTxnIsInternal(res.txnp)) { |
| TSDebug(PLUGIN_NAME, "No TCP-INFO available for internal transactions"); |
| return; |
| } |
| TSReturnCode tsSsn; |
| int fd; |
| struct tcp_info info; |
| socklen_t tcp_info_len = sizeof(info); |
| tsSsn = TSHttpTxnClientFdGet(res.txnp, &fd); |
| if (tsSsn != TS_SUCCESS || fd <= 0) { |
| TSDebug(PLUGIN_NAME, "error getting the client socket fd from ssn"); |
| } |
| if (getsockopt(fd, IPPROTO_TCP, TCP_INFO, &info, &tcp_info_len) != 0) { |
| TSDebug(PLUGIN_NAME, "getsockopt(%d, TCP_INFO) failed: %s", fd, strerror(errno)); |
| } |
| |
| if (tsSsn == TS_SUCCESS) { |
| if (tcp_info_len > 0) { |
| char buf[12 * 4 + 9]; // 4x uint32's + 4x "; " + '\0' |
| #if !defined(freebsd) || defined(__GLIBC__) |
| snprintf(buf, sizeof(buf), "%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32 "", info.tcpi_rtt, info.tcpi_rto, |
| info.tcpi_snd_cwnd, info.tcpi_retrans); |
| #else |
| snprintf(buf, sizeof(buf), "%" PRIu32 ";%" PRIu32 ";%" PRIu32 ";%" PRIu32 "", info.tcpi_rtt, info.tcpi_rto, |
| info.tcpi_snd_cwnd, info.__tcpi_retrans); |
| #endif |
| s += buf; |
| } |
| } |
| #else |
| s += "-"; |
| #endif |
| } |