-
Notifications
You must be signed in to change notification settings - Fork 41
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Re-implement connection ID generation for UDP tracker #60
Conversation
4dd4053
to
3545368
Compare
pub struct TorrentPeer {
pub peer_id: PeerId,
pub peer_addr: SocketAddr,
#[serde(serialize_with = "ser_instant")]
pub updated: std::time::Instant,
#[serde(with = "NumberOfBytesDef")]
pub uploaded: NumberOfBytes,
#[serde(with = "NumberOfBytesDef")]
pub downloaded: NumberOfBytes,
#[serde(with = "NumberOfBytesDef")]
pub left: NumberOfBytes,
#[serde(with = "AnnounceEventDef")]
pub event: AnnounceEvent,
} @WarmBeer , would not it be nice to rename the #[serde(serialize_with = "ser_instant")]
#[serde(rename(deserialize = "updated_milliseconds_ago", serialize = "updated_milliseconds_ago"))]
pub updated: std::time::Instant, To get a json like this: {
"info_hash": "4beb7001cb833968582c67f55cc59dcc6c8d3fe5",
"seeders": 1,
"completed": 0,
"leechers": 0,
"peers": [
{
"peer_id": {
"id": "2d7142343431302d7358376d33786d2877674179",
"client": "qBittorrent"
},
"peer_addr": "192.168.1.88:17548",
"updated_milliseconds_ago": 385,
"uploaded": 0,
"downloaded": 0,
"left": 0,
"event": "None"
}
]
} I generally like to add the units in the attribute name. We can use any other name like: |
I think renaming it to "updated_milliseconds_ago" is fine. Just “updated” is indeed too vague. |
I'm not going to do it in this PR. Especially since it's a breaking change. Unless I find a way to add an extra field in the json without removing the "update" one. |
hi @WarmBeer, I do not understand the requirements for the UDP tracker connection ID. pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId {
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64),
Err(_) => ConnectionId(0x7FFFFFFFFFFFFFFF),
}
} I have created three temporarily tests, but they may be wrong.
Are those behaviours right? 1 is passing, but I do not know if that is true. If that is true, how many hours should the connection id last? On the other hand, should not the connection id be "guessable by the client"? From https://www.bittorrent.org/beps/bep_0015.html. With that implementation, I guess the client knows it because it knows its address and the current time in Unix Epoch. Finally, as you can see in the test implementations, "mocking time" in tests leads to fragile, slow, or not feasible tests. One solution is controlling the clock in tests. For example https://users.rust-lang.org/t/proper-way-to-mock-in-rust/60621/5?u=josecelano For dynamic languages, I could override the SystemTime::now() function for tests. I've seen this answer to do something similar https://stackoverflow.com/a/59614532/3012842 in Rust, but it implies changing your production code. I suppose the best option is to create the trait and use a different implementation for testing. In the end, I have to change the prod code too :-) but injecting the dependency instead of writing the "switch" directly. In general, I think it is worth it to be able to mock the clock; other tests may depend on the time inderectly. For example, the torrent endpoint returns the time elapsed since the client was updated. |
pub fn get_connection_id(remote_address: &SocketAddr) -> ConnectionId {
match std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH) {
Ok(duration) => ConnectionId(((duration.as_secs() / 3600) | ((remote_address.port() as u64) << 36)) as i64),
Err(_) => ConnectionId(0x7FFFFFFFFFFFFFFF),
}
} This is a very poor implementation of generating a |
OK, then I can:
|
I did not know there was another implementation of the UDP tracker, and that function was copied from there: https://github.com/naim94a/udpt/blob/master/src/server.rs#L354-L359 Other implementations generate a random id, and they store it in the DB: I think I'm going to keep this change out of the scope of this PR and create a new issue, so we can discuss what it's the best implementation for the connection ID. |
It seems the current implementation for the Long explanation: #62 (comment) If that is the case I will continue adding tests to it and an explanation about why it works. |
@WarmBeer I think we could move the function |
protocol
moduleprotocol
module
hi @WarmBeer @da2ce7, I have fixed the tests and created new ones for the connection id generation:
I assume the function is OK. We should find out why it sues one to expire instead of the two minutes mentioned in the protocol specification. I've create a "clock" module, but for this test I was not necessary to mock the system clock. I've only mocked the current time (the "now"). Now you need to pass the "now": pub fn get_connection_id(remote_address: &SocketAddr, current_timestamp: u64) -> ConnectionId {
ConnectionId(((current_timestamp / 3600) | ((remote_address.port() as u64) << 36)) as i64)
} And this is the way to can use it: pub async fn handle_connect(remote_addr: SocketAddr, request: &ConnectRequest, tracker: Arc<TorrentTracker>) -> Result<Response, ServerError> {
let connection_id = get_connection_id(&remote_addr, current_timestamp_from_system_clock());
... I have slightly changed the behavior of the |
d9e90e3
to
3072e2c
Compare
I do not know yet requirements for the conenction id. These tests have to be changed once we find them out.
The current implementation of `get_connection_id` seems to be OK. I've added some tests. I needed to mock the current time becuase it's used in the ID generation. For now, I only neded to mock the current time (now). Not the clock service. I have also changed the signature of the `handle_connect` method. Now it panics if we cannot get the system time. In the previous behavior it was returning always a hardcoded connection ID: ConnectionId(0x7FFFFFFFFFFFFFFF)
9b3ec0a
to
bfa803b
Compare
Rebased. |
…ments listed in BEP-15
Improved get_connection_id function to conform with the requirements listed in BEP-15
protocol
moduleprotocol::utils
module
hi @WarmBeer, I've been thinking again about your solution, and I do not know if adding a hash increases security. In the previous implementations, you have 10000 possible connections ids for a given two-minute window. In the new implementation for one IP, you still have the same amount of possible connections ids. If you are an attacker, you can call the tracker and generate those 10000 IDs. Becuase the SALT does not depend on the client. It's the same for all clients. This is like a challenge-response authentication where we use a finite number of responses for the challenge. Maybe that's why the protocol says the connection ID must last only 2 minutes. You can make 10000 requests in less than 2 minutes with all the possibilities, and one of them will be accepted as a legitimate request. On the other hand, adding the IP to the hash does not improve security because it is supposed that one attacker will try to impersonate one IP. So it will try to generate the ID only for that IP. Am I missing something? cc @da2ce7 |
hi @WarmBeer could you please look at the latest commit. I've extracted the I did the most simple thing: pub struct RequestHandler {
encrypted_connection_id_issuer: EncryptedConnectionIdIssuer,
// todo: inject also a crate::protocol::Clock in order to make it easier to test it.
} but I suppose I should do something like: pub struct RequestHandler {
encrypted_connection_id_issuer: Arc<EncryptedConnectionIdIssuer>
} I suppose I have to move the RequestHandler initialization up to use always the same one. In fact, until we reach the server. So we must also convert the I want a confirmation because I do not know that part of the code and it uses concurrency. Until now we were using pure functions. |
Hi @josecelano , I moved some code around so that the RequestHandler (renamed it to PacketHandler) gets created at UdpServer::start() instead of on every UDP packet: #78 |
moved RequestHandler creation
The UDP tracker server needs an encryption key to encrypt the conenction id. It generates a new one on server start up.
Now it generates the random Blowfish key here. I've copied the code from @da2ce7's PR. I've also asked in the crate repo what the safest way to generate a random key is. |
@josecelano Instead of: https://crates.io/crates/rust-crypto I used: https://crates.io/crates/blowfish The |
@da2ce7 yes, I left you a comment in your PR. I did not see any deprecation message. You are using https://crates.io/crates/blowfish:
The base branch uses https://crates.io/crates/rust-crypto:
I wonder why it was deprecated. It seems to be used by a lot of people. But OK, then we should change it. |
@WarmBeer I think I have implemented 80% of the changes in your PR. I was thinking again about what you said here, and I tried to change my implementation to reflect your idea. This is the commit: Now I destroy the current object when it's not needed anymore: I consume the unneeded value, but I do not reuse it. It's a solution in the middle. It uses less memory, but it's not as fast as yours because we build the new value from scratch. If this solution were slow, I would change it again to your proposal. |
@josecelano I think doing it like this is ok 👍 My only nitpicks are that preferably And: fn extract_expiration_timestamp(&self) -> Timestamp32 {
let timestamp_bytes = &self.0[4..];
let timestamp = Timestamp32::from_le_bytes(timestamp_bytes);
timestamp
} to: fn extract_expiration_timestamp(&self) -> Timestamp32 {
let timestamp_bytes = &self.0[4..];
Timestamp32::from_le_bytes(timestamp_bytes)
} As this is preferred by Clippy: https://rust-lang.github.io/rust-clippy/master/index.html#let_and_return |
OK. Regarding conventions, I thought the Regarding Clippy, I'm using Visual Studio Code with the Rust Analyzer extension, and I thought Clippy was the default linter, but It's not. I've switched it to Clippy, and now I get that warning. By the way, what IDE are you using? If you want to enforce Clippy rules, we can install Megalinter with Clippy. I did it here. |
As porposed by @WarmBeer here: #60 (comment) Co-authored-by: Warm Beer <mickvd99@gmail.com>
Done. |
Hey, @WarmBeer In fact now I see a lot of warnings from Clippy. I will fix all of them in a new commit. Mine :-) |
Hey @josecelano ,
This describes the naming convention for using
I am using Intellij IDEA with Rust plugins. I've only switched to Clippy a short time ago, so I wouldn't be surprised if you see warnings for my older code as well :|). |
This is a good idea 👍 |
hi @WarmBeer @da2ce7, I think these other PRs make this one obsolete: The only difference is the type of connection id. This PR uses the encrypted version, and the other uses the witness. Anyway, I think it's faster to add the encrypted version to the other version. Maybe we should reconsider which version to use. The Witness version seems to be more common and speed could not be a problem given the slight difference. On the other hand, maybe we could keep all of them just like "live documentation" making it clear only one is used in production at the moment. My proposal is:
|
TODO:
verify_connection_id
get_connection_id
toudp
module.current_time
toclock
module.///
inline documentation in this case.verify_connection_id
to verify the UDP announce request.REMOVED FROM TODO: