feature: Support verify shaping DSCP value.
This commit is contained in:
@@ -6,7 +6,7 @@ ADD dign_client /opt/dign_client
|
||||
|
||||
RUN sed -i s@/dl-cdn.alpinelinux.org/@/mirrors.ustc.edu.cn/@g /etc/apk/repositories \
|
||||
&& apk update \
|
||||
&& apk add curl-dev gcc libc-dev curl gzip \
|
||||
&& apk add curl-dev gcc libc-dev curl gzip libpcap-dev\
|
||||
&& pip3 install pycurl \
|
||||
&& pip3 install httpstat \
|
||||
&& pip3 install CIUnitTest \
|
||||
@@ -14,6 +14,7 @@ RUN sed -i s@/dl-cdn.alpinelinux.org/@/mirrors.ustc.edu.cn/@g /etc/apk/repositor
|
||||
&& pip3 install dnspython \
|
||||
&& pip3 install prettytable \
|
||||
&& pip3 install pyyaml \
|
||||
&& pip3 install scapy \
|
||||
&& mv /opt/dign_client/etc/client.conf /opt/dign_client/etc/client.conf.sample
|
||||
|
||||
WORKDIR /opt/dign_client
|
||||
|
||||
@@ -20,6 +20,13 @@ import logging
|
||||
import copy
|
||||
from prettytable import PrettyTable,NONE,HEADER
|
||||
import yaml
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from scapy.all import *
|
||||
from scapy.layers.inet import IP, TCP
|
||||
from scapy.sendrecv import AsyncSniffer
|
||||
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
class ConfigLoader:
|
||||
DEFAULT_CONFIGS = {
|
||||
@@ -326,43 +333,93 @@ class CommandParser:
|
||||
|
||||
class ServerAddressBuilder:
|
||||
IPv4_4TH_OCTET_LEFT_EDGE = 101
|
||||
DOMAIN_TO_PORT_LIST = [
|
||||
('sha384.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('sha256.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('expired.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('self-signed.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('untrusted-root.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('web-replay.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('web-replay.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('testing-download.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('http.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('http-credit-card.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('http-dynamic-login.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('http-login.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('sha512.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('rsa2048.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('rsa4096.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('testing-firewall-filter-host.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('testing-firewall-filter-url.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('testing-proxy-filter-host.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('testing-proxy-filter-url.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('testing-rate-limit-0bps.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('testing-rate-limit-0bps.badssl.selftest.gdnt-cloud.website', 443),
|
||||
('testing-rate-limit-1000gbps.badssl.selftest.gdnt-cloud.website', 80),
|
||||
('testing-rate-limit-1000gbps.badssl.selftest.gdnt-cloud.website', 443)
|
||||
]
|
||||
IPv4_1_TO_3TH_OCTET = "192.0.2"
|
||||
|
||||
def __init__(self, service_function_index: int):
|
||||
self._service_function_index = service_function_index
|
||||
self._ipv4_4th_octet = self.IPv4_4TH_OCTET_LEFT_EDGE + self._service_function_index
|
||||
|
||||
@property
|
||||
def resolves(self) -> list:
|
||||
return [f"{domain}:{port}:192.0.2.{self._ipv4_4th_octet}" for domain, port in self.DOMAIN_TO_PORT_LIST]
|
||||
def read_resolves_by_url(self, url):
|
||||
parsed_url = urlparse(url)
|
||||
port = parsed_url.port
|
||||
if not port:
|
||||
if parsed_url.scheme == 'https':
|
||||
port = 443
|
||||
elif parsed_url.scheme == 'http':
|
||||
port = 80
|
||||
return [f"{parsed_url.hostname}:{port}:{self.IPv4_1_TO_3TH_OCTET}.{self._ipv4_4th_octet}"]
|
||||
|
||||
|
||||
# @property
|
||||
# def resolves(self) -> list:
|
||||
# return [f"{domain}:{port}:192.0.2.{self._ipv4_4th_octet}" for domain, port in self.DOMAIN_TO_PORT_LIST]
|
||||
|
||||
@property
|
||||
def nameservers(self) -> list:
|
||||
return ['192.0.2.%d' % self._ipv4_4th_octet]
|
||||
return [f'{self.IPv4_1_TO_3TH_OCTET}.{self._ipv4_4th_octet}']
|
||||
|
||||
class TcpPacketsCapture:
|
||||
IFACE = "net1"
|
||||
DSCP_KEY = ["DSCP"]
|
||||
def __init__(self, server_ip, server_port):
|
||||
self._server_ip = server_ip
|
||||
self._server_port = server_port
|
||||
self._quadruple_to_dscp_table = {}
|
||||
self._build_filter()
|
||||
|
||||
# def _read_server_ip_and_port(self):
|
||||
|
||||
def _build_filter(self):
|
||||
self._filter = f"tcp and src host {self._server_ip} and src port {self._server_port}"
|
||||
|
||||
def start(self):
|
||||
self._sniff_thread = AsyncSniffer(iface=self.IFACE, prn=self._packet_callback, filter=self._filter)
|
||||
self._sniff_thread.start()
|
||||
|
||||
def _packet_callback(self, packet):
|
||||
if IP in packet and TCP in packet:
|
||||
src_ip = packet[IP].src
|
||||
if src_ip == self._server_ip:
|
||||
dscp_value = self._read_dscp_value_from_packet(packet)
|
||||
quadruple = self._build_quadruple(packet, True)
|
||||
self._update_quadruple_to_dscp_table(quadruple, dscp_value)
|
||||
|
||||
def _read_dscp_value_from_packet(self, packet):
|
||||
ip_header = packet[IP]
|
||||
return (ip_header.tos & 0xfc) >> 2
|
||||
|
||||
def _build_quadruple(self, packet, is_s2c):
|
||||
src_ip = packet[IP].src
|
||||
dst_ip = packet[IP].dst
|
||||
src_port = packet[TCP].sport
|
||||
dst_port = packet[TCP].dport
|
||||
if is_s2c:
|
||||
return f"{dst_ip}:{dst_port},{src_ip}:{src_port}"
|
||||
else:
|
||||
return f"{src_ip}:{src_port},{dst_ip}:{dst_port}"
|
||||
|
||||
def _update_quadruple_to_dscp_table(self, quadruple, dscp_value):
|
||||
self._quadruple_to_dscp_table[quadruple] = dscp_value
|
||||
|
||||
def stop(self):
|
||||
self._sniff_thread.stop()
|
||||
self._sniff_thread.join()
|
||||
|
||||
def read_dscp_value_by_quadruple(self, quadruple):
|
||||
if quadruple in self._quadruple_to_dscp_table:
|
||||
return self._quadruple_to_dscp_table[quadruple]
|
||||
else:
|
||||
return None
|
||||
|
||||
class TcpPacketsCaptureAnalyzer:
|
||||
def is_dscp_equal(self, present_dscp, desired_dscp):
|
||||
if present_dscp is None:
|
||||
return False, f"Error: Not read DSCP value."
|
||||
|
||||
if present_dscp == desired_dscp:
|
||||
return True, None
|
||||
else:
|
||||
return False, f"Error: Failed to verify DSCP value. Present DSCP: {present_dscp}, desired DSCP: {desired_dscp}."
|
||||
|
||||
class URLTransferBuilder:
|
||||
def __init__(self, url: str, request_resolve: list, conn_timeout: int, max_recv_speed_large: int):
|
||||
@@ -375,6 +432,10 @@ class URLTransferBuilder:
|
||||
self._response_buffer = BytesIO()
|
||||
self._error_info = None
|
||||
self._size_download = None
|
||||
self._local_ip = None
|
||||
self._local_port = None
|
||||
self._remote_ip = None
|
||||
self._remote_port = None
|
||||
|
||||
def _setup_connection(self):
|
||||
self._response_buffer = BytesIO()
|
||||
@@ -389,6 +450,10 @@ class URLTransferBuilder:
|
||||
self._conn.perform()
|
||||
self._response_code = self._conn.getinfo(self._conn.RESPONSE_CODE)
|
||||
self._size_download = self._conn.getinfo(pycurl.SIZE_DOWNLOAD)
|
||||
self._local_ip = self._conn.getinfo(pycurl.LOCAL_IP)
|
||||
self._local_port = self._conn.getinfo(pycurl.LOCAL_PORT)
|
||||
self._remote_ip = self._conn.getinfo(pycurl.PRIMARY_IP)
|
||||
self._remote_port = self._conn.getinfo(pycurl.PRIMARY_PORT)
|
||||
|
||||
def _close_connection(self):
|
||||
self._conn.close()
|
||||
@@ -418,6 +483,10 @@ class URLTransferBuilder:
|
||||
def size_download(self):
|
||||
return self._size_download
|
||||
|
||||
@property
|
||||
def quadruple(self):
|
||||
return f"{self._local_ip}:{self._local_port},{self._remote_ip}:{self._remote_port}"
|
||||
|
||||
class HttpURLTransferBuilder(URLTransferBuilder):
|
||||
def _perform_connection(self):
|
||||
super()._perform_connection()
|
||||
@@ -784,6 +853,7 @@ class ShapingCaseRunner:
|
||||
def __init__(self) -> None:
|
||||
self._analyzer = URLTransferResponseAnalyzer()
|
||||
self._dns_analyzer = DNSResponseAnalyzer()
|
||||
self._capture_analyzer = TcpPacketsCaptureAnalyzer()
|
||||
|
||||
def rate_limit_0bps_protocol_http(self, url, resolves, conn_timeout, max_recv_speed_large):
|
||||
conn = HttpURLTransferBuilder(url, resolves, conn_timeout, max_recv_speed_large)
|
||||
@@ -802,19 +872,31 @@ class ShapingCaseRunner:
|
||||
return True, None
|
||||
|
||||
def rate_limit_1000gbps_protocol_http(self, url, resolves, conn_timeout, max_recv_speed_large):
|
||||
server_ip, server_port = self._read_server_ip_and_port_from_resolve(resolves)
|
||||
capture = TcpPacketsCapture(server_ip, server_port)
|
||||
capture.start()
|
||||
conn = HttpURLTransferBuilder(url, resolves, conn_timeout, max_recv_speed_large)
|
||||
conn.connect()
|
||||
capture.stop()
|
||||
is_error_none = self._analyzer.is_pycurl_error_none(conn.error_info)
|
||||
if not is_error_none[0]:
|
||||
return False, is_error_none[1]
|
||||
is_code_equal = self._analyzer.is_response_code_equal(conn.response_code, 200)
|
||||
if not is_code_equal[0]:
|
||||
return False, is_code_equal[1]
|
||||
present_dscp = capture.read_dscp_value_by_quadruple(conn.quadruple)
|
||||
is_dscp_equal = self._capture_analyzer.is_dscp_equal(present_dscp, 8)
|
||||
if not is_dscp_equal[0]:
|
||||
return False, is_dscp_equal[1]
|
||||
return True, None
|
||||
|
||||
def rate_limit_1000gbps_protocol_https(self, url, resolves, conn_timeout, max_recv_speed_large):
|
||||
server_ip, server_port = self._read_server_ip_and_port_from_resolve(resolves)
|
||||
capture = TcpPacketsCapture(server_ip, server_port)
|
||||
capture.start()
|
||||
conn = HttpsURLTransferBuilder(url, resolves, conn_timeout, max_recv_speed_large)
|
||||
conn.connect()
|
||||
capture.stop()
|
||||
is_error_none = self._analyzer.is_pycurl_error_none(conn.error_info)
|
||||
if not is_error_none[0]:
|
||||
return False, is_error_none[1]
|
||||
@@ -824,8 +906,17 @@ class ShapingCaseRunner:
|
||||
is_cert_matched = self._analyzer.is_cert_issuer_matched(conn.cert_issuer, r'\bCN[\s]*=[\s]*BadSSL\b')
|
||||
if not is_cert_matched[0]:
|
||||
return False, is_cert_matched[1]
|
||||
present_dscp = capture.read_dscp_value_by_quadruple(conn.quadruple)
|
||||
is_dscp_equal = self._capture_analyzer.is_dscp_equal(present_dscp, 8)
|
||||
if not is_dscp_equal[0]:
|
||||
return False, is_dscp_equal[1]
|
||||
return True, None
|
||||
|
||||
def _read_server_ip_and_port_from_resolve(self, resolves):
|
||||
resolve = resolves[0]
|
||||
resolve_split = resolve.split(":")
|
||||
return resolve_split[2], resolve_split[1]
|
||||
|
||||
class FirewallCasesRunner:
|
||||
def __init__(self) -> None:
|
||||
self._analyzer = URLTransferResponseAnalyzer()
|
||||
@@ -1415,24 +1506,25 @@ class DiagnoseCasesRunner:
|
||||
print(format(("Service function name: " + str(sf_pair["name"]) + ",Test end time: " + end_timestamp),'=^100s'))
|
||||
|
||||
def _run_cases_in_one_service_function_id(self, service_function_id):
|
||||
resolve_Builder = ServerAddressBuilder(service_function_id)
|
||||
resolve_builder = ServerAddressBuilder(service_function_id)
|
||||
exporter = ResultExportBuilder()
|
||||
for case_info in self._cases_info:
|
||||
if re.fullmatch(self._case_names_regexp, case_info["name"]):
|
||||
self._run_one_case(case_info, resolve_Builder, exporter)
|
||||
self._run_one_case(case_info, resolve_builder, exporter)
|
||||
exporter.export()
|
||||
|
||||
def _run_one_case(self, case_info, resolve_Builder, exporter):
|
||||
def _run_one_case(self, case_info, resolve_builder, exporter):
|
||||
conn_timeout = self._get_conn_timeout_from_configs(case_info["name"])
|
||||
max_recv_speed_large = self._get_max_recv_speed_large_from_configs(case_info["name"])
|
||||
|
||||
test_func = case_info.get("test_function")
|
||||
if test_func:
|
||||
if case_info["protocol_type"] == "http" or case_info["protocol_type"] == "https":
|
||||
ret = test_func(case_info["request_content"], resolve_Builder.resolves, conn_timeout, max_recv_speed_large)
|
||||
resolve = resolve_builder.read_resolves_by_url(case_info["request_content"])
|
||||
ret = test_func(case_info["request_content"], resolve, conn_timeout, max_recv_speed_large)
|
||||
|
||||
if case_info["protocol_type"] == "dns":
|
||||
ret = test_func(case_info["request_content"], resolve_Builder.nameservers, conn_timeout)
|
||||
ret = test_func(case_info["request_content"], resolve_builder.nameservers, conn_timeout)
|
||||
|
||||
exporter.append_case_result(case_info["name"], ret)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user