Neutrino is an encrypted and event-driven UDP based network protocol with focus on simplicity (as far this is possible which such a protocol) and high performance. In July 2024 I started to use Neutrino in production.
The idea for Neutrino initially came from QUIC which is expected to replace TCP with encrypted UDP in HTTP/3. Due to the lack of implementations and the complexity of this protocol (and the lack of encryption in plain UDP) I decided to create Neutrino in a module based way, where in the basic version at least encrypted UDP can be provided – which is mandatory nowadays.
For better separation of concerns it comes in three different versions – the basic version and two extensions:
-
Neutrino
The basic protocol. Packets1 are always encrypted and must have a size of <= 1280 bytes. -
NeutrinoReliable
An extension which introduces detection and correction of packet loss and detection of duplicates or packets which are out of order. -
NeutrinoExtended (Not ready yet)
Relies on NeutrinoReliable and raises the packet size limit.
1 With the exception of the initial PACKET_TYPE_CLIENT_HELLO1.
For a short example use ServerExampleNeutrinoReliable.py
and ClientExampleNeutrinoReliable.py
, see here.
The Inspector is used for testing purposes. For instance, it interferes with the traffic to trigger the duplicate packet or packet loss detection.
The Monitoring class is also used for testing purposes. It more or less visualizes the traffic:
- Python >= 3.7
- PyNaCl (libsodium / https://github.com/jedisct1/libsodium)
The packet payload and parts of the header (containing the packet number) are encrypted using XChaCha20-Poly1305. This functionality is provided by the easily portable libsodium project which is available in PHP and in Python via PyNaCl.
apt install python3-nacl
RAW_PACKET = (HEADER + PAYLOAD)
The header consists of a left and right part. While the left part is unprotected, the right side – which includes the packet number – is protected.
HEADER(
UNPROTECTED(
[Protocol Identifier = u32 bit (4 bytes)]
[Protocol Version = u8 bit (1 byte)]
[Type = u8 bit (1)]
[Session ID = u64 bit (8 bytes)]
)
ENCRYPTED(
[Packet Number = u64 bit (8 bytes)]
[Keyword: Reserved for arbitrary use = u32 bit (4)]
)
)
PAYLOAD(
[Amount of Payload Words = u8 bit (1 byte)]
for word_number_n=0 to [Amount of Payload Words]
WORD_N(
[Playload Word Size = u16 bit (2 bytes)]
[Word = ? bytes]
)
)
PAYLOAD(
[Amount of Payload Words = u16 bit (2 bytes)]
for word_number_n=0 to [Amount of Payload Words]
WORD_N(
[Playload Word Size = u32 bit (4 bytes)]
[Word = ? bytes]
)
)