Repository: secdev/scapy

Issue:
DoIP 'plain' Routing Activation Request length cannot be shorter than 19 bytes.
### Brief description

DoIP Routing Activaion Request cannot be lesser than 19 bytes, to be exact - can't be equal to 11 bytes



### Scapy version

2.4.5rc1.dev183

### Python version

3.8.12

### Operating system

Linux 5.16.0-1

### Additional environment information

_No response_

### How to reproduce

1. Downlaod newest Scapy version
2. load_contrib('automotive.doip')
3. len(DoIP(payload_type=0x0005, payload_length=11))

### Actual result

>>> len(DoIP(payload_type=0x0005, payload_length=11))
19

### Expected result

>>> len(DoIP(payload_type=0x0005, payload_length=11))
11

### Related resources

ISO 13400-2:2019, since vehicle manufacturer-related field is optional in most recent available ISO 13400-2 version
If requested, I'd try to provide PDF copy of this version.
DoIP 'plain' Routing Activation Request length cannot be shorter than 19 bytes.
### Brief description

DoIP Routing Activaion Request cannot be lesser than 19 bytes, to be exact - can't be equal to 11 bytes



### Scapy version

2.4.5rc1.dev183

### Python version

3.8.12

### Operating system

Linux 5.16.0-1

### Additional environment information

_No response_

### How to reproduce

1. Downlaod newest Scapy version
2. load_contrib('automotive.doip')
3. len(DoIP(payload_type=0x0005, payload_length=11))

### Actual result

>>> len(DoIP(payload_type=0x0005, payload_length=11))
19

### Expected result

>>> len(DoIP(payload_type=0x0005, payload_length=11))
11

### Related resources

ISO 13400-2:2019, since vehicle manufacturer-related field is optional in most recent available ISO 13400-2 version
If requested, I'd try to provide PDF copy of this version.


Discussion:
![doip](https://user-images.githubusercontent.com/1676055/150304304-3fbf7aef-47b3-4dff-8024-e9b4fb57e328.png)
The DoIP packet itself has a fixed size of 8 byte if payload_type 1 is specified. The field `payload_length`counts the payload bytes of a DoIP packet. In the picture, you can see the fixed length of DoIP. 
Can you please provide the standard, because I'm not sure what the issue is in your case.
@polydroi I will provide as soon as I will be back home
I'm terribly sorry, it's not about vehicle ID request, it's about routing activation request, I will change title and modify description after I will get home, I'm terribly sorry.
Can you please explain your usecase? 

payload_length is just a field for a value. Changing this value doesn't change the packet and therefore doesn't have an effect on its length. On the other hand, scapy will fill out this value for you, if you don't specify it .
Basically to be able to not include some optional fields in a packet in easier way than pkt.build(), then dropping unnecessary (for me) bytes and replacing payload_length byte to match overall DoIP packet. 
Sorry that use case is currently not supported. The easiest would probably be to derive a custom class and overwrite the pre_build or post_build hook. 
In my opinion such a implicit behavior would not follow the scapy style. Therefore I think this should not end up mainline.
> In my opinion such a implicit behavior would not follow the scapy style. Therefore I think this should not end up mainline.

i was thinking about something like mandatory_only flag to only include mandatory fields in built packet, to not break existing style as much :)
That might be possible, but how do you define mandatory? Is the standard that you have providing a description which fields are optional and which are mandatory?
> That might be possible, but how do you define mandatory? Is the standard that you have providing a description which fields are optional and which are mandatory?

Exactly that. Inside standard itself is written which field is mandatory and which are optional
Ok. Can you provide the standard?

On January 22, 2022 1:54:04 PM GMT+01:00, "Damian Zaręba" ***@***.***> wrote:
>> That might be possible, but how do you define mandatory? Is the
>standard that you have providing a description which fields are
>optional and which are mandatory?
>
>Exactly that. Inside standard itself is written which field is
>mandatory an dwhich are optional
>
>-- 
>Reply to this email directly or view it on GitHub:
>https://github.com/secdev/scapy/issues/3507#issuecomment-1019246354
>You are receiving this because you commented.
>
>Message ID: ***@***.***>

Sure. Before I will finish cleaning-up document from watermarks & stuff, I'm able to provide page regarding Routing Activation Request.
![Screenshot from 2022-01-22 15-07-00](https://user-images.githubusercontent.com/12693890/150641652-1642ab84-966c-48c3-b29e-5bd88ff466c8.png)


I suggest to make the field oem_specific to a XStrLenField with a max_length of 4. 

Could you please also provide the Standard for the routing activation response. 

Can you create a PR for this?
> I suggest to make the field oem_specific to a XStrLenField with a max_length of 4.
> 
> Could you please also provide the Standard for the routing activation response.
> 
> Can you create a PR for this?

I will provide page for response today as well. In the meantime I will cleanup formatting for whole PDF (thanks, LO Draw -,-).
What kind of PR do you need? Should I provide first draft of code change?
I did already an PR. Can you please test this PR against your use case?
![doip](https://user-images.githubusercontent.com/1676055/150304304-3fbf7aef-47b3-4dff-8024-e9b4fb57e328.png)
The DoIP packet itself has a fixed size of 8 byte if payload_type 1 is specified. The field `payload_length`counts the payload bytes of a DoIP packet. In the picture, you can see the fixed length of DoIP. 
Can you please provide the standard, because I'm not sure what the issue is in your case.
@polydroi I will provide as soon as I will be back home
I'm terribly sorry, it's not about vehicle ID request, it's about routing activation request, I will change title and modify description after I will get home, I'm terribly sorry.
Can you please explain your usecase? 

payload_length is just a field for a value. Changing this value doesn't change the packet and therefore doesn't have an effect on its length. On the other hand, scapy will fill out this value for you, if you don't specify it .
Basically to be able to not include some optional fields in a packet in easier way than pkt.build(), then dropping unnecessary (for me) bytes and replacing payload_length byte to match overall DoIP packet. 
Sorry that use case is currently not supported. The easiest would probably be to derive a custom class and overwrite the pre_build or post_build hook. 
In my opinion such a implicit behavior would not follow the scapy style. Therefore I think this should not end up mainline.
> In my opinion such a implicit behavior would not follow the scapy style. Therefore I think this should not end up mainline.

i was thinking about something like mandatory_only flag to only include mandatory fields in built packet, to not break existing style as much :)
That might be possible, but how do you define mandatory? Is the standard that you have providing a description which fields are optional and which are mandatory?
> That might be possible, but how do you define mandatory? Is the standard that you have providing a description which fields are optional and which are mandatory?

Exactly that. Inside standard itself is written which field is mandatory and which are optional
Ok. Can you provide the standard?

On January 22, 2022 1:54:04 PM GMT+01:00, "Damian Zaręba" ***@***.***> wrote:
>> That might be possible, but how do you define mandatory? Is the
>standard that you have providing a description which fields are
>optional and which are mandatory?
>
>Exactly that. Inside standard itself is written which field is
>mandatory an dwhich are optional
>
>-- 
>Reply to this email directly or view it on GitHub:
>https://github.com/secdev/scapy/issues/3507#issuecomment-1019246354
>You are receiving this because you commented.
>
>Message ID: ***@***.***>

Sure. Before I will finish cleaning-up document from watermarks & stuff, I'm able to provide page regarding Routing Activation Request.
![Screenshot from 2022-01-22 15-07-00](https://user-images.githubusercontent.com/12693890/150641652-1642ab84-966c-48c3-b29e-5bd88ff466c8.png)


I suggest to make the field oem_specific to a XStrLenField with a max_length of 4. 

Could you please also provide the Standard for the routing activation response. 

Can you create a PR for this?
> I suggest to make the field oem_specific to a XStrLenField with a max_length of 4.
> 
> Could you please also provide the Standard for the routing activation response.
> 
> Can you create a PR for this?

I will provide page for response today as well. In the meantime I will cleanup formatting for whole PDF (thanks, LO Draw -,-).
What kind of PR do you need? Should I provide first draft of code change?
I did already an PR. Can you please test this PR against your use case?

Resolving patch:
diff --git a/scapy/contrib/automotive/doip.py b/scapy/contrib/automotive/doip.py
--- a/scapy/contrib/automotive/doip.py
+++ b/scapy/contrib/automotive/doip.py
@@ -14,7 +14,7 @@
 
 from scapy.fields import ByteEnumField, ConditionalField, \
     XByteField, XShortField, XIntField, XShortEnumField, XByteEnumField, \
-    IntField, StrFixedLenField
+    IntField, StrFixedLenField, XStrField
 from scapy.packet import Packet, bind_layers, bind_bottom_up
 from scapy.supersocket import StreamSocket
 from scapy.layers.inet import TCP, UDP
@@ -143,7 +143,8 @@ class DoIP(Packet):
         ConditionalField(XShortField("source_address", 0),
                          lambda p: p.payload_type in [5, 8, 0x8001, 0x8002, 0x8003]),  # noqa: E501
         ConditionalField(XByteEnumField("activation_type", 0, {
-            0: "Default", 1: "WWH-OBD", 0xe0: "Central security"
+            0: "Default", 1: "WWH-OBD", 0xe0: "Central security",
+            0x16: "Default", 0x116: "Diagnostic", 0xe016: "Central security"
         }), lambda p: p.payload_type in [5]),
         ConditionalField(XShortField("logical_address_tester", 0),
                          lambda p: p.payload_type in [6]),
@@ -157,7 +158,8 @@ class DoIP(Packet):
             0x04: "Routing activation denied due to missing authentication.",
             0x05: "Routing activation denied due to rejected confirmation.",
             0x06: "Routing activation denied due to unsupported routing activation type.",  # noqa: E501
-            0x07: "Reserved by ISO 13400.", 0x08: "Reserved by ISO 13400.",
+            0x07: "Routing activation denied because the specified activation type requires a secure TLS TCP_DATA socket.",  # noqa: E501
+            0x08: "Reserved by ISO 13400.",
             0x09: "Reserved by ISO 13400.", 0x0a: "Reserved by ISO 13400.",
             0x0b: "Reserved by ISO 13400.", 0x0c: "Reserved by ISO 13400.",
             0x0d: "Reserved by ISO 13400.", 0x0e: "Reserved by ISO 13400.",
@@ -167,7 +169,7 @@ class DoIP(Packet):
         }), lambda p: p.payload_type in [6]),
         ConditionalField(XIntField("reserved_iso", 0),
                          lambda p: p.payload_type in [5, 6]),
-        ConditionalField(XIntField("reserved_oem", 0),
+        ConditionalField(XStrField("reserved_oem", b""),
                          lambda p: p.payload_type in [5, 6]),
         ConditionalField(XByteEnumField("diagnostic_power_mode", 0, {
             0: "not ready", 1: "ready", 2: "not supported"
@@ -175,7 +177,7 @@ class DoIP(Packet):
         ConditionalField(ByteEnumField("node_type", 0, {
             0: "DoIP gateway", 1: "DoIP node"
         }), lambda p: p.payload_type in [0x4002]),
-        ConditionalField(XByteField("max_open_sockets", 0),
+        ConditionalField(XByteField("max_open_sockets", 1),
                          lambda p: p.payload_type in [0x4002]),
         ConditionalField(XByteField("cur_open_sockets", 0),
                          lambda p: p.payload_type in [0x4002]),
@@ -249,6 +251,8 @@ class DoIPSocket(StreamSocket):
                            determined if routing activation request is sent
     :param activation_type: This allows to set a different activation type for
                             the routing activation request
+    :param reserved_oem: Optional parameter to set value for reserved_oem field
+                         of routing activation request
 
     Example:
         >>> socket = DoIPSocket("169.254.0.131")
@@ -257,8 +261,8 @@ class DoIPSocket(StreamSocket):
     """  # noqa: E501
     def __init__(self, ip='127.0.0.1', port=13400, activate_routing=True,
                  source_address=0xe80, target_address=0,
-                 activation_type=0):
-        # type: (str, int, bool, int, int, int) -> None
+                 activation_type=0, reserved_oem=b""):
+        # type: (str, int, bool, int, int, int, bytes) -> None
         self.ip = ip
         self.port = port
         self.source_address = source_address
@@ -266,7 +270,7 @@ def __init__(self, ip='127.0.0.1', port=13400, activate_routing=True,
 
         if activate_routing:
             self._activate_routing(
-                source_address, target_address, activation_type)
+                source_address, target_address, activation_type, reserved_oem)
 
     def _init_socket(self, sock_family=socket.AF_INET):
         # type: (int) -> None
@@ -279,11 +283,12 @@ def _init_socket(self, sock_family=socket.AF_INET):
     def _activate_routing(self,
                           source_address,  # type: int
                           target_address,  # type: int
-                          activation_type  # type: int
+                          activation_type,  # type: int
+                          reserved_oem=b""  # type: bytes
                           ):  # type: (...) -> None
         resp = self.sr1(
             DoIP(payload_type=0x5, activation_type=activation_type,
-                 source_address=source_address),
+                 source_address=source_address, reserved_oem=reserved_oem),
             verbose=False, timeout=1)
         if resp and resp.payload_type == 0x6 and \
                 resp.routing_activation_response == 0x10:
@@ -311,6 +316,8 @@ class DoIPSocket6(DoIPSocket):
                            determined if routing activation request is sent
     :param activation_type: This allows to set a different activation type for
                             the routing activation request
+    :param reserved_oem: Optional parameter to set value for reserved_oem field
+                         of routing activation request
 
     Example:
         >>> socket = DoIPSocket6("2001:16b8:3f0e:2f00:21a:37ff:febf:edb9")
@@ -319,8 +326,8 @@ class DoIPSocket6(DoIPSocket):
     """  # noqa: E501
     def __init__(self, ip='::1', port=13400, activate_routing=True,
                  source_address=0xe80, target_address=0,
-                 activation_type=0):
-        # type: (str, int, bool, int, int, int) -> None
+                 activation_type=0, reserved_oem=b""):
+        # type: (str, int, bool, int, int, int, bytes) -> None
         self.ip = ip
         self.port = port
         self.source_address = source_address
@@ -328,7 +335,7 @@ def __init__(self, ip='::1', port=13400, activate_routing=True,
 
         if activate_routing:
             super(DoIPSocket6, self)._activate_routing(
-                source_address, target_address, activation_type)
+                source_address, target_address, activation_type, reserved_oem)
 
 
 class UDS_DoIPSocket(DoIPSocket):


Test patch:
diff --git a/test/contrib/automotive/doip.uts b/test/contrib/automotive/doip.uts
--- a/test/contrib/automotive/doip.uts
+++ b/test/contrib/automotive/doip.uts
@@ -80,6 +80,19 @@ assert p.vin_gid_status == 0
 
 p = DoIP(bytes(DoIP(payload_type=5)))
 
+assert p.protocol_version == 0x02
+assert p.inverse_version == 0xfd
+assert p.payload_length == 7
+assert p.payload_type == 5
+assert p.source_address == 0
+assert p.activation_type == 0
+assert p.reserved_iso == 0
+assert p.reserved_oem == b""
+
+= Build test 5.1
+
+p = DoIP(bytes(DoIP(payload_type=5, reserved_oem=b"1234")))
+
 assert p.protocol_version == 0x02
 assert p.inverse_version == 0xfd
 assert p.payload_length == 11
@@ -87,7 +100,22 @@ assert p.payload_type == 5
 assert p.source_address == 0
 assert p.activation_type == 0
 assert p.reserved_iso == 0
-assert p.reserved_oem == 0
+p.show()
+print(p.reserved_oem)
+assert p.reserved_oem == b"1234"
+
+= Build test 5.2
+
+p = DoIP(bytes(DoIP(payload_type=5, reserved_oem=b"12")))
+
+assert p.protocol_version == 0x02
+assert p.inverse_version == 0xfd
+assert p.payload_length == 9
+assert p.payload_type == 5
+assert p.source_address == 0
+assert p.activation_type == 0
+assert p.reserved_iso == 0
+assert p.reserved_oem == b"12"
 
 = Build test 6
 
@@ -95,12 +123,12 @@ p = DoIP(bytes(DoIP(payload_type=6)))
 
 assert p.protocol_version == 0x02
 assert p.inverse_version == 0xfd
-assert p.payload_length == 13
+assert p.payload_length == 9
 assert p.payload_type == 6
 assert p.logical_address_tester == 0
 assert p.logical_address_doip_entity == 0
 assert p.reserved_iso == 0
-assert p.reserved_oem == 0
+assert p.reserved_oem == b""
 
 = Build test 7
 
@@ -140,7 +168,7 @@ assert p.inverse_version == 0xfd
 assert p.payload_length == 7
 assert p.payload_type == 0x4002
 assert p.node_type == 0
-assert p.max_open_sockets == 0
+assert p.max_open_sockets == 1
 assert p.cur_open_sockets == 0
 assert p.max_data_size == 0
 
@@ -220,7 +248,7 @@ assert p.payload_type == 0x5
 assert p.source_address == 0xe80
 assert p.activation_type == 0
 assert p.reserved_iso == 0
-assert p.reserved_oem == 0
+assert p.reserved_oem == b"\x00\x00\x00\x00"
 
 = dissect test of routing activation pkts resp