Skip to content
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

Review the process of selecting the client/peer IP when the tracker is behind a proxy #163

Open
josecelano opened this issue Feb 1, 2023 · 4 comments
Labels
- Admin - Enjoyable to Install and Setup our Software Needs Research We Need to Know More About This Question / Discussion Community Feedback
Milestone

Comments

@josecelano
Copy link
Member

A comment on this PR originated a discussion and finally this issue.

Introduction

The tracker can be installed behind a reverse proxy:

client          <-> HTTP proxy                       <-> tracker                   <-> Internet
ip:                 header:                              config:                       peer addr:
145.254.214.256     X-Forwarded-For = 145.254.214.256    on_reverse_proxy = true       145.254.214.256

Preconditions

  • The tracker service provider also sets up and handles the HTTP proxy.
  • The HTTP proxy adds a new X-Forwarded-For header with the client IP if the header does not exist yet. If the header exists, it adds the client IP at the enf (rigth) of the header value, containg the list of address.
  • The tracker is configured with the option on_reverse_proxy = true

Actual behavior

The application is taken the rigthmost IP address in the header. That means the IP of the direct client of the owned HTTP proxy. The reason is we do not trust other proxies. They could spoof that header.

https://github.com/torrust/torrust-tracker/blob/develop/src/http/filters.rs#L135-L158

/// Get `PeerAddress` from `RemoteAddress` or Forwarded
fn peer_addr((on_reverse_proxy, remote_addr, x_forwarded_for): (bool, Option<SocketAddr>, Option<String>)) -> WebResult<IpAddr> {
    if !on_reverse_proxy && remote_addr.is_none() {
        return Err(reject::custom(Error::AddressNotFound));
    }

    if on_reverse_proxy && x_forwarded_for.is_none() {
        return Err(reject::custom(Error::AddressNotFound));
    }

    if on_reverse_proxy {
        let mut x_forwarded_for_raw = x_forwarded_for.unwrap();
        // remove whitespace chars
        x_forwarded_for_raw.retain(|c| !c.is_whitespace());
        // get all forwarded ip's in a vec
        let x_forwarded_ips: Vec<&str> = x_forwarded_for_raw.split(',').collect();
        // set client ip to last forwarded ip
        let x_forwarded_ip = *x_forwarded_ips.last().unwrap();

        IpAddr::from_str(x_forwarded_ip).map_err(|_| reject::custom(Error::AddressNotFound))
    } else {
        Ok(remote_addr.unwrap().ip())
    }
}

Consecuence

Since we do not trust proxies, the tracker will have only the rigth client IP if there is exactly one proxy (owned proxy) in the middle.

If the client tries to reach the tracker and there are other proxies in the middle, the tracker will not have the rigth client public IP.

This was the intended behavior.

If we use the leftmost IP address (or the first public IP starting on the left) the tracker could risk to be spammed with false peers.

The UDP tracker uses a conenction ID to avoid it.

Conclusion

  • Should we keep the current behavior?
  • Is there al alternative way to avoid the spam/spoofing?

Links

Initial Proposals

  1. Keep the current behavior.
  2. Take the leftmost (public) IP address and do nothing about false peers. The tracker would contain false peers but peers can go offile or the can change thier IP often so that's should not be a big problem.
  3. Try to connect to the peer to validate it.
josecelano added a commit that referenced this issue Feb 1, 2023
c46e417 test(http): [#159] add more tests for scrape request (Jose Celano)
c24d744 refactor(test): [#159] refactor tests for announce request (Jose Celano)
7ee588a refactor(test): [#159] refactor tests for scrape request (Jose Celano)
c89a1f3 fix(http): [#159] minor text fixes (Jose Celano)
2754189 refactor(http): [#159] rename struct in announce responses to follow new scrape conventions (Jose Celano)
dc304e7 test(http): [#159] scaffolding to test scrape responses in http tracker (Jose Celano)
d7610ef refactor(http): [#159] move mods to folders (Jose Celano)
953a100 docs(http): [#159] add links to info about scrape requests (Jose Celano)
fcd60e2 fix(http): Display impl for tracker::peer:ID (Jose Celano)
e1765f3 fix: new clippy errors (Jose Celano)
badb791 test(http): [#159] add tests for announce request in private mode (Jose Celano)
11492a3 test(http): [#159] add tests for announce request in whitelisted mode (Jose Celano)
86155d6 refactor(http): improve readability (Jose Celano)
452b81a test(http): [#159] add tests for assigning IP to peers in announce request (Jose Celano)
5cc2ac1 refactor(http): [#159] add dependency local-ip-address (Jose Celano)
080f3c4 test(http): [#159] add tests for uptadint statistics after announce request (Jose Celano)
85a4894 test(http): [#159] add test for default announce response format (Jose Celano)
3fce688 refactor(http): extract converter (Jose Celano)
96fb56c  test(http): [#159] add test for compact announce response (Jose Celano)
8e5c992 refactor(http): [#159] add dependency: serde_bytes (Jose Celano)
8ae4928 test(http): [#159] add test for invalid announce request params (Jose Celano)
62dbffa test(http): [#159] add test for missing announce req params in http tracker (Jose Celano)
7fa8ec8 test(http): add test for failing convertion of peer Id into String (Jose Celano)
ca8fc22 test(http): [#159] add tests for public http tracker (Jose Celano)
dd38045 refactor(http): [#159] extract test builders (Jose Celano)
1a558d2 test(http): [#159] add tests for public http tracker (Jose Celano)
41ad07f refactor(http): [#159] add dependencies: serde_urlencoded and serde_repr (Jose Celano)
3449202 test(http): [#159] HTTP tracker tests scaffolding (Jose Celano)
c26b356 feat(http): [#159] during HTTP tracker setup wait until job is running (Jose Celano)

Pull request description:

  Add tests for the HTTP tracker.

  - [x] Tests for request authentication.
  - [x] Tests for `announce` request.
  - [x] Tests for `scrape` request.

  These subtasks require discussion and a formal definition of the solution we will implement. I will create new issues for them:

  - [ ] Fix empty peer ID in response. Return UTF8 representation of the bytes, for example: `-qB000000000000000004`.
  - [ ] [Review the process of selecting the client/peer IP from the `X-Forwarded-For` header when the tracker is behind a proxy](#163). More info [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For#selecting_an_ip_address).

  **UPDATE**: 01-02-2023

ACKs for top commit:
  josecelano:
    ACK c46e417

Tree-SHA512: 0a6101941cdcef5eb9309dce2726ea5f4a865ccdf695f8551b02859821c5068e76b16e989cdb7bbd4a1e3484709995d8fbebc843cdb6951e293882ba11e82cde
@da2ce7 da2ce7 added - Admin - Enjoyable to Install and Setup our Software Needs Research We Need to Know More About This Question / Discussion Community Feedback labels Oct 10, 2023
@coldwinds
Copy link

coldwinds commented Dec 1, 2023

Hello, @josecelano

Is the ip parameter could be supported as an option?

see here https://wiki.theory.org/BitTorrent_Tracker_Protocol

http://some.tracker.com:999/announce ?info_hash=12345678901234567890 &peer_id=ABCDEFGHIJKLMNOPQRST &ip=255.255.255.255 &port=6881 &downloaded=1234 &left=98765 &event=stopped

Which will give a chance to reverse proxy to pass peer's ip by rewrite peer's query and add/overwrite a ip parameter to it.

Opentracker's Makefile also have a build option for this:

https://erdgeist.org/arts/software/opentracker/

By default opentracker will only allow the connecting endpoint's IP address to be announced. Bittorrent standard allows clients to provide an IP address in its query string. You can make opentracker use this IP address by enabling -DWANT_IP_FROM_QUERY_STRING.

@josecelano
Copy link
Member Author

Hello, @josecelano

Is the ip parameter could be supported as an option?

see here https://wiki.theory.org/BitTorrent_Tracker_Protocol

http://some.tracker.com:999/announce ?info_hash=12345678901234567890 &peer_id=ABCDEFGHIJKLMNOPQRST &ip=255.255.255.255 &port=6881 &downloaded=1234 &left=98765 &event=stopped

Which will give a chance to reverse proxy to pass peer's ip by rewrite peer's query and add/overwrite a ip parameter to it.

Opentracker's Makefile also have a build option for this:

https://erdgeist.org/arts/software/opentracker/

By default opentracker will only allow the connecting endpoint's IP address to be announced. Bittorrent standard allows clients to provide an IP address in its query string. You can make opentracker use this IP address by enabling -DWANT_IP_FROM_QUERY_STRING.

Hi @coldwinds I think that's a good-to-have feature and I think it should not be hard to implement. Maybe you can open a discussion for a feature request so that we can discuss how to implement it. We could add a new option in the configuration for the core Tracker service or maybe that could be enabled independently for any of the HTTP and/or UDP trackers. I prefer the second option because it's more flexible:

[[udp_trackers]]
bind_address = "0.0.0.0:6868"
enabled = true
want_ip_from_query_string = true
# ...

[[udp_trackers]]
bind_address = "0.0.0.0:6969"
enabled = true
want_ip_from_query_string = false

[[http_trackers]]
bind_address = "0.0.0.0:7070"
enabled = true
want_ip_from_query_string = false
# ...

[[http_trackers]]
bind_address = "0.0.0.0:7171"
enabled = true
want_ip_from_query_string = true

We're focussed now on releasing version v3.0.0 but I will add it to the roadmap.

Just for the record:

The BEP 03 - HTTP Trackers says:

An optional parameter giving the IP (or dns name) which this peer is at. Generally used for the origin if it's on the same machine as the tracker.

The BEP 15 - UDP Trackers says:

Do note that most trackers will only honor the IP address field under limited circumstances.

cc @da2ce7 @WarmBeer

@coldwinds
Copy link

Hi @josecelano I just post a feature request here #532

And yes I think separately config on trackers is more flexible and looks good.

@josecelano
Copy link
Member Author

Using the Forwarded header instead of X-Forwarded-For:
https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/

@cgbosse cgbosse moved this to Help Wanted in Torrust Solution Jan 8, 2024
@cgbosse cgbosse moved this from Help Wanted to Feature & Enhancement request in Torrust Solution Jan 16, 2024
@cgbosse cgbosse moved this from Feature & Enhancement request to Help Wanted in Torrust Solution Jan 16, 2024
@cgbosse cgbosse added this to the v3.1.0 milestone Jan 16, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
- Admin - Enjoyable to Install and Setup our Software Needs Research We Need to Know More About This Question / Discussion Community Feedback
Projects
Status: Help Wanted
Development

No branches or pull requests

4 participants