Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions iocore/net/P_SSLNetVConnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,13 @@ class SSLNetVConnection : public UnixNetVConnection
*/
int populate(Connection &con, Continuation *c, void *arg) override;

void
setSSLVerifyCallback(void *callback, void *args)
{
sslVerifyCallback = callback;
sslVerifyCallbackArgs = args;
}

SSL *ssl = nullptr;
ink_hrtime sslHandshakeBeginTime = 0;
ink_hrtime sslHandshakeEndTime = 0;
Expand Down Expand Up @@ -352,9 +359,13 @@ class SSLNetVConnection : public UnixNetVConnection
bool sslTrace = false;
bool SNIMapping = false;
int64_t redoWriteSize = 0;
void *sslVerifyCallback = nullptr;
void *sslVerifyCallbackArgs = nullptr;
#ifdef SSL_MODE_ASYNC
EventIO signalep;
#endif

static int sslVerifyCallbackProxy(int preverify_ok, X509_STORE_CTX *x509_ctx);
};

typedef int (SSLNetVConnection::*SSLNetVConnHandler)(int, void *);
Expand Down
17 changes: 17 additions & 0 deletions iocore/net/SSLNetVConnection.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1036,6 +1036,10 @@ SSLNetVConnection::sslStartHandShake(int event, int &err)
}
}

if (this->sslVerifyCallback != nullptr) {
SSL_set_verify(this->ssl, SSL_VERIFY_PEER, sslVerifyCallbackProxy);
}

return sslClientHandShakeEvent(err);

default:
Expand Down Expand Up @@ -1710,6 +1714,19 @@ SSLNetVConnection::populate(Connection &con, Continuation *c, void *arg)
return EVENT_DONE;
}

int
SSLNetVConnection::sslVerifyCallbackProxy(int preverify_ok, X509_STORE_CTX *x509_ctx)
{
SSL *ssl = static_cast<SSL *>(X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()));
SSLNetVConnection *netvc = SSLNetVCAccess(ssl);
if (netvc == nullptr) {
return preverify_ok;
} else {
return reinterpret_cast<int (*)(void *, int, X509_STORE_CTX *x509_ctx)>(netvc->sslVerifyCallback)(netvc->sslVerifyCallbackArgs,
preverify_ok, x509_ctx);
}
}

std::string_view
SSLNetVConnection::map_tls_protocol_to_tag(const char *proto_string) const
{
Expand Down
3 changes: 3 additions & 0 deletions proxy/api/ts/ts.h
Original file line number Diff line number Diff line change
Expand Up @@ -1508,6 +1508,7 @@ tsapi void TSHttpTxnArgSet(TSHttpTxn txnp, int arg_idx, void *arg);
tsapi void *TSHttpTxnArgGet(TSHttpTxn txnp, int arg_idx);
tsapi void TSHttpSsnArgSet(TSHttpSsn ssnp, int arg_idx, void *arg);
tsapi void *TSHttpSsnArgGet(TSHttpSsn ssnp, int arg_idx);
tsapi bool TSVConnVerifyCallbackSet(TSVConn connp, void *callback, void *args);
tsapi void TSVConnArgSet(TSVConn connp, int arg_idx, void *arg);
tsapi void *TSVConnArgGet(TSVConn connp, int arg_idx);

Expand Down Expand Up @@ -2450,6 +2451,8 @@ tsapi TSReturnCode TSRemapToUrlGet(TSHttpTxn txnp, TSMLoc *urlLocp);
*/
tsapi TSIOBufferReader TSHttpTxnPostBufferReaderGet(TSHttpTxn txnp);

tsapi TSVConn TSHttpTxnServerVConnGet(TSHttpTxn txnp);

#ifdef __cplusplus
}
#endif /* __cplusplus */
40 changes: 30 additions & 10 deletions src/traffic_server/InkAPI.cc
Original file line number Diff line number Diff line change
Expand Up @@ -5410,16 +5410,7 @@ TSHttpTxnIncomingAddrGet(TSHttpTxn txnp)
sockaddr const *
TSHttpTxnOutgoingAddrGet(TSHttpTxn txnp)
{
sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);

HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);

HttpServerSession *ssn = sm->get_server_session();
if (ssn == nullptr) {
return nullptr;
}

NetVConnection *vc = ssn->get_netvc();
NetVConnection *vc = reinterpret_cast<NetVConnection *>(TSHttpTxnServerVConnGet(txnp));
if (vc == nullptr) {
return nullptr;
}
Expand Down Expand Up @@ -9657,3 +9648,32 @@ TSHttpTxnPostBufferReaderGet(TSHttpTxn txnp)
HttpSM *sm = (HttpSM *)txnp;
return (TSIOBufferReader)sm->get_postbuf_clone_reader();
}

bool
TSVConnVerifyCallbackSet(TSVConn connp, void *callback, void *args)
{
sdk_assert(sdk_sanity_check_iocore_structure(connp) == TS_SUCCESS);

NetVConnection *net_vc = reinterpret_cast<NetVConnection *>(connp);
SSLNetVConnection *ssl_vc = dynamic_cast<SSLNetVConnection *>(net_vc);
if (ssl_vc != nullptr) {
ssl_vc->setSSLVerifyCallback(callback, args);
return true;
}
return false;
}

TSVConn
TSHttpTxnServerVConnGet(TSHttpTxn txnp)
{
sdk_assert(sdk_sanity_check_txn(txnp) == TS_SUCCESS);

HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);

HttpServerSession *ssn = sm->get_server_session();
if (ssn == nullptr) {
return nullptr;
}

return reinterpret_cast<TSVConn>(ssn->get_netvc());
}
2 changes: 1 addition & 1 deletion tests/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ tr.Processes.Default.Env=ts.Env
These are the current variable that are define dynamically

port - the ipv4 port to listen on
portv6 - the ipv4 port to listen on
portv6 - the ipv6 port to listen on
manager_port - the manager port used. This is set even is select_port is False
admin_port - the admin port used. This is set even is select_port is False

Expand Down
27 changes: 27 additions & 0 deletions tests/gold_tests/tls/ssl/ca.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEAvf/vbv7bSVXx3xL6JghGxEFh4BenkFT3m3LIHwYziFDEp3Ae
A4Q83fja8sdeEc/HA0vYxgjgEwMX+3cDvDIwfcJD7QaZ7ddhaazLfsGjqN76sOjy
Pt5Y+m5++MvSy8Q8T39YNwvdIqorUElx+ttoUstEUXjFHCgHHOLNHdeHFn+R+gIb
gSZgkGYe6xLGs8UD1cTnEjQOZ17jc3Y9grYx1DJ0heAARFZVyOd9k13RbV1UV2NY
c2xI1i+a5zYvcJpGY921cb8zowCIYD4rg6Ef97oU6RgCzST57xRicOwtu+78Zv28
qfJa582zIoH+wFvOMR8G4G+03mVt2uyHM+4giwIDAQABAoIBABXWEEKFb9Zh1kZ4
+97bHKb5jLIghdWErmBjNLS8FbgY8ED+FbT5wbURILi+SQVrB8hNM0CvTHFi7aKs
7vT2H7SUWxImrBI1qrBmd4y93H1QAyfJ3e1zyG4fvOADLNrWVsgcLptE9clCHXBA
wKTACqFZUyS56I1v3EFpn4FKf/AJzkEFxuICGLQZ+leR8pDA6hEuG9UE2uTI00Dp
oFdO9oVd4Z59MslZB3rGYZkE7rXB7n/LMyOUAfad2oAwzIY6P6KMnO6/6dOUu4GL
XEM7PS1Q6vmcl1iqQGln2RyYsRIuLYIBzsyzTPm44XQFRL2QzDfCGsWPDmlZQ1fu
1BThkiECgYEA+MyBmaUmdgfb79XkMyeygfXK5GGt/u/KVpKrIGMNOwlIs6N4p5Bo
eoq0BIcIDcDM/6tON3d4d+rEyIfbq4N88rJmM2CziUmtj8DTAh2/AScH3gQlrD10
fxsvFRqP9SlcER1IJALypg3R+lDcRdl+do6BpNeyKqhQdZLXhQnYFPUCgYEAw3/A
ri/8SginDxTDpP5KbJhSwOr10SUQ8j17z3Zr/qqtslMmLniGGJCGhxJd7kWgF7Tc
m8qhYELXaRM+WUUeNx2tXPkWy0RM4mKM0RODNgtbVJUFVspd62ieqCOWn8ymx2PV
7P57OyN4jhlWklSpnpV7/QhFOOW+nbIwEAqf738CgYBFvfX6XtwYK/3QDjIo0NDu
MztLp8IBe7BJclW7/2gfC2wwxvs0ZXuUFjt+JWfxiwEsDhYf4EKJwuqkl8jRHEuz
MKULc+j/7AJWEKRVrIb1SL6g0qLI1HVKiBE7pjWCcLSZK48kYRspxyf8Nhx5DA2y
KCJZVglggcwEGUSGy4qh3QKBgQC/YSSiYF89qlLANLeBoV4CCOuZ52lUoFCCkyXM
5kS6PSNk2nkaLOo4yeKn/5bsVkRucKM175sivMxbFHeItFzr36WCymVB/060n3zy
f4OHOH7jwi5D1D804GnQd2ZQcOsjbdxR8J16OzMdCDjzbuXNVNDX12zMUjvT7EeY
rI38yQKBgQCTvk/2HjoJ1uLmdPo7BYyIqLuXlhHmpO23gI/U6scFJrLJatkrmU6s
eVOmd8N5Y7q9hBPKkKOPmZZ21KfTkN8nffNDPpRqH3fMpGHDogEN33A4r23uuJs3
7+GwA182xqQl3JGOOWJkY8fK36154mawZRj06a9Ytseg2CDoHaWcsg==
-----END RSA PRIVATE KEY-----
20 changes: 20 additions & 0 deletions tests/gold_tests/tls/ssl/ca.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDOzCCAiOgAwIBAgIJAMj4prNPmwCCMA0GCSqGSIb3DQEBCwUAMDQxCzAJBgNV
BAYTAlhYMRAwDgYDVQQHDAdDQSBDaXR5MRMwEQYDVQQKDApDQSBDb21wYW55MB4X
DTE4MDcyNjAzNTUxMloXDTI4MDcyMzAzNTUxMlowNDELMAkGA1UEBhMCWFgxEDAO
BgNVBAcMB0NBIENpdHkxEzARBgNVBAoMCkNBIENvbXBhbnkwggEiMA0GCSqGSIb3
DQEBAQUAA4IBDwAwggEKAoIBAQC9/+9u/ttJVfHfEvomCEbEQWHgF6eQVPebcsgf
BjOIUMSncB4DhDzd+Nryx14Rz8cDS9jGCOATAxf7dwO8MjB9wkPtBpnt12FprMt+
waOo3vqw6PI+3lj6bn74y9LLxDxPf1g3C90iqitQSXH622hSy0RReMUcKAcc4s0d
14cWf5H6AhuBJmCQZh7rEsazxQPVxOcSNA5nXuNzdj2CtjHUMnSF4ABEVlXI532T
XdFtXVRXY1hzbEjWL5rnNi9wmkZj3bVxvzOjAIhgPiuDoR/3uhTpGALNJPnvFGJw
7C277vxm/byp8lrnzbMigf7AW84xHwbgb7TeZW3a7Icz7iCLAgMBAAGjUDBOMB0G
A1UdDgQWBBRIzBkqtZ9A9gkzSahu45SNgKFJhTAfBgNVHSMEGDAWgBRIzBkqtZ9A
9gkzSahu45SNgKFJhTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCF
fJmwg4/mu7j81ULV0bB3GcswPNyfPlDEcTvHGyv1vZoW2NchdQdk7n6CWPs1bwmt
uuQB+gv3+UGcz/IXLgRKCNNVFO3p1Iy01VLvhvRMZbiM7tPkToBX1HFm+x3bu1rB
Rle5BMCS/Rv0C2IijBYIQ0tg2Cm8JrmZhW7PSIglxHwIiCZjYLHtMzqQHe2BtYjZ
nlZRLM1dG41urUeZR9XRTZW3MChGNYVnOCLItaefTaVORwNKz8XEa/N84ONI5RxN
yd0+DhghNiVrABBhK798aZGxGRIvRSjPPD0bCWXuSNAN87XE6SojBtGMVV4lUFX8
tSdQSo1b5sYFqx0sK3jX
-----END CERTIFICATE-----
27 changes: 27 additions & 0 deletions tests/gold_tests/tls/ssl/server.ats.test.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAsbtcU6kGr7Oyt/b6aQwvMnblFTozICKuQOo0xHzEled1PKek
4SPLQj/vvFhjyGybkZKSZq1XCwtRoASU3wr5iROWxE6i0Ah334/h48y9I9SqaaRv
tYqceLW2Xf4OBMePF15Zh39G78mgwzYqEhkdy6VsgdpJ1uU35+B5lddLpqc07kA3
jgSm0ynI6oM/h4E5d53VQm1YftZvN6gtDmhTD8/Cv5Gbgq050ToJbulL0q/S+JPM
90h7Mm5BmMOnG9RSAmlmfil1jozqc9PgSZcZsAfmAXVIeSxieeE57RHz/I0l+cO2
823prtgYwTaY74/2GDhXNrkExnnfYKkFqhzKswIDAQABAoIBAFSGhqJDAKsPCy/y
haTv5oJZiRlPVIEz/StiNzY7IEMqkNcH46IvI7ueLKWmY84t2M6ipn2ucncOKwOv
Kh3pWkOWzV0PCN9nJ5trEkMEW+9udJ1JVtc/1KqTwOdyq7gaMDMs6+dE4LepbItq
dxekWkc/pRscdYF+AdiFKU3Z1mCq2cPtxnyw94VcFE8MIPbuxRc+RQRgyvos+BkR
GegTvrVZLy9XUZ1UPMycghQCJFtG59iBfeGKXWH0nW6TtAy31yglxnsz0jYfnnG4
IsNEC8G5wSNRqxzIZmtRdH64L0xfU6nPMWATyA9G+aO5Mbtl9K82Y0CNbIdp14nd
cbk3+AECgYEA3BrMJJ5aSJ1UWmr6yKhEXF0nP+Wj4AHDk7OPuqoHInjxKLvdaPji
9zbeGz34c9p+pK4iZfldTBboJZ0D4J/oD6jttpA6pwRBML6so0d2l6gyt3y9IRqy
Yd2rp8fzGeu3BkctCtrsXwk6NMmA4vM33Zy/FZ/ZskhUGDhSOo0x2JECgYEAzreH
9roeHJTRioetZHLSL5VxGDtcAXdbgyeAos89psowTj+TmSL5WwoeToP2KbtikWJI
vfcEbMWxOBST3Eu6Bq+3wYgACyxRVr1g5Lvk+/Ah59cYO30RXkekwEVYTBkggWWO
Cc1LEXSbMF1FkUhfD19Cb2WV4lq+fjDXF6ybsQMCgYBraTBiVlycvxphHX/Q2Dy0
DkmPJxO3x2SX+bKUV1EwA3E8faFpDrxEBCSTisRqVSAkYU4sEWr0LOGRKXSw85gd
vER1KSFWS/88OHCrJb6797r4TAlH1G1k0PZp5GvAJIWmrKlQeNGM90XOaN9a4XJL
1ahydxWXqTBQuXt3U1wV4QKBgEhFsHqZjaeOfEhDsWKaJlROmlDPiBZXJZ6QNBA4
BqUGsabZ4X+705R50CjBSFJE2mxcAXSxJVHmOFSUFTIDI9Hso4E9TE6buwBpi7PM
4X0X8rK86BL8YhMlVzuFwx4v9sZmvCc/KEy9CPj60zS9KccVFgQeby3JbipuZvU2
+uQDAoGAddPMlNKsBxEvjMzEHWzM0XaTYTG5dES92bb5B5PNabQDQZV+q5NkPmzT
OeRMYvragztATaKPdcp+xoKUoKzNi2+SlVBr20HFtl6hXBCYL/ipK/eZAYWoRqbe
LEsqpI8pAmvhoWeZ8Ux9Rw7d68126ydU4C40Tyvgtn18BJHUblE=
-----END RSA PRIVATE KEY-----
20 changes: 20 additions & 0 deletions tests/gold_tests/tls/ssl/server.ats.test.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIJAO7Xyt9kZGa1MA0GCSqGSIb3DQEBCwUAMDQxCzAJBgNV
BAYTAlhYMRAwDgYDVQQHDAdDQSBDaXR5MRMwEQYDVQQKDApDQSBDb21wYW55MB4X
DTE4MDcyNjA1MTA0NloXDTI4MDcyMzA1MTA0NlowPDELMAkGA1UEBhMCWFgxFDAS
BgNVBAcMC1NlcnZlciBDaXR5MRcwFQYDVQQKDA5TZXJ2ZXIgQ29tcGFueTCCASIw
DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALG7XFOpBq+zsrf2+mkMLzJ25RU6
MyAirkDqNMR8xJXndTynpOEjy0I/77xYY8hsm5GSkmatVwsLUaAElN8K+YkTlsRO
otAId9+P4ePMvSPUqmmkb7WKnHi1tl3+DgTHjxdeWYd/Ru/JoMM2KhIZHculbIHa
SdblN+fgeZXXS6anNO5AN44EptMpyOqDP4eBOXed1UJtWH7WbzeoLQ5oUw/Pwr+R
m4KtOdE6CW7pS9Kv0viTzPdIezJuQZjDpxvUUgJpZn4pdY6M6nPT4EmXGbAH5gF1
SHksYnnhOe0R8/yNJfnDtvNt6a7YGME2mO+P9hg4Vza5BMZ532CpBaocyrMCAwEA
AaNXMFUwHwYDVR0jBBgwFoAUSMwZKrWfQPYJM0mobuOUjYChSYUwCQYDVR0TBAIw
ADALBgNVHQ8EBAMCBPAwGgYDVR0RBBMwEYIPc2VydmVyLmF0cy50ZXN0MA0GCSqG
SIb3DQEBCwUAA4IBAQAgdSfs8wXGQPkiIm/BwRE/nr38RXSmh2I5zOOu5c54Y/0S
m2lDJI1ZX3U++asdydtODRFZ9mEK4hCcRCYstKVymAQHyXVnqoKll0YDzKXspL1a
+FmDE2B8yKp7punnAWJ48TyoxPmZRLEAojcnucIfbXzLbQT/jkJGclPeZ8VpZwcz
MtI1bh6xmB3PVFcGwOtOTFO6iU2hFBUG8CwLfJM4JJw+UM6AFe8c51SPPTtuAFJj
dULxa8/cfJa3x8CAfVhU13erM9p8euN+if7N5WJsNbHQ+eFN8A5EtAvboZd9vHrD
GLyvRjbrqjknHQldw+Oqs6+bHahYtRq+V9bWnvT/
-----END CERTIFICATE-----
88 changes: 88 additions & 0 deletions tests/gold_tests/tls/ssl_verify.test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# 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.

import os

Test.Summary = '''
Test SSL verify callback by checking DNS record in Subject Alternative Name
'''

Test.SkipUnless(
Condition.HasProgram('curl', 'Curl need to be installed on system for this test to work')
)

SERVER_NAME_IN_CERT = 'server.ats.test'
# the synthetic server has different domain name, which mismatches the DNS record in the server cert
SYNTH_SERVER_NAME = 'synthetic.ats.test'

ts = Test.MakeATSProcess('ts')
server = Test.MakeOriginServer("server", ssl=True, options={
'--cert': '{0}/{1}.pem'.format(ts.Variables.SSLDir, SERVER_NAME_IN_CERT),
'--key': '{0}/{1}.key'.format(ts.Variables.SSLDir, SERVER_NAME_IN_CERT),
})

request_header = {"headers": "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n", "timestamp": "1469733493.993"}
response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993"}
server.addResponse("sessionfile.log", request_header, response_header)

ts.addSSLfile("ssl/ca.pem")
ts.addSSLfile("ssl/ca.key")
ts.addSSLfile("ssl/{0}.pem".format(SERVER_NAME_IN_CERT))
ts.addSSLfile("ssl/{0}.key".format(SERVER_NAME_IN_CERT))

dns = Test.MakeDNServer("dns")
dns.addRecords(records = {
SERVER_NAME_IN_CERT: ['127.0.0.1'],
SYNTH_SERVER_NAME: ['127.0.0.1'],
})

ts.Disk.records_config.update({
# reduce the number of unnecessary retries in negative case
'proxy.config.http.connect_attempts_max_retries': 0,
# CA cert is required to pass the positive case
'proxy.config.ssl.client.CA.cert.path': '{}'.format(ts.Variables.SSLDir),
'proxy.config.ssl.client.CA.cert.filename': 'ca.pem',
'proxy.config.dns.nameservers': '127.0.0.1:{0}'.format(dns.Variables.Port),
'proxy.config.dns.resolv_conf': 'NULL',
})

ts.Disk.remap_config.AddLine(
'map /positive https://{0}:{1} @plugin=ssl_verify_remap.so @pparam={0}'.format(SERVER_NAME_IN_CERT, server.Variables.Port)
)
ts.Disk.remap_config.AddLine(
'map /negative https://{0}:{1} @plugin=ssl_verify_remap.so @pparam={0}'.format(SYNTH_SERVER_NAME, server.Variables.Port)
)

ts.Disk.diags_log.Content = Testers.ContainsExpression('ERROR:', 'SSL3_GET_SERVER_CERTIFICATE:certificate verify failed')

Test.PreparePlugin(os.path.join(Test.Variables.AtsTestToolsDir, 'plugins', 'ssl_verify_remap.cc'), ts)

tr = Test.AddTestRun('Positive')
tr.Processes.Default.StartBefore(Test.Processes.ts)
tr.Processes.Default.StartBefore(server, ready=When.PortOpen(server.Variables.Port))
tr.Processes.Default.StartBefore(dns)
tr.Processes.Default.Command = 'curl -sf -o /dev/null http://127.0.0.1:{0}/positive'.format(ts.Variables.port)
tr.Processes.Default.ReturnCode = 0
tr.StillRunningAfter = ts
tr.StillRunningAfter = server
tr.StillRunningAfter = dns

tr = Test.AddTestRun('Negative')
tr.Processes.Default.Command = 'curl -sf -o /dev/null http://127.0.0.1:{0}/negative'.format(ts.Variables.port)
tr.Processes.Default.ReturnCode = 22
tr.StillRunningAfter = ts
tr.StillRunningAfter = server
tr.StillRunningAfter = dns
Loading