Skip to content

Commit 5b09a9e

Browse files
authored
fix(hip): HIPv2-only parameters are accepted inside HIPv1 packets (#1160)
1 parent 6e71d55 commit 5b09a9e

2 files changed

Lines changed: 131 additions & 4 deletions

File tree

‎pcapkit/protocols/internet/hip.py‎

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,7 +1236,7 @@ def _read_param_ack(self, schema: 'Schema_ACKParameter', *, version: 'int', # p
12361236
)
12371237
return ack
12381238

1239-
def _read_param_dh_group_list(self, schema: 'Schema_DHGroupListParameter', *, version: 'int', # pylint: disable=unused-argument
1239+
def _read_param_dh_group_list(self, schema: 'Schema_DHGroupListParameter', *, version: 'int',
12401240
options: 'Parameter') -> 'Data_DHGroupListParameter': # pylint: disable=unused-argument
12411241
"""Read HIP ``DH_GROUP_LIST`` parameter.
12421242
@@ -1262,7 +1262,13 @@ def _read_param_dh_group_list(self, schema: 'Schema_DHGroupListParameter', *, ve
12621262
Returns:
12631263
Parsed parameter data.
12641264
1265+
Raises:
1266+
ProtocolError: The parameter is **NOT** supported in HIPv1.
1267+
12651268
"""
1269+
if version == 1:
1270+
raise ProtocolError(f'HIPv{version}: [ParamNo {schema.type}] invalid parameter')
1271+
12661272
dh_group_list = Data_DHGroupListParameter(
12671273
type=schema.type,
12681274
critical=bool(schema.type & 0b1),
@@ -1380,9 +1386,12 @@ def _read_param_hip_cipher(self, schema: 'Schema_HIPCipherParameter', *, version
13801386
Parsed parameter data.
13811387
13821388
Raises:
1383-
ProtocolError: If ``schema.len`` is **NOT** a ``2`` modulo.
1389+
ProtocolError: If the parameter is used in HIPv1, or ``schema.len`` is
1390+
**NOT** a ``2`` modulo.
13841391
13851392
"""
1393+
if version == 1:
1394+
raise ProtocolError(f'HIPv{version}: [ParamNo {schema.type}] invalid parameter')
13861395
if schema.len % 2 != 0:
13871396
raise ProtocolError(f'HIPv{version}: [ParamNo {schema.type}] invalid format')
13881397

@@ -1596,7 +1605,7 @@ def _read_param_host_id(self, schema: 'Schema_HostIDParameter', *, version: 'int
15961605
)
15971606
return host_id
15981607

1599-
def _read_param_hit_suite_list(self, schema: 'Schema_HITSuiteListParameter', *, version: 'int', # pylint: disable=unused-argument
1608+
def _read_param_hit_suite_list(self, schema: 'Schema_HITSuiteListParameter', *, version: 'int',
16001609
options: 'Parameter') -> 'Data_HITSuiteListParameter': # pylint: disable=unused-argument
16011610
"""Read HIP ``HIT_SUITE_LIST`` parameter.
16021611
@@ -1622,7 +1631,13 @@ def _read_param_hit_suite_list(self, schema: 'Schema_HITSuiteListParameter', *,
16221631
Returns:
16231632
Parsed parameter data.
16241633
1634+
Raises:
1635+
ProtocolError: The parameter is **NOT** supported in HIPv1.
1636+
16251637
"""
1638+
if version == 1:
1639+
raise ProtocolError(f'HIPv{version}: [ParamNo {schema.type}] invalid parameter')
1640+
16261641
hit_suite_list = Data_HITSuiteListParameter(
16271642
type=schema.type,
16281643
critical=bool(schema.type & 0b1),
@@ -2005,9 +2020,12 @@ def _read_param_transport_format_list(self, schema: 'Schema_TransportFormatListP
20052020
Parsed parameter data.
20062021
20072022
Raises:
2008-
ProtocolError: If ``schema.len`` is **NOT** ``2`` modulo.
2023+
ProtocolError: If the parameter is used in HIPv1, or ``schema.len`` is
2024+
**NOT** ``2`` modulo.
20092025
20102026
"""
2027+
if version == 1:
2028+
raise ProtocolError(f'HIPv{version}: [ParamNo {schema.type}] invalid parameter')
20112029
if schema.len % 2 != 0:
20122030
raise ProtocolError(f'HIPv{version}: [ParamNo {schema.type}] invalid format')
20132031

@@ -3512,7 +3530,13 @@ def _make_param_dh_group_list(self, code: 'Enum_Parameter', param: 'Optional[Dat
35123530
Returns:
35133531
HIP parameter schema.
35143532
3533+
Raises:
3534+
ProtocolError: The parameter is **NOT** supported in HIPv1.
3535+
35153536
"""
3537+
if version == 1:
3538+
raise ProtocolError(f'HIPv{version}: [ParamNo {code}] invalid parameter')
3539+
35163540
if param is not None:
35173541
group_id = cast('list[Enum_Group]', param.group_id)
35183542
else:
@@ -3634,7 +3658,13 @@ def _make_param_hip_cipher(self, code: 'Enum_Parameter', param: 'Optional[Data_H
36343658
Returns:
36353659
HIP parameter schema.
36363660
3661+
Raises:
3662+
ProtocolError: The parameter is **NOT** supported in HIPv1.
3663+
36373664
"""
3665+
if version == 1:
3666+
raise ProtocolError(f'HIPv{version}: [ParamNo {code}] invalid parameter')
3667+
36383668
if param is not None:
36393669
cipher_id = cast('list[Enum_Cipher]', param.cipher_id)
36403670
else:
@@ -3917,7 +3947,13 @@ def _make_param_hit_suite_list(self, code: 'Enum_Parameter', param: 'Optional[Da
39173947
Returns:
39183948
HIP parameter schema.
39193949
3950+
Raises:
3951+
ProtocolError: The parameter is **NOT** supported in HIPv1.
3952+
39203953
"""
3954+
if version == 1:
3955+
raise ProtocolError(f'HIPv{version}: [ParamNo {code}] invalid parameter')
3956+
39213957
if param is not None:
39223958
suite_id = cast('list[Enum_HITSuite]', param.suite_id)
39233959
else:
@@ -4339,7 +4375,13 @@ def _make_param_transport_format_list(self, code: 'Enum_Parameter', param: 'Opti
43394375
Returns:
43404376
HIP parameter schema.
43414377
4378+
Raises:
4379+
ProtocolError: The parameter is **NOT** supported in HIPv1.
4380+
43424381
"""
4382+
if version == 1:
4383+
raise ProtocolError(f'HIPv{version}: [ParamNo {code}] invalid parameter')
4384+
43434385
if param is not None:
43444386
tf_type = cast('list[Enum_Parameter]', param.tf_type)
43454387
else:
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -*- coding: utf-8 -*-
2+
"""HIP rejects the HIPv2-only list parameters inside a HIPv1 packet.
3+
4+
GitHub issue #1133. :rfc:`7401` introduces ``DH_GROUP_LIST`` (511),
5+
``HIP_CIPHER`` (579), ``HIT_SUITE_LIST`` (715) and ``TRANSPORT_FORMAT_LIST``
6+
(2049); :rfc:`5201` has none of them. All four built and parsed back at
7+
version 1. They now raise :class:`~pcapkit.utilities.exceptions.ProtocolError`
8+
on both sides, as ``R1_Counter`` does for the opposite mismatch.
9+
"""
10+
from __future__ import annotations
11+
12+
import importlib.util
13+
import unittest
14+
15+
from tests._support import reimport_once_per_class
16+
17+
RUNTIME_DEPS = ('tbtrim', 'aenum', 'chardet', 'dictdumper')
18+
HAS_RUNTIME = all(importlib.util.find_spec(name) is not None for name in RUNTIME_DEPS)
19+
20+
#: Header fields shared by every HIP packet built here.
21+
HIP_BASE = {
22+
'next': 6, 'packet': 1, 'checksum': b'\x00\x00',
23+
'controls_anonymous': False, 'shit': 0, 'rhit': 0, 'payload': b'',
24+
}
25+
26+
27+
@unittest.skipUnless(HAS_RUNTIME, 'runtime dependencies not installed')
28+
class HIPv2OnlyParameterTests(unittest.TestCase):
29+
"""The four :rfc:`7401` list parameters, at HIP versions 1 and 2."""
30+
31+
def setUp(self) -> None:
32+
reimport_once_per_class(self)
33+
34+
def _cases(self) -> 'dict[object, dict[str, object]]':
35+
"""Map each HIPv2-only parameter code to the keywords that build it."""
36+
from pcapkit.const.hip.parameter import Parameter
37+
38+
return {
39+
Parameter.DH_GROUP_LIST: {'groups': [1]},
40+
Parameter.HIP_CIPHER: {'ciphers': [1]},
41+
Parameter.HIT_SUITE_LIST: {'suites': [1]},
42+
Parameter.TRANSPORT_FORMAT_LIST: {'formats': [Parameter.ESP_INFO]},
43+
}
44+
45+
def _build(self, code: 'object', kwargs: 'dict[str, object]', version: 'int') -> 'bytes':
46+
from pcapkit.protocols.internet.hip import HIP
47+
48+
return HIP(parameters=[(code, kwargs)], extension=True,
49+
version=version, **HIP_BASE).data
50+
51+
def test_building_at_version_1_raises(self) -> None:
52+
from pcapkit.utilities.exceptions import ProtocolError
53+
54+
for code, kwargs in self._cases().items():
55+
with self.subTest(code=code):
56+
with self.assertRaisesRegex(ProtocolError, rf'HIPv1: \[ParamNo {code}\] invalid parameter'):
57+
self._build(code, kwargs, 1)
58+
59+
def test_parsing_at_version_1_raises(self) -> None:
60+
from pcapkit.protocols.internet.hip import HIP
61+
from pcapkit.utilities.exceptions import ProtocolError
62+
63+
for code, kwargs in self._cases().items():
64+
with self.subTest(code=code):
65+
# Build at version 2, then rewrite the 4-bit Version field
66+
# (the high nibble of octet 3) to 1.
67+
octets = bytearray(self._build(code, kwargs, 2))
68+
octets[3] = (octets[3] & 0x0F) | 0x10
69+
with self.assertRaisesRegex(ProtocolError, rf'HIPv1: \[ParamNo {code}\] invalid parameter'):
70+
HIP(bytes(octets), len(octets), extension=True)
71+
72+
def test_version_2_still_round_trips(self) -> None:
73+
from pcapkit.protocols.internet.hip import HIP
74+
75+
for code, kwargs in self._cases().items():
76+
with self.subTest(code=code):
77+
octets = self._build(code, kwargs, 2)
78+
parsed = HIP(octets, len(octets), extension=True)
79+
self.assertEqual(parsed.info.version, 2)
80+
codes = [int(key) for key in parsed.info.parameters]
81+
self.assertEqual(codes, [int(code)]) # type: ignore[call-overload]
82+
83+
84+
if __name__ == '__main__':
85+
unittest.main()

0 commit comments

Comments
 (0)