Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tests for #1061 and #1535 issue #195

Merged
merged 81 commits into from
Feb 14, 2022
Merged
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
81 commits
Select commit Hold shift + click to select a range
fd958ef
Added the 1st revision of the functional test for the #1061 issue
Dmitry-Gouriev Dec 3, 2021
5c22fb0
Added TiedStream class to helpers to get raw request text
Dmitry-Gouriev Dec 4, 2021
3d12e0a
(Intermediary commit) Fixed errors in helpers/. 6/10 tests passed.
Dmitry-Gouriev Dec 4, 2021
86e3961
Completed simpe (non heavy chanked) tests for #1061
Dmitry-Gouriev Dec 6, 2021
f89a901
Fix a bug
Dmitry-Gouriev Dec 6, 2021
9ebb9f3
Some refactoring
Dmitry-Gouriev Dec 6, 2021
347d8db
Formatting a little (backend descriptions)
Dmitry-Gouriev Dec 6, 2021
19c3504
Refinment to previous
Dmitry-Gouriev Dec 6, 2021
ece327c
Refinment to previous
Dmitry-Gouriev Dec 6, 2021
c8e03e4
Fix typo
Dmitry-Gouriev Dec 6, 2021
9f2015e
Remove some garbadge code
Dmitry-Gouriev Dec 6, 2021
6bc612c
Drafts for heavy chunked tests (preliminary commit)
Dmitry-Gouriev Dec 7, 2021
33e289b
Typo
Dmitry-Gouriev Dec 7, 2021
e283628
Draft 2 for heavy chunked
Dmitry-Gouriev Dec 7, 2021
69fed64
Remove fantasy which is ahead time
Dmitry-Gouriev Dec 7, 2021
f58373e
Completed heavy chunked tests for #1061 issue
Dmitry-Gouriev Dec 7, 2021
71df582
Promote "heavy chunked" parameters to client config
Dmitry-Gouriev Dec 7, 2021
c81130d
Added heavy chunked test for bug in ss_skb_chop_head_tail() mentioned…
Dmitry-Gouriev Dec 8, 2021
873b2ae
Update README.md
Dmitry-Gouriev Dec 8, 2021
a5386a3
Corrected misprint in README.md
Dmitry-Gouriev Dec 8, 2021
28cae11
Some useful renaming
Dmitry-Gouriev Dec 9, 2021
66e7b23
Merge branch 'dg-tests-1061-1535' of https://github.com/tempesta-tech…
Dmitry-Gouriev Dec 9, 2021
b23a961
Fixed Coding style & Copygight
Dmitry-Gouriev Dec 13, 2021
d5bd57f
Comparison of full requests in test_purge_hch.py
Dmitry-Gouriev Dec 13, 2021
4a06c57
Addition to previous
Dmitry-Gouriev Dec 13, 2021
911017c
Drop out TiedStream class
Dmitry-Gouriev Dec 13, 2021
8a5f396
Drop out computations of original length in HttpMessage class (helper…
Dmitry-Gouriev Dec 13, 2021
64d9f03
More style & Copyrights
Dmitry-Gouriev Dec 13, 2021
1f825e2
Addition to removal of computations of original length
Dmitry-Gouriev Dec 13, 2021
d8476d6
Added the test test_correct_headers.py - a chunking example
Dmitry-Gouriev Dec 20, 2021
87ef1fd
Some generalization/liberization of the example
Dmitry-Gouriev Dec 20, 2021
79838bd
Even more generalization + some docs
Dmitry-Gouriev Dec 20, 2021
1da77da
Add chunking functionality to deproxy server (need testing)
Dmitry-Gouriev Dec 22, 2021
af18235
Debugged chunking of server response.
Dmitry-Gouriev Dec 22, 2021
7e80c12
Update README.md
Dmitry-Gouriev Dec 23, 2021
b46fae9
Update README.md
Dmitry-Gouriev Dec 23, 2021
39e0016
Update README.md
Dmitry-Gouriev Dec 23, 2021
08c328b
1st draft for helpers/selfproxy.py
Dmitry-Gouriev Jan 24, 2022
3c0ba14
Syntax
Dmitry-Gouriev Jan 24, 2022
075a384
Formatting
Dmitry-Gouriev Jan 24, 2022
f41fda9
Intermediary backup for debugging files
Dmitry-Gouriev Jan 27, 2022
cd98e89
Added scapy to required modules
Dmitry-Gouriev Feb 1, 2022
a52c82a
Merge remote-tracking branch 'origin/master' into dg-tests-1061-1535
Dmitry-Gouriev Feb 1, 2022
30dd26f
Merge branch 'dg_check_dep' into dg-tests-1061-1535
Dmitry-Gouriev Feb 1, 2022
b2d41a4
Intermediary backup of debugging version
Dmitry-Gouriev Feb 3, 2022
be1a821
Clean from ready variable
Dmitry-Gouriev Feb 3, 2022
7588e02
Clean out debug prints
Dmitry-Gouriev Feb 3, 2022
7839395
Merge branch 'master' into dg-tests-1061-1535
Dmitry-Gouriev Feb 3, 2022
4214b65
Intermediary debug backup
Dmitry-Gouriev Feb 4, 2022
692b78c
connect TLS client via chunking proxy (draft)
Dmitry-Gouriev Feb 4, 2022
75ede2a
Debugged TLS chunking in deproxy client
Dmitry-Gouriev Feb 4, 2022
a22196d
Update doc in malformed/test_correct_headers.py
Dmitry-Gouriev Feb 7, 2022
dcb4b83
Update doc in malformed/test_malformed_crlfs.py
Dmitry-Gouriev Feb 7, 2022
64900d1
Rearranged the example, improved embedded doc
Dmitry-Gouriev Feb 7, 2022
477c1c9
Renamed the example file
Dmitry-Gouriev Feb 7, 2022
f61a2ba
rearrangement and small bugfix in deproxy_client
Dmitry-Gouriev Feb 7, 2022
a4479c0
Fixed minor comments
Dmitry-Gouriev Feb 7, 2022
2cd453d
Update README.md due to file renaming
Dmitry-Gouriev Feb 7, 2022
5b8e8fc
Copyright timestamp update
Dmitry-Gouriev Feb 7, 2022
3747ccd
Copyright timestamp update
Dmitry-Gouriev Feb 7, 2022
6e5f3e0
test_purge_hch.py moved to cache/
Dmitry-Gouriev Feb 8, 2022
6225df7
Minor improvements and renaming
Dmitry-Gouriev Feb 8, 2022
74bce1b
removed dbg print (forgotten)
Dmitry-Gouriev Feb 8, 2022
4e756d1
Added a test for #1535 chunked response
Dmitry-Gouriev Feb 8, 2022
047a9a0
remove some garbage
Dmitry-Gouriev Feb 8, 2022
a281df7
removed debug script for selfproxy
Dmitry-Gouriev Feb 8, 2022
71abe13
and fckn copyright timestamp
Dmitry-Gouriev Feb 8, 2022
f7ecaff
Fixed renaming error
Dmitry-Gouriev Feb 8, 2022
23dc64d
formatting + additional check
Dmitry-Gouriev Feb 8, 2022
237beaf
Fixed formatting and optimization comments
Dmitry-Gouriev Feb 9, 2022
b2558c9
Chunking examples for tls/ tests (draft)
Dmitry-Gouriev Feb 10, 2022
781ae79
Syntax
Dmitry-Gouriev Feb 10, 2022
078fb14
Syntax, comments++
Dmitry-Gouriev Feb 10, 2022
57e3884
Improve comments
Dmitry-Gouriev Feb 10, 2022
69c6ced
Chunking examples for tls/ tests - debugged
Dmitry-Gouriev Feb 10, 2022
073013b
Added modules to __init__.py files
Dmitry-Gouriev Feb 10, 2022
f78f77b
Merge branch 'master' into dg-tests-1061-1535
Dmitry-Gouriev Feb 10, 2022
0292412
Fix for failed connection bug (wrong bind())
Dmitry-Gouriev Feb 10, 2022
3cdc3b2
Another fix for failed connection bug (wrong bind())
Dmitry-Gouriev Feb 10, 2022
ebde26b
Fixed typo
Dmitry-Gouriev Feb 10, 2022
c0a0372
Remove debug print
Dmitry-Gouriev Feb 14, 2022
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
48 changes: 37 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -282,24 +282,40 @@ other options, depending on item type.

Now such backends are supported:
1) type == nginx
status_uri: uri where nginx status is located
config: nginx config
- status_uri: uri where nginx status is located
- config: nginx config

2) type == deproxy
port: listen this port
response: type of response. Now only 'static' is supported
response == static:
response_content: always response this content
- port: listen this port
- response: type of response. Now only 'static' is supported
- response == static:
- response_content: always response this content,
- keep_original_data: optional: if set to True,
the original request will be kept in Request.original_data field
as it has arrived by the wire,
otherwise (say, the parameter is not defined at all)
Request.original_data will be left blank
- segment_size: optional: TCP segment size for heavy chunked testing, bytes, 0 for disable
- segment_gap: optional: inter-segment gap for heavy chunked testing, ms, 0 for disable
- you usualy do not need it; update timeouts if you use it

and such clients:
1) type == wrk
addr: 'ip:port'
- addr: 'ip:port'

2) type == deproxy
addr: ip addr of server to connect
port: port

All options are mandatory
- addr: ip addr of server to connect
- port: port
- keep_original_data: optional: if set to True,
the original response will be kept in Response.original_data field
as it has arrived by the wire,
otherwise (say, the parameter is not defined at all)
Response.original_data will be left blank
- segment_size: optional: TCP segment size for heavy chunked testing, bytes, 0 for disable
- segment_gap: optional: inter-segment gap for heavy chunked testing, ms, 0 for disable
- you usualy do not need it; update timeouts if you use it

All options are mandatory, unless explicitly stated otherwise.
Dmitry-Gouriev marked this conversation as resolved.
Show resolved Hide resolved

nginx config, deproxy response, addr and port can use templates
in format `${part_variable}` where `part` is one of 'server',
Expand All @@ -313,6 +329,16 @@ Example tests can be found in `selftests/test_framework.py`
Tests can be skipped or marked as expected to fail.
More info at [Python documentation](https://docs.python.org/3/library/unittest.html).

### Testing with chunked messages

Some tests require division of request or response into small TCP segments ("chunks").
This division is controlled by segment_size parameter of the client or the backend
(see above). Usualy better to set this parameter programmaticaly rather than in client
or backend configuration.

An example to create tests which divide requests or responsies into chunks and
iterate over various chunk sizes is here: `malformed/test_chunking_example.py`.

## Internal structure and motivation of user configured tests

User configured tests have very flexible structure. They allow arbitrary
Expand Down
79 changes: 74 additions & 5 deletions framework/deproxy_client.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import abc
krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
import time
krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
import socket

from helpers import deproxy, tf_cfg, stateful
from helpers import deproxy, tf_cfg, stateful, selfproxy

__author__ = 'Tempesta Technologies, Inc.'
__copyright__ = 'Copyright (C) 2018-2019 Tempesta Technologies, Inc.'
__copyright__ = 'Copyright (C) 2018-2022 Tempesta Technologies, Inc.'
__license__ = 'GPL2'

class BaseDeproxyClient(deproxy.Client):
Expand All @@ -21,9 +22,29 @@ def __init__(self, *args, **kwargs):
self.rps = 0
self.valid_req_num = 0
self.cur_req_num = 0
# This parameter controls whether to keep original data with the response
# (See deproxy.HttpMessage.original_data)
self.keep_original_data = None
# Following 2 parameters control heavy chunked testing
# You can set it programmaticaly or via client config
# TCP segment size, bytes, 0 for disable, usualy value of 1 is sufficient
self.segment_size = 0
# Inter-segment gap, ms, 0 for disable.
# You usualy do not need it; update timeouts if you use it.
self.segment_gap = 0
# This state variable contains a timestamp of the last segment sent
self.last_segment_time = 0
# The following 2 variables are used to save destination address and port
# when overriding to connect via ssl chunking proxy
self.overriden_addr = None
self.overriden_port = None
# a presense of selfproxy
self.selfproxy_present = False

def handle_connect(self):
deproxy.Client.handle_connect(self)
if self.segment_size and not self.selfproxy_present:
self.socket.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
self.start_time = time.time()

def set_events(self, polling_lock):
Expand All @@ -34,12 +55,13 @@ def set_rps(self, rps):

def __stop_client(self):
tf_cfg.dbg(4, '\tStop deproxy client')
self.release_selfproxy()
if self.polling_lock != None:
self.polling_lock.acquire()
try:
self.close()
except Exception as e:
tf_cfg.dbg(2, "Exception while start: %s" % str(e))
tf_cfg.dbg(2, "Exception while stop: %s" % str(e))
if self.polling_lock != None:
self.polling_lock.release()
raise e
Expand All @@ -54,6 +76,8 @@ def run_start(self):
self.start_time = 0
self.valid_req_num = 0
self.cur_req_num = 0
if self.ssl and self.segment_size != 0:
self.insert_selfproxy()
if self.polling_lock != None:
self.polling_lock.acquire()
try:
Expand All @@ -76,7 +100,8 @@ def handle_read(self):
try:
method = self.methods[self.nrresp]
response = deproxy.Response(self.response_buffer,
method=method)
method=method,
keep_original_data=self.keep_original_data)
self.response_buffer = \
self.response_buffer[response.original_length:]
except deproxy.IncompleteMessage:
Expand All @@ -99,6 +124,9 @@ def writable(self):
return False
if time.time() < self.next_request_time():
return False
if (self.segment_gap != 0 and not self.selfproxy_present and
time.time() - self.last_segment_time < self.segment_gap / 1000.0):
return False;
return True

def next_request_time(self):
Expand All @@ -110,9 +138,13 @@ def handle_write(self):
reqs = self.request_buffers
tf_cfg.dbg(4, '\tDeproxy: Client: Send request to Tempesta.')
tf_cfg.dbg(5, reqs[self.cur_req_num])
sent = self.send(reqs[self.cur_req_num])
if self.segment_size != 0 and not self.selfproxy_present:
sent = self.send(reqs[self.cur_req_num][:self.segment_size])
else:
sent = self.send(reqs[self.cur_req_num])
if sent < 0:
return
self.last_segment_time = time.time()
reqs[self.cur_req_num] = reqs[self.cur_req_num][sent:]
if len(reqs[self.cur_req_num]) == 0:
self.cur_req_num += 1
Expand All @@ -121,6 +153,8 @@ def make_requests(self, requests):
request_buffers = []
methods = []
valid_req_num = 0
if self.selfproxy_present:
self.update_selfproxy()
while len(requests) > 0:
try:
req = deproxy.Request(requests)
Expand Down Expand Up @@ -160,6 +194,41 @@ def make_request(self, request):
def receive_response(self, response):
raise NotImplementedError("Not implemented 'receive_response()'")

def insert_selfproxy(self):
# inserting the chunking proxy between ssl client and server
if not self.ssl or self.segment_size == 0:
return
selfproxy.request_client_selfproxy(
listen_host = "127.0.0.1",
listen_port = selfproxy.CLIENT_MODE_PORT_REPLACE,
forward_host = self.conn_addr,
forward_port = self.port,
segment_size = self.segment_size,
segment_gap = self.segment_gap)
self.overriden_addr = self.conn_addr
self.overriden_port = self.port
self.conn_addr = "127.0.0.1"
self.port = selfproxy.CLIENT_MODE_PORT_REPLACE
self.selfproxy_present = True

def release_selfproxy(self):
# action reverse to insert_selfproxy
if self.selfproxy_present:
selfproxy.release_client_selfproxy()
self.selfproxy_present = False
if self.overriden_addr is not None:
self.conn_addr = self.overriden_addr
self.overriden_addr = None
if self.overriden_port is not None:
self.port = self.overriden_port
self.overriden_port = None

def update_selfproxy(self):
# update chunking parameters
if self.selfproxy_present:
selfproxy.update_client_selfproxy_chunking(
self.segment_size, self.segment_gap)


class DeproxyClient(BaseDeproxyClient):
last_response = None
Expand Down
43 changes: 39 additions & 4 deletions framework/deproxy_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import port_checks

__author__ = 'Tempesta Technologies, Inc.'
__copyright__ = 'Copyright (C) 2018 Tempesta Technologies, Inc.'
__copyright__ = 'Copyright (C) 2018-2021 Tempesta Technologies, Inc.'
__license__ = 'GPL2'

krizhanovsky marked this conversation as resolved.
Show resolved Hide resolved
class ServerConnection(asyncore.dispatcher_with_send):
Expand All @@ -21,23 +21,36 @@ def __init__(self, server, sock=None, keep_alive=None):
asyncore.dispatcher_with_send.__init__(self, sock)
self.server = server
self.keep_alive = keep_alive
self.last_segment_time = 0
self.responses_done = 0
self.request_buffer = ''
tf_cfg.dbg(6, '\tDeproxy: SrvConnection: New server connection.')

def initiate_send(self):
""" Override dispatcher_with_send.initiate_send() which transfers
data with too small chunks of 512 bytes.
However if server.segment_size is set (!=0), use this value.
"""
num_sent = 0
num_sent = asyncore.dispatcher.send(self, self.out_buffer[:4096])
num_sent = asyncore.dispatcher.send(self, self.out_buffer[:
self.server.segment_size
if self.server.segment_size > 0
else 4096 ])
self.out_buffer = self.out_buffer[num_sent:]
self.last_segment_time = time.time()

def send_pending_and_close(self):
while len(self.out_buffer):
self.initiate_send()
self.handle_close()

def writable(self):
if ( self.server.segment_gap != 0 and
time.time() - self.last_segment_time
< self.server.segment_gap / 1000.0 ):
return False;
return asyncore.dispatcher_with_send.writable(self)

def send_response(self, response):
if response:
tf_cfg.dbg(4, '\tDeproxy: SrvConnection: Send response.')
Expand Down Expand Up @@ -66,7 +79,9 @@ def handle_close(self):
def handle_read(self):
self.request_buffer += self.recv(deproxy.MAX_MESSAGE_SIZE)
try:
request = deproxy.Request(self.request_buffer)
request = deproxy.Request(self.request_buffer,
keep_original_data =
self.server.keep_original_data)
except deproxy.IncompleteMessage:
return
except deproxy.ParseError:
Expand All @@ -89,6 +104,18 @@ def handle_read(self):
class BaseDeproxyServer(deproxy.Server, port_checks.FreePortsChecker):

def __init__(self, *args, **kwargs):
# This parameter controls whether to keep original data with the request
# (See deproxy.HttpMessage.original_data)
self.keep_original_data = kwargs.pop("keep_original_data", None)

# Following 2 parameters control heavy chunked testing
# You can set it programmaticaly or via client config
# TCP segment size, bytes, 0 for disable, usualy value of 1 is sufficient
self.segment_size = kwargs.pop("segment_size", 0)
# Inter-segment gap, ms, 0 for disable.
# You usualy do not need it; update timeouts if you use it.
self.segment_gap = kwargs.pop("segment_gap", 0)

deproxy.Server.__init__(self, *args, **kwargs)
self.stop_procedures = [self.__stop_server]
self.is_polling = threading.Event()
Expand All @@ -99,6 +126,8 @@ def handle_accept(self):
pair = self.accept()
if pair is not None:
sock, _ = pair
if self.segment_size:
sock.setsockopt(socket.SOL_TCP, socket.TCP_NODELAY, 1)
handler = ServerConnection(server=self, sock=sock,
keep_alive=self.keep_alive)
self.connections.append(handler)
Expand Down Expand Up @@ -187,10 +216,16 @@ def deproxy_srv_factory(server, name, tester):
else:
port = int(port)
srv = None
ko = server.get("keep_original_data", None)
ss = server.get("segment_size", 0)
sg = server.get("segment_gap", 0)
rtype = server['response']
if rtype == 'static':
content = fill_template(server['response_content'], server)
srv = StaticDeproxyServer(port=port, response=content)
srv = StaticDeproxyServer(port=port, response=content,
keep_original_data = ko,
segment_size = ss,
segment_gap = sg)
else:
raise Exception("Invalid response type: %s" % str(rtype))

Expand Down
5 changes: 4 additions & 1 deletion framework/tester.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import struct

__author__ = 'Tempesta Technologies, Inc.'
__copyright__ = 'Copyright (C) 2018-2019 Tempesta Technologies, Inc.'
__copyright__ = 'Copyright (C) 2018-2021 Tempesta Technologies, Inc.'
__license__ = 'GPL2'

backend_defs = {}
Expand Down Expand Up @@ -144,6 +144,9 @@ def __create_client_deproxy(self, client, ssl, bind_addr):
# the client configuration.
server_hostname = fill_template(client['ssl_hostname'], client)
clt.set_server_hostname(server_hostname)
clt.segment_size = int(client.get('segment_size', 0))
clt.segment_gap = int(client.get('segment_gap', 0))
clt.keep_original_data = bool(client.get('keep_original_data', None))
return clt

def __create_client_wrk(self, client, ssl):
Expand Down
Loading