netrexmc / raknet Goto Github PK
View Code? Open in Web Editor NEWRakNet implementation in Rust
License: Apache License 2.0
RakNet implementation in Rust
License: Apache License 2.0
Unconnected connections (eg: ones that have not under-gone the connection handshake) should be handled separately from one's that have.
Currently, rak_rs
does not attempt to discover MTU size from the client. Currently, we time out the client if the server doesn't send a OpenConnectReply
however this behavior is wrong, we need to continuously send a OpenConnectRequest
until the server accepts the MTU size.
Implementation example:
// discover MTU
let mut mtu = MAX_MTU;
let mut connect_request = OpenConnectRequest {
protocol: 11,
mtu_size: mtu
};
for i in (MIN_MTU_SIZE...mtu).rev() {
// attempt to send request
if !send_packet(&socket, connect_request.into()).await {
// error sending
}
let reply = expect_reply!(socket.clone(), OpenConnectReply::id());
if reply.is_none() {
// try again, unless...
if i == MIN_MTU_SIZE {
return Err(HandshakeStatus::Failed);
}
}
// we got a reply!
break;
}
When running the async-std example, the client throws an error when connecting
version = "0.3.1"
Client (only ip was changed from "na.zeqa.net:19132"):
[rakrs] DBG! [CLIENT] Attempting to connect to address: 0.0.0.0:19133
[rakrs] DBG! [CLIENT] Waiting for pong packet...
[rakrs] DBG! [CLIENT] Recieved pong packet!
[rakrs] DBG! [CLIENT] Starting connection handshake
[rakrs] DBG! [CLIENT] Sending OpenConnectRequest to server...
[rakrs] DBG! [CLIENT] Received OpenConnectReply from server!
[rakrs] DBG! [CLIENT] Discovered MTU size: 1417
[rakrs] DBG! [CLIENT] Sending SessionInfoRequest to server...
[rakrs] DBG! [CLIENT] Received SessionInfoReply from server!
[rakrs] DBG! [CLIENT] Sent ConnectionRequest to server!
thread 'async-std/runtime' panicked at /home/honnisha/Projects/RakNet/src/client/handshake.rs:270:47:
called `Result::unwrap()` on an `Err` value: OldSeq
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Server (debug options was added):
[rakrs] DBG! listener: Bound to 0.0.0.0:19133
[rakrs] DBG! [127.0.0.1:44553] Client requested Mtu Size: 1417
[rakrs] DBG! Creating new session for 127.0.0.1:44553
[rakrs] DBG! Created Session for 127.0.0.1:44553
[rakrs] DBG! [127.0.0.1:44553] Updated mtu size to 1417
Why is it counted as 20 when the correct size is 23?
Line 60 in 2af4c5a
1 byte (flags) + 2 bytes (payload length) + 3 bytes (reliable index) + 3 bytes (sequence index) + 3 bytes (order index) + 1 byte (order channel) + 4 bytes (compound size) + 2 bytes (compound id) + 4 bytes (compound index) = 23
The following implementation is incorrect and buggy: https://github.com/NetrexMC/RakNet/tree/fragments/src/frame/mod.rs#L161-L180
Why?
We receive a large frame from the client, but the fragments are incorrectly reassembled by the fragment list.
I will investigate this more tomorrow.
This issue is high priority because when it is fixed, we will be able to actually work on the software.
Currently, the api for starting a RakNet Server is not very dynamic in that it only supports a one way solution making it hard to throw your own solution on top of the current one.
The issue lies within creating a RakNet Server and starting one up, currently you need to invoke the start
function which will spawn all threads and start waiting for clients, (as expected). However, the issue lies in the fact as the end user, you have no access to the connection itself or the listener, this revision aims to change that. Consider the current implementation:
let server = RakNetServer::new(String::from("0.0.0.0:19132"));
let channel = netrex_events::Channel::<RakEvent, RakResult>::new();
let mut listener = |event, _| {
match event {
RakEvent::ConnectionCreated(address) => {
println!("[RakNet] [{}] Client connected", address);
}
};
None
};
channel.receive(&mut listener);
let v = start(server, channel).await;
v.0.await;
The revision for the code above would allow the end user to optionally process either each packet from each connection or each raw packet using asynchronous methods.
For example, the new way to handle connections with this revision may look something like:
async fn spawn_server() {
let server = Server::new("0.0.0.0:19132");
// If this is used, the server will spawn a thread to manage the connections.
// When the thread is spawned, the handle to it is returned.
let handling_thread = server.listen();
loop {
// Waits for a packet. Resolved when a packet from a connection is recieved.
// For raw packets, use `server.recv()`
let pk_processor = server.process().await?;
let connection = pk_processor.connection.as_ref().lock();
// do your handling here.
// keep in mind that connection is a mutable reference.
// For example, we can hijack the servers packet processing.
pk_processor.prevent_default();
// The server is waiting on a packet to be set within the packet processor.
// Using this method will resolve that future.
pk_processor.reply_with(DisconnectPacket::new().into());
// Alternatively, to use default behavior you can use:
pk_processor.proceed();
}
}
This change is necessary because of how restrictive the current implementation is. This revision would allow you to have better control of the raknet server as well as it's processing of packets, which currently is done through the disaster of events, which should only be used for server plugins.
RakNet should use the updated binary-util
crate provided by netrex as it is more efficient than prior releases.
I just noticed this repository has no stated license.
I'd suggest the Apache 2.0 license, in line with other Rust libraries.
See title.
I'd like to use this library myself, but with async-std rather than Tokio -- my existing code is written against async-std.
Could async-std be supported by a feature flag? I am willing to create a PR if necessary
Describe the bug
The sort_by
function in the collect method of FragmentQueue
is using id
for comparison during sorting (it should actually be index
), causing the Frame
to be sorted incorrectly. As a result, in some instances, the output Vec<u8>
may be incorrect.
To Reproduce
Steps to reproduce the behavior:
This issue may probabilistically occur when receiving a Fragment
because the sorting is not done correctly, primarily due to the improper use of id
instead of index
in the sorting process.
Expected behavior
Correcting the usage of the sort_by
function, here is the suggested modified code:
frames.sort_by(|a, b| {
a.fragment_meta
.as_ref()
.unwrap()
.index
.cmp(&b.fragment_meta.as_ref().unwrap().index)
});
Server versions (please complete the following information):
Additional context
Here is the debug output of my code, and the buffer concatenation is not correct:
code:
for frame in frames.iter() {
let fm = frame.fragment_meta.clone().unwrap();
println!("index: {}, len: {}", fm.index, frame.body.len());
buffer.extend_from_slice(&frame.body);
}
output:
index: 0, len: 1463
index: 1, len: 1463
index: 5, len: 637
index: 2, len: 1463
index: 3, len: 1463
index: 4, len: 1463
In an async server software you often have a loop which recv packets and some sort of client struct that contains a way to send packets.
This would mean we would either need to copy/clone the connection or split it.
Tokio's tcp stream has a nice way to split it into a recv half and write half.
use tokio::prelude::*;
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut stream = TcpStream::connect("localhost:8080").await?;
let (mut read, mut write) = tokio::io::split(stream);
tokio::spawn(async move {
loop {
let mut buf = [0u8; 32];
read.read(&mut buf).await.unwrap();
println!("{:?}", std::str::from_utf8(&buf));
}
});
Ok(())
}
this code is from stack overflow
Even though Raknet uses udp it would still be nice if an api for splitting the connection or copying/cloning it would be added.
This is very important for writing effiecient server software that utilise multi threading.
The Motd struct represents the protocol version as an u16
, but the protocol version (for mcbe at least) is always represented as an i32
.
(I don't know why it is a signed integer, lets hope protocol version -42
is planned)
Due to the raknet docs not defining the motd and mojang rather doing their own stuff in this area, this is a bug of consistency.
Describe the bug
The Client
and Connection
structs both have an issue when communicating with eachother, that being, when the tasks are paused, there is no way to "wake them up" currently, meaning if a disconnect signal is received by the server, the other tasks will have no way of knowing that this signal was received causing a weird case where the connection freezes and has to wait for the connection timeout.
To Reproduce
Steps to reproduce the behavior:
Expected behavior
These structs should dispatch signals properly to all tasks without any need for locking.
Screenshots
If applicable, add screenshots to help explain your problem.
Server versions (please complete the following information):
This functionality could be adequate for people implementing RakNet for various other games. For instance, someone may want to send the client chunks on channel 2, but receive regular game packets on channel 0.
yeah, that's it
This issue is a followup for #39
The RFC Allows you to open a RakNet Peer Client to connect to other RakNet instances. This RFC contains the following functions:
Connect to a server instance
Force ACK/NAK flush
Example:
use rakrs::client::Client;
async fn connect(address: &'static str) -> Result<(), std::error::Error> {
let mut conn = Client::new(&address, 10);
conn.open();
loop {
let buf = conn.recv().await?;
// Send a packet on ordered channel
conn.send_ord(&[buf], 0);
// Send a packet sequenced
conn.send_seq(&[buf], 0);
// Send packet reliably (not ord, use send_ord for that)
conn.send(&[buf], Reliability::Reliable);
}
// flush ack and nack forcefully.
conn.flush_ack();
conn.close().await?;
}
It looks like wee can't change the 2nd line of the MOTD and it will always be Netrex.
Is there a way to fix this/a add this?
It would be awesome to have a field in the motd struct for the 2nd line of the displayed motd...
This issue is tracking the connection implementation behind RakNet.
Implement Reliable Queues
Implement dynamic dispatch for each connection
Reliable queues: Are for internal packet handling, similar to the v2 version you can see that here. However the newer queues are more robust in the fact that they will handle the repetition of ordering packets and ACKING as needed. For instance, the SendQueue
will hold an ack of all packets we've sent, also known as the recovery queue, while the RecvQueue
will hold an ack of all packets we're expecting, as well as handle reassembling of frames and out of order packets with an ordered queue.
Dynamic Dispatch: The main thread should be able to notify each connection of it's state regardless of the current processing state. EG: If we terminate a client, there's no reason it should need to wait until the next tick to figure that out, or if we need to send a packet immediately, there's no reason why we should need to queue this when we can dispatch it immediately to the server.
While this feature is not useful for Netrex, it's a feature that should be added to support other games that use raknet, like roblox. This could possibly also open the doors for custom raknet interfaces, like some sort of adapter to be added on-to a listener.
There's a few things on V2 that need to be better implemented before the update, such as removing redundant methods and making the api more robust, in that you can have full control of whether or not you want something to be reliable or not.
The raknet server never removes a "connected" client after timeout resulting in a potential memory leak.
Code: https://github.com/NetrexMC/RakNet/blob/protocol/src/server.rs#L66-L69
We need packet implementation, and preferably have it separate from the receiving thread.
Packets should:
IClientBound
and IServerBound
traits.The currently supported motd is the motd used by mojang, but other games might use another motd structure.
This could easily be added with a simple enum like:
enum Advertisement {
MinecraftBedrock(McMotd),
Other(String),
}
This would also allow other advertisements to be supported, from other games/applications.
This has multiple advantages, such that we can call async methods from producers, etc...
I can't send a packet that weighs about more than 1600 bytes.
C→S: [Resource Pack Client Response] (I receive confirmation that it is possible to send a "Start Game" packet)
S→C: [Start Game] (I am sending a packet that is about more than 1600 bytes and nothing happens, but in the wireshark program it shows a "malformed packet");
I guarantee that the interaction with the client was correct before, which means that the problem is in the library.
It seems that the library cannot split the packet into raknet fragments in order to send it to the client in parts.
1 screen (
1 packet is | C→S: The client's response to the resource package,
2 packet is | S→C: Start game
Which is about more than 1600 bytes
)
screen 2, the same data, but I have reduced the "Start Game" packet.
Moreover, I receive a large "Login" package from the client without any problems. (screen 3)
I used a translator, so the message may be incorrect.
While we care for race conditions, we can share the mutable vector, that has synchronized connection information in it, which should be more beneficial than the current implementation where a lock is help over the entire vector until the connection is modified / accessed, at which then it will be released.
For an example on this, see my test
Issue / Question:
Hello, I stumbled upon this project and was wondering if it supports creating a RakNet client or if it was only restricted to creating a server?
Proposals / Details:
None
Before #35 is complete, we need to improve the documentation within the library itself and prepare for the crates.io release, this will allow users to better see how to use rakrs
without needing to create an issue or look through the source code.
Here I will be discussing possible solutions on a discussion with this issue linked.
For more information on why this is an issue, reference this issue.
The server needs to be able to communicate certain events to each connection, and vice-versa, for instance, when a state is updated, the server should be notified of this change. Other minor things to consider are things like motd generation and session closures.
This issue is tracking a new way to implement this on both async-std
and tokio
with the same api.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.