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

I2P: add support for transient addresses for outbound connections #25355

Merged
merged 7 commits into from
Aug 26, 2022

Conversation

vasild
Copy link
Contributor

@vasild vasild commented Jun 13, 2022

Add support for generating a transient, one-time I2P address for ourselves when making I2P outbound connection and discard it once the connection is closed.

Background

In I2P connections, the host that receives the connection knows the I2P address of the connection initiator. This is unlike the Tor network where the recipient does not know who is connecting to them, not even the initiator's Tor address.

Persistent vs transient I2P addresses

Even if an I2P node is not accepting incoming connections, they are known to other nodes by their outgoing I2P address. This creates an opportunity to white-list given nodes or treat them differently based on their I2P address. However, this also creates an opportunity to fingerprint or analyze a given node because it always uses the same I2P address when it connects to other nodes. Thus, if a node is not accepting incoming I2P connections (-i2pacceptincoming=0) we will generate a transient (disposable), one-time I2P address for each new outgoing connection. That address is never going to be reused again, not even if reconnecting to the same peer later. If -i2pacceptincoming=1 is used then we will use the persistent listening address for outgoing connections and will advertise it to the peers we connect to (like before this PR).

@DrahtBot DrahtBot added the P2P label Jun 13, 2022
@laanwj
Copy link
Member

laanwj commented Jun 13, 2022

Concept ACK. Interesting idea. Note that we do a similar thing for Tor by creating a new circuit for every outgoing connection (by providing random SOCKS5 auth).

@kristapsk
Copy link
Contributor

Concept ACK

@kristapsk
Copy link
Contributor

Are there some noticable tradeoffs / downsides with this? Why not just enable this by default?

@vasild
Copy link
Contributor Author

vasild commented Jun 14, 2022

I was just thinking the same.

The reason I set the default value of i2ptransientout to 0 is in case somebody is too attached to the persistent addresses or if the new code has a bug - let it mature for a release or two and then flip the default.

I don't have a strong opinion, maybe it is ok to introduce it as i2ptransientout=1 as well.

On the low-level, each session requires an extra control socket:

  • with i2ptransientout=0 we need 1 + n_incoming + n_outgoing (one control socket to rule them all)
  • with i2ptransientout=1 we need 1 + n_incoming + 2 * n_outgoing (one control socket for all incoming + one control socket for each outgoing).

I think this is ok wrt max open sockets on the system because we don't have 100s or 1000s of outgoing connections.

doc/i2p.md Outdated Show resolved Hide resolved
doc/i2p.md Outdated Show resolved Hide resolved
@ghost
Copy link

ghost commented Jun 14, 2022

Add support for generating a transient, one-time I2P address for ourselves when making I2P outbound connection and discard it once the connection is closed.

Concept ACK

@vasild vasild force-pushed the i2p_transient_outbound_addr branch from ab91728 to 9be11f7 Compare June 16, 2022 11:56
@vasild
Copy link
Contributor Author

vasild commented Jun 16, 2022

ab9172846f...9be11f7496: rebase due to conflicts

@DrahtBot
Copy link
Contributor

DrahtBot commented Jun 16, 2022

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #25619 (net: avoid overriding non-virtual ToString() in CService and use better naming by vasild)
  • #25572 (refactor: Introduce EvictionManager and use it for the inbound eviction logic by dergoegge)
  • #25174 (net/net_processing: Add thread safety related annotations for CNode and Peer by ajtowns)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@luke-jr
Copy link
Member

luke-jr commented Jun 18, 2022

I suggest enabling it by default, and having a net permission flag to use the "real" source address (requires #17167 I guess?).

@vasild vasild force-pushed the i2p_transient_outbound_addr branch from 9be11f7 to a69528c Compare June 28, 2022 11:27
@vasild
Copy link
Contributor Author

vasild commented Jun 28, 2022

9be11f7496...a69528c9f6: rebase and change the default of -i2ptransientout to true.

@vasild
Copy link
Contributor Author

vasild commented Jul 14, 2022

Further idea (out of the scope of this PR): periodically disconnect long lived outbound connections to Tor and to I2P if -i2ptransientout=1. Having a prolonged outbound connection to an adversary is the same as not changing our address for the duration of the connection. Even if, by chance, we reconnect to the same peer after the disconnect, they would not know it is us again.

@vasild
Copy link
Contributor Author

vasild commented Jul 15, 2022

It may be desirable to have this in 24.0, feature freeze in mid August.

@jonatack
Copy link
Member

jonatack commented Jul 15, 2022 via email

@vasild
Copy link
Contributor Author

vasild commented Aug 11, 2022

18ee27ce7f...6f3e418908: ditch -i2ptransientout and use transient addresses if -i2pacceptincoming=0. Based on the discussion in #25355 (comment).

Copy link
Contributor

@mzumsande mzumsande left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested ACK 6f3e418

src/net.cpp Show resolved Hide resolved
Copy link
Member

@achow101 achow101 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ACK 6f3e418

If you need to retouch, the commit message for a9d2d2c should say "in CNode" rather than "is CNode".

src/net.h Outdated Show resolved Hide resolved
and destroy it when `CNode::m_sock` is closed.

I2P transient sessions are created per connection (i.e. per `CNode`) and
should be destroyed when the connection is closed. Storing the session
in `CNode` is a convenient way to destroy it together with the connection
socket (`CNode::m_sock`).

An alternative approach would be to store a list of all I2P sessions in
`CConnman` and from `CNode::CloseSocketDisconnect()` to somehow ask the
`CConnman` to destroy the relevant session.
If not accepting I2P connections, then do not create
`CConnman::m_i2p_sam_session`.

When opening a new outbound I2P connection either use
`CConnman::m_i2p_sam_session` like before or create a temporary one and
store it in `CNode` for destruction later.
The test is a bit primitive as it checks the Bitcoin Core log and
assumes that if it logs that it creates a transient session, then it
does that indeed.

A more thorough test would be to check that it indeed sends the
`SESSION CREATE ... DESTINATION=TRANSIENT` command and that it uses
the returned I2P address for connecting, even for repeated connections
to the same I2P peer. That would require a mocked SAM server (proxy)
implementation in Python.
This way the log messages are consistent with "Creating SAM session..."
@vasild vasild force-pushed the i2p_transient_outbound_addr branch from 6f3e418 to 59aa54f Compare August 16, 2022 11:07
@vasild
Copy link
Contributor Author

vasild commented Aug 16, 2022

6f3e418908...59aa54f731: address minor suggestions (hopefully trivial to re-ack)

Invalidates ACKs from @mzumsande, @achow101

Previously invalidated ACK from @jonatack

@mzumsande
Copy link
Contributor

ACK 59aa54f (verified via range-diff that just a typo / unique_ptr initialisation were fixed)

@achow101
Copy link
Member

re-ACK 59aa54f

Copy link
Member

@jonatack jonatack left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK 59aa54f reviewed range diff, rebased to master, debug build + relevant tests + review at each commit

Will test tomorrow.

* Whether this is a transient session (the I2P private key will not be
* read or written to disk).
*/
const bool m_transient;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2b781ad Don't make data members const.

Suggested change
const bool m_transient;
bool m_transient;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That guideline says "They are not useful" (const data members), but they improve readability - at a glance one can tell that this variable is only assigned in the constructor and never modified afterwards. Without the const one would have to manually check all references to the variable to find the places where it is modified.

Then the guideline says that const data members make the type difficult to use because it is not copy-assignable. But this is a feature and exactly what we want - to avoid copying a persistent session to a transient one, effectively converting it. Anyway, i2p::sam::Session is already non-copy-able, so adding a const data member does not change its copy-ability.


m_my_addr = CService(DestBinToAddr(MyDestination()), I2P_SAM31_PORT);
m_session_id = session_id;
m_control_sock = std::move(sock);

LogPrintfCategory(BCLog::I2P, "SAM session created: session id=%s, my address=%s\n",
m_session_id, m_my_addr.ToString());
Log("%s SAM session %s created, my address=%s",
Copy link
Member

@jonatack jonatack Aug 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested with -i2pacceptincoming on and off.

When logging the creation of a transient SAM session, it might be helpful to log to which peer, i.e. something like

Transient SAM session 74fc489e60 created to peer=<id>, peeraddr=<ip>, my address=4ejzt3gyxn3icwcn2s473tqayt32ti55e7zlliwusuzlppgagypq.b32.i2p:0

otherwise in a log with many i2p peers, it's unclear to which peer the transient address is connecting to.

2022-08-17T09:54:26Z [i2p] Transient SAM session 74fc489e60 created, my address=4ejzt3gyxn3icwcn2s473tqayt32ti55e7zlliwusuzlppgagypq.b32.i2p:0
2022-08-17T09:54:28Z New outbound peer connected: version: 70016, blocks=749823, peer=8, peeraddr=83.240...6:8333 (outbound-full-relay)
2022-08-17T09:54:28Z New outbound peer connected: version: 70016, blocks=749823, peer=9, peeraddr=190.2...4:8333 (outbound-full-relay)
2022-08-17T09:54:34Z New outbound peer connected: version: 70016, blocks=749823, peer=13, peeraddr=31.42....9:8333 (outbound-full-relay)
2022-08-17T09:54:34Z [i2p] Creating transient SAM session 15cf78cf17 with 127.0.0.1:7656
2022-08-17T09:54:39Z New outbound peer connected: version: 70016, blocks=749823, peer=14, peeraddr=76.89....:8333 (outbound-full-relay)
2022-08-17T09:54:54Z [i2p] Transient SAM session 15cf78cf17 created, my address=vnugpix23m4inijbw24d2s6nha6u3rzqpkfsvz3r2sonxvx7o6ma.b32.i2p:0
2022-08-17T09:54:57Z [i2p] Destroying SAM session 15cf78cf17
2022-08-17T09:54:58Z [i2p] Creating transient SAM session 6d503018cc with 127.0.0.1:7656
2022-08-17T09:55:16Z New outbound peer connected: version: 70016, blocks=749823, peer=15, peeraddr=wwbw7nqr....7tpoqjswvcwa.b32.i2p:0 (manual)
2022-08-17T09:55:18Z [i2p] Transient SAM session 6d503018cc created, my address=h75ad7btqhxbkyapqsrnaprhtots5xxoq7yp6qjl3z77idzazg6q.b32.i2p:0
2022-08-17T09:55:33Z New outbound peer connected: version: 70016, blocks=749823, peer=17, peeraddr=jz3s4eurm5vzj...wakylgkxmq.b32.i2p:0 (manual)

Perhaps also similar for the Destroying SAM session <id> to peer=<id>, peeraddr=<addr> log message.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that would be helpful. It would require seeping knowledge of the peer's address from the higher level code in net.cpp down to i2p::sam::Session. However, a transient session may as well be used for more than one connection. We may at some point decide to use one transient session for all connections over 5 minutes, then ditch it and roll to another one.

The higher level code in net.cpp already logs the peer address just before creating the I2P session:

bitcoin/src/net.cpp

Lines 454 to 456 in a8f6954

LogPrintLevel(BCLog::NET, BCLog::Level::Debug, "trying connection %s lastseen=%.1fhrs\n",
pszDest ? pszDest : addrConnect.ToString(),
Ticks<HoursDouble>(pszDest ? 0h : Now<NodeSeconds>() - addrConnect.nTime));

So, it should already be possible to link the session id with the peer address, the two messages will be adjacent in the log:

trying connection foozt3gyxn3icwcn2s473tqayt32ti55e7zlliwusuzlppgagypq.b32.i2p:0 ...
Creating transient SAM session 15cf78cf17 ...

@theStack
Copy link
Contributor

Concept ACK

corresponding private key) will be automatically generated and saved in a file
named `i2p_private_key` in the Bitcoin Core data directory.
The first time Bitcoin Core connects to the I2P router, if
`-i2pacceptincoming=1`, then it will automatically generate a persistent I2P
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we specify here what is default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On one hand that would be useful, on the other hand if the source code is changed and this forgotten, they would go out of sync.

This PR has 4 ACKs. Should I open a followup PR with this change after this PR is merged or leave it as is?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right, this is certainly an insufficient reason to invalidate 4 acks.

not know who is connecting to them and can't tell if two connections are from
the same peer or not.

If an I2P node is not accepting incoming connections, then Bitcoin Core uses
Copy link
Member

@naumenkogs naumenkogs Aug 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I'm reading it correctly. What if it does accept incoming connections? Why don't do transient for outbound still?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I guess, you are reading it correctly :)

What if it does accept incoming connections?

Then for outbound it will use its persistent address.

Why don't do transient for outbound still?

That is a good question and a subtle one. In earlier incarnations of this PR we did that, but then @mzumsande shot it down 🔫, pointing out that we would self-advertise our persistent address over the outbound connection that uses a transient one, defeating the purpose of using a transient address. OTOH if we do not self-advertise anything, then listening would be in vain because nobody would know our listening (persistent) address because we never told it to anybody. See #25355 (comment) for the whole discussion.

@naumenkogs
Copy link
Member

Concept ACK, light code review ACK 47c0d02

@achow101 achow101 merged commit eed2bd3 into bitcoin:master Aug 26, 2022
sidhujag pushed a commit to syscoin/syscoin that referenced this pull request Aug 26, 2022
@vasild vasild deleted the i2p_transient_outbound_addr branch August 28, 2022 10:11
@mzumsande
Copy link
Contributor

mzumsande commented Sep 6, 2022

Post-Merge comment: There is no longer a -i2ptransientout option, it was forgotten to update the PR description according to the discussion.
Since people will refer to this PR (e.g. in the OpTech newsletter, BitDevs etc.), I think it would be good to update the text of the OP post-merge.

@vasild
Copy link
Contributor Author

vasild commented Sep 7, 2022

Updated, thanks!

@fanquake
Copy link
Member

Another post-merge comment here: 2b781ad#r83655059.

I think you want SIGNATURE_TYPE=7 in here, without it you're going to get a DSA-SHA1 destination back, at least from Java I2P. See the SAM spec. Not sure what i2pd does. You can tell because a DSA-SHA1 destination will be a few bytes shorter than a Ed25519 one.

@ghost ghost mentioned this pull request Sep 29, 2022
@bitcoin bitcoin locked and limited conversation to collaborators Sep 12, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.