Neutrino is an encrypted and event-driven UDP based network protocol. Due to its event-driven design, it does not make use of asyncio.
Most of the development has taken place between 2021 and 2022. In July 2024 I started to use Neutrino in production. It is the direct result of a personal research project, which goals and thoughts were as follows:
- UDP vs TCP: TCP nowadays is too complex and intransparent since it shall serve as a all-in-one solution (so-called protocol ossification). UDP can be way more effective for a new network protocol which is the reason that HTTP/3 (QUIC) is using it.
- Events vs Asyncio: Given the stateless design of UDP, I decided that even-though Neutrino brings statefulness it is more natural to use a pure event-driven design without making use of
asyncio. Another reason for this was that I think it ensures simpler portability. - Separation: Designing a network protocol is not an easy task, but the basic Neutrino protocol (Neutrino.py) consists of less than 2,000 lines including all the comments. It makes it easy for interested people to learn how it works and to modify it. This only works if it is not an all-in-one solution. The reliable version of Neutrino (NeutrinoReliable.py) (which detects loss, ensures that packets are in order and not duplicated) contains less than 600 lines of code including comments and extends Neutrino by these features.
As said, 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. -
NeutrinoReliableExtended (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 Python via PyNaCl.
apt install python3-naclPACKET_TYPE_CLIENT_HELLO1: int = 0x01
PACKET_TYPE_SERVER_HELLO2: int = 0x02
PACKET_TYPE_CLIENT_HELLO3: int = 0x03
PACKET_TYPE_KEEP_ALIVE: int = 0x04
PACKET_TYPE_CLIENT_GOOD_BYE: int = 0x05
PACKET_TYPE_SERVER_SHUTDOWN: int = 0x06
PACKET_TYPE_DATA: int = 0x47The only unprotected (non-encrypted) packet is PACKET_TYPE_CLIENT_HELLO1. It is sent by the client to the server in establish_session_to_server().
RAW_PACKET = (HEADER + PAYLOAD)The header consists of a left and right part. While the left part is unprotected (not encrypted), the right side – which includes the packet number – is protected (encrypted).
HEADER(
UNPROTECTED(
[Protocol Identifier = u32 bit (4 bytes)] = 0x5baa260c
[Protocol Version = u8 bit (1 byte)] = 0x02
[Packet Type = u8 bit (1)] = e.g. PACKET_TYPE_CLIENT_HELLO1
[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]
)
)request_frame() >> _get_next_packet_from_the_server() >> _clients_read() >> _read() request_frame() >> _get_next_packet_from_any_client() >> _servers_read() >> _read()

