Skip to content

Commit 62d72d2

Browse files
committed
wip: openvpn observations
1 parent 1fe719c commit 62d72d2

6 files changed

Lines changed: 130 additions & 14 deletions

File tree

oonidata/src/oonidata/models/dataformats.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -367,23 +367,17 @@ class NetworkEvent(BaseModel):
367367
dial_id: Optional[int] = None
368368
conn_id: Optional[int] = None
369369

370-
@add_slots
371-
@dataclass
372-
class OpenVPNConnectStatus(BaseModel):
373-
success: bool
374-
failure: Union[Failure, bool] = None
375370

376371
@add_slots
377372
@dataclass
378373
class OpenVPNHandshake(BaseModel):
379-
bootstrap_time: float
374+
handshake_time: float
380375
endpoint: str
381376
ip: str # we might want to make this optional, and scrub in favor of ASN/prefix
382377
port: int
383378
transport: str
384379
provider: str
385380
openvpn_options: Optional[Dict[str, str]] = None
386-
status: OpenVPNConnectStatus
387381
t0: float
388382
t: float
389383
tags: Optional[List[str]] = None

oonidata/src/oonidata/models/nettests/openvpn.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
from dataclasses import dataclass
22
from typing import List, Optional
3-
from oonidata.compat import add_slots
4-
from oonidata.models.dataformats import (
3+
4+
from ..compat import add_slots
5+
6+
from ..base import BaseModel
7+
from ..dataformats import (
58
BaseTestKeys,
69
Failure,
710
TCPConnect,
@@ -14,12 +17,15 @@
1417
@add_slots
1518
@dataclass
1619
class OpenVPNTestKeys(BaseTestKeys):
17-
failure: Failure = None
1820
success: Optional[bool] = False
21+
failure: Failure = None
1922

2023
network_events: Optional[List[OpenVPNNetworkEvent]] = None
21-
openvpn_handshake: Optional[List[OpenVPNHandshake]] = None
2224
tcp_connect: Optional[List[TCPConnect]] = None
25+
openvpn_handshake: Optional[List[OpenVPNHandshake]] = None
26+
27+
bootstrap_time: Optional[float] = None
28+
tunnel: str = None
2329

2430

2531
@add_slots

oonidata/src/oonidata/models/observations.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -375,7 +375,7 @@ class HTTPMiddleboxObservation:
375375
table_index=("measurement_uid", "observation_id", "measurement_start_time"),
376376
)
377377
@dataclass
378-
class OpenVPNHandshakeObservation:
378+
class OpenVPNObservation:
379379
measurement_meta: MeasurementMeta
380380
probe_meta: ProbeMeta
381381
processing_meta: ProcessingMeta
@@ -401,8 +401,8 @@ class OpenVPNHandshakeObservation:
401401

402402
# OpenVPN handshake observation
403403
openvpn_handshake_failure: Optional[Failure] = None
404-
openvpn_handshake_success: Optional[bool] = None
405404
openvpn_handshake_t: Optional[float] = None
405+
openvpn_handshake_t0: Optional[float] = None
406406

407407
# timing info about the handshake packets
408408
openvpn_handshake_hr_client_t: Optional[float] = None

oonipipeline/src/oonipipeline/transforms/measurement_transformer.py

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525
NetworkEvent,
2626
TCPConnect,
2727
TLSHandshake,
28+
OpenVPNHandshake,
29+
OpenVPNNetworkEvent,
2830
maybe_binary_data_to_bytes,
2931
)
3032
from oonidata.models.nettests.base_measurement import BaseMeasurement
@@ -36,6 +38,7 @@
3638
TCPObservation,
3739
TLSObservation,
3840
WebObservation,
41+
OpenVPNObservation,
3942
)
4043
from oonidata.datautils import (
4144
InvalidCertificateChain,
@@ -760,6 +763,63 @@ def make_measurement_meta(msmt: BaseMeasurement, bucket_date: str) -> Measuremen
760763
measurement_start_time=measurement_start_time,
761764
)
762765

766+
def count_key_exchange_packets(network_events: List[OpenVPNNetworkEvent]) -> int:
767+
"""
768+
return number of packets exchanged in the SENT_KEY state
769+
"""
770+
n = 0
771+
for evt in network_events:
772+
if evt.stage == "SENT_KEY" and evt.operation.startswith("packet_"):
773+
n+=1
774+
return n
775+
776+
def measurement_to_openvpn_observation(
777+
msmt_meta: MeasurementMeta,
778+
probe_meta: ProbeMeta,
779+
netinfodb: NetinfoDB,
780+
openvpn_h: OpenVPNHandshake,
781+
tcp_connect: Optional[List[TCPConnect]],
782+
network_events: Optional[List[OpenVPNNetworkEvent]],
783+
) -> OpenVPNObservation:
784+
785+
oo = OpenVPNObservation(
786+
measurement_meta=msmt_meta,
787+
probe_meta=probe_meta,
788+
failure=normalize_failure(openvpn_h.failure),
789+
timestamp=make_timestamp(msmt_meta.measurement_start_time, openvpn_h.t),
790+
success=openvpn_h.failure == None,
791+
protocol="openvpn",
792+
)
793+
794+
if len(tcp_connect) != 0:
795+
tcp = tcp_connect[0]
796+
oo.tcp_success = tcp.status.success
797+
oo.tcp_failure = tcp.status.failure
798+
oo.tcp_t = tcp.t
799+
800+
oo.handshake_failure = openvpn_h.failure
801+
oo.handshake_t = openvpn_h.t
802+
oo.handshake_t0 = openvpn_h.t0
803+
804+
for evt in network_events:
805+
if evt.packet is not None:
806+
if evt.packet.opcode == "P_CONTROL_HARD_RESET_CLIENT_V2":
807+
oo.openvpn_handshake_hr_client_t = evt.t
808+
elif evt.packet.opcode == "P_CONTROL_HARD_RESET_SERVER_V2":
809+
oo.openvpn_handshake_hr_server_t = evt.t
810+
elif "client_hello" in evt.tags:
811+
oo.openvpn_handshake_clt_hello_t = evt.t
812+
elif "server_hello" in evt.tags:
813+
oo.openvpn_handshake_clt_hello_t = evt.t
814+
elif evt.operation == "state" and evt.stage == "GOT_KEY":
815+
oo.openvpn_handshake_got_keys__t = evt.t
816+
elif evt.operation == "state" and evt.stage == "GENERATED_KEYS":
817+
oo.openvpn_handshake_gen_keys__t = evt.t
818+
819+
oo.openvpn_handshake_key_exchg_n = count_key_exchange_packets(network_events)
820+
821+
return oo
822+
763823

764824
class MeasurementTransformer:
765825
"""
@@ -911,7 +971,7 @@ def consume_web_observations(
911971
912972
It will attempt to map them via the transaction_id or ip:port tuple.
913973
914-
Any observation that cannot be mapped will be returned inside of it's
974+
Any observation that cannot be mapped will be returned inside of its
915975
own WebObservation with all other columns set to None.
916976
"""
917977
web_obs_list: List[WebObservation] = []
@@ -1008,5 +1068,38 @@ def consume_web_observations(
10081068

10091069
return web_obs_list
10101070

1071+
def make_openvpn_observations(self,
1072+
tcp_connect: Optional[List[TCPConnect]],
1073+
openvpn_handshakes: Optional[List[OpenVPNHandshake]],
1074+
network_events: Optional[List[OpenVPNNetworkEvent]],
1075+
bootstrap_time: float,
1076+
) -> List[OpenVPNObservation]:
1077+
"""
1078+
Returns a list of OpenVPNObservations by mapping all related
1079+
TCPObservations, OpenVPNNetworkevents and OpenVPNHandshakes.
1080+
"""
1081+
openvpn_obs_list: List[OpenVPNObservation] = []
1082+
1083+
for openvpn_handshake in openvpn_handshakes:
1084+
web_obs_list.append(
1085+
measurement_to_openvpn_observation(
1086+
msmt_meta=self.measurement_meta,
1087+
probe_meta=self.probe_meta,
1088+
netinfodb=self.netinfodb,
1089+
tcp_connect=tcp_connect,
1090+
openvpn_handshake=openvpn_handshake,
1091+
network_events=network_events,
1092+
)
1093+
)
1094+
1095+
# TODO: can factor out function with web_observation
1096+
for idx, obs in enumerate(openvpn_obs_list):
1097+
obs.observation_id = f"{obs.measurement_meta.measurement_uid}_{idx}"
1098+
obs.created_at = datetime.now(timezone.utc).replace(
1099+
microsecond=0, tzinfo=None
1100+
)
1101+
1102+
return openvpn_obs_list
1103+
10111104
def make_observations(self, measurement):
10121105
assert RuntimeError("make_observations is not implemented")
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from typing import List, Tuple
2+
from oonidata.models.nettests import OpenVPN
3+
from oonidata.models.observations import OpenVPNObservation
4+
5+
from ..measurement_transformer import MeasurementTransformer
6+
7+
8+
class OpenVPNTransformer(MeasurementTransformer):
9+
def make_observations(self, msmt: OpenVPN) -> Tuple[List[OpenVPNObservation]]:
10+
openvpn_obs_list = []
11+
if not msmt.test_keys:
12+
return (openvpn_obs_list,)
13+
14+
tcp_observations = self.make_tcp_observations(msmt.tcp_connect)
15+
16+
return self.make_openvpn_observations(
17+
tcp_observations=tcp_observations,
18+
openvpn_handshakes=msmt.openvpn_handshake,
19+
network_events=msmt.network_events,
20+
bootstrap_time=msmt.bootstrap_time,
21+
)

oonipipeline/src/oonipipeline/transforms/observations.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from .nettests.browser_web import BrowserWebTransformer
1919
from .nettests.urlgetter import UrlGetterTransformer
2020
from .nettests.web_connectivity import WebConnectivityTransformer
21+
from .nettests.openvpn import OpenVPNTransformer
2122
from .nettests.http_invalid_request_line import (
2223
HTTPInvalidRequestLineTransformer,
2324
)
@@ -37,6 +38,7 @@
3738
"http_header_field_manipulation": HTTPHeaderFieldManipulationTransformer,
3839
"http_invalid_request_line": HTTPInvalidRequestLineTransformer,
3940
"web_connectivity": WebConnectivityTransformer,
41+
"openvpn": OpenVPNTransformer,
4042
}
4143

4244
TypeWebConnectivityObservations = Tuple[

0 commit comments

Comments
 (0)