Repository: secdev/scapy

Issue:
When using PPP layers with ipv6 protocol as 0x0057，it will be only one byte in the packet to send.
### Brief description

when i want to use ppp and some other layers to create one router advertisement packet for my test environment

i find it will fail for my dut to recognize the packet  and i find that the protocol in ppp layers it seems to be one byte show in 
 packet captured by wireshark

the left one is the packet built by myself and the right one is normal
![de8c613d49c69f7f4d2360b9f545135](https://user-images.githubusercontent.com/64533170/221492960-16455a1d-71dc-4f8d-94e3-7ae6524ada62.png)


### Scapy version

2.4.5

### Python version

3.8.5

### Operating system

ubuntu 20.0.4 and windows 10

### Additional environment information

_No response_

### How to reproduce

from scapy.all import *
a = PPPoE()
b = PPP(proto=0x0057)
c=IPv6(src='2a01:4f8:161:5300::40', dst='ff02::1:ff00:1')

d=ICMPv6ND_RA()

e=ICMPv6NDOptSrcLLAddr(lladdr='fa:63:a5:0c:8b:40')
f=ICMPv6NDOptMTU()
g=ICMPv6NDOptPrefixInfo(prefix='2a01:4f8:161:5300::', prefixlen=64)
send(a/b/c/d/e/f/g,iface = 'eth3')

you can use the beyond code to reproduce and  use wireshark to capture at eth3 at the same time 

### Actual result
when make ppp protocl as ipv6 0x0057
the protocol of ppp is one byte


### Expected result
when make ppp protocl as ipv6 0x0057
the protocol of ppp is two bytes


### Related resources

_No response_


Discussion:
Hi,

I don't understand this issue. From my tests, the packet `a/b/c/d/e/f/g` has a `.protocol` field encoded on one byte. That's correctly decoded by Wireshark and that matches the "Expected result" from your issue.

I have tested with both Scapy 2.4.5 and current development branch.
yes, i know the packet can be correctly decoded by wireshark, howerver,the length of ppp protocol should be two bytes,but when the protocol valud less than 0x100，it will be one bytes length,this will cause many device can not decode the  packet.
It is caused by lines 309 through 311 in layers/ppp.py  when the val<0x100 ,the length is one byte, and  i think the length should be always two bytes.
> i think the length should be always two bytes

According to https://www.rfc-editor.org/rfc/rfc1661#section-2
> The Protocol field is one or two octets

Though according to https://www.rfc-editor.org/rfc/rfc1661#section-6.5
> By default, all
      implementations MUST transmit packets with two octet PPP Protocol
      fields.

PPP Protocol field numbers can be compressed into a single octet form but according to the RFC
> Compressed Protocol fields MUST NOT be transmitted unless this
      Configuration Option has been negotiated.

It should be possible to send two bytes by using `RawVal`:
```python
PPP(proto=RawVal(b"\x00\x57"))
```
It doesn't fix the issue in general but it should help to temporarily get it around at least.
I aviod this problem by modify line 309-311 and  force to use  2 bytes when handle ppp protocol filed,  i hope this can be fixed soon.  And in summary,  I am really grateful for your help.
> force to use 2 bytes when handle ppp protocol filed

Looks like it should work too. Judging by
> When negotiated, PPP
      implementations MUST accept PPP packets with either double-octet
      or single-octet Protocol fields, and MUST NOT distinguish between
      them.

it should be safe to always send two bytes. That being said it could be that some scripts in the wild can somehow rely on `scapy` sending exactly one byte and switching to two bytes unconditionally can probably break them.

@summeer1998 could you change the issue description? Right now it says that one byte is expected but it should be the other way around. My guess would be that "actual result" and "expected result" just got mixed up.
Firstly,I am really sorry for that I actually put "actual result" and "expected result" in the wrong place when I report this issue.
I have change the description no.
And secondly,in my opinion，scapy should give the user the option of using 1 byte or 2 bytes.And when the val<0x100 use one byte default,and the user can choose to use two bytes.
I also met the same issue. The PPPoE packet was dropped when the protocol field of the PPP with 1 byte.
The Linux kernel seems to treat the Protocol Field as 16 bits (2 Bytes) only.
@summeer1998 I agree with your idea. Suggest Scapy provides an additional argument to force Protocol Field with 2 bytes.

[scapy/layers/ppp.py](https://github.com/secdev/scapy/blob/0f294fd6231baa5876624776aab9817bd6bc2b39/scapy/layers/ppp.py)
``` python
class _PPPProtoField(EnumField):
    """
    A field that can be either Byte or Short, depending on the PPP RFC.
    See RFC 1661 section 2
    <https://tools.ietf.org/html/rfc1661#section-2>
    """
    def getfield(self, pkt, s):
        if ord(s[:1]) & 0x01:
            self.fmt = "!B"
            self.sz = 1
        else:
            self.fmt = "!H"
            self.sz = 2
        self.struct = struct.Struct(self.fmt)
        return super(_PPPProtoField, self).getfield(pkt, s)

    def addfield(self, pkt, s, val):
        if val < 0x100:
            self.fmt = "!B"
            self.sz = 1
        else:
            self.fmt = "!H"
            self.sz = 2
        self.struct = struct.Struct(self.fmt)
        return super(_PPPProtoField, self).addfield(pkt, s, val)
```

[linux 6.3](https://elixir.bootlin.com/linux/latest/source/drivers/net/ppp/ppp_generic.c#L236)
``` C
/* Get the PPP protocol number from a skb */
#define PPP_PROTO(skb)	get_unaligned_be16((skb)->data)
```

Table 1. List my environment
| No. | Name | Content |
| ---  | --- | --- |
| 1    | Operation System | Ubuntu 22.04 |
| 2    | Scapy                    | v2.2.4 |  






Resolving patch:
diff --git a/scapy/layers/ppp.py b/scapy/layers/ppp.py
--- a/scapy/layers/ppp.py
+++ b/scapy/layers/ppp.py
@@ -292,6 +292,14 @@ class _PPPProtoField(EnumField):
 
     See RFC 1661 section 2
     <https://tools.ietf.org/html/rfc1661#section-2>
+
+    The generated proto field is two bytes when not specified, or when specified
+    as an integer or a string:
+      PPP()
+      PPP(proto=0x21)
+      PPP(proto="Internet Protocol version 4")
+    To explicitly forge a one byte proto field, use the bytes representation:
+      PPP(proto=b'\x21')
     """
     def getfield(self, pkt, s):
         if ord(s[:1]) & 0x01:
@@ -304,12 +312,18 @@ def getfield(self, pkt, s):
         return super(_PPPProtoField, self).getfield(pkt, s)
 
     def addfield(self, pkt, s, val):
-        if val < 0x100:
-            self.fmt = "!B"
-            self.sz = 1
+        if isinstance(val, bytes):
+            if len(val) == 1:
+                fmt, sz = "!B", 1
+            elif len(val) == 2:
+                fmt, sz = "!H", 2
+            else:
+                raise TypeError('Invalid length for PPP proto')
+            val = struct.Struct(fmt).unpack(val)[0]
         else:
-            self.fmt = "!H"
-            self.sz = 2
+            fmt, sz = "!H", 2
+        self.fmt = fmt
+        self.sz = sz
         self.struct = struct.Struct(self.fmt)
         return super(_PPPProtoField, self).addfield(pkt, s, val)
 


Test patch:
diff --git a/test/scapy/layers/ppp.uts b/test/scapy/layers/ppp.uts
--- a/test/scapy/layers/ppp.uts
+++ b/test/scapy/layers/ppp.uts
@@ -112,12 +112,24 @@ assert  raw(p) == raw(q)
 assert  q[PPP_ECP_Option].data == b"ABCDEFG" 
 
 
-= PPP with only one byte for protocol
+= PPP IP check that default protocol length is 2 bytes
+~ ppp ip
+
+p = PPP()/IP()
+p
+r = raw(p)
+r
+assert r.startswith(b'\x00\x21')
+assert len(r) == 22
+
+
+= PPP check parsing with only one byte for protocol
 ~ ppp
 
-assert len(raw(PPP() / IP())) == 21
+assert len(raw(PPP(proto=b'\x21') / IP())) == 21
 
 p = PPP(b'!E\x00\x00<\x00\x00@\x008\x06\xa5\xce\x85wP)\xc0\xa8Va\x01\xbbd\x8a\xe2}r\xb8O\x95\xb5\x84\xa0\x12q \xc8\x08\x00\x00\x02\x04\x02\x18\x04\x02\x08\nQ\xdf\xd6\xb0\x00\x07LH\x01\x03\x03\x07Ao')
 assert IP in p
 assert TCP in p
 
+assert PPP(b"\x00\x21" + raw(IP())) == PPP(b"\x21" + raw(IP()))