mirror of
https://github.com/i2p/i2p.i2p.git
synced 2024-12-06 19:27:00 +01:00
Merge branch 'ssu1-4' into 'master'
Transport: SSU1 removal part 4/n See merge request i2p-hackers/i2p.i2p!186
This commit is contained in:
@@ -85,7 +85,6 @@ class IntroductionManager {
|
||||
private final RouterContext _context;
|
||||
private final Log _log;
|
||||
private final UDPTransport _transport;
|
||||
private final PacketBuilder _builder;
|
||||
private final PacketBuilder2 _builder2;
|
||||
/** map of relay tag to Charlie PeerState that should receive the introduction (we are Bob) */
|
||||
private final Map<Long, PeerState> _outbound;
|
||||
@@ -115,15 +114,11 @@ class IntroductionManager {
|
||||
private static final long INTRODUCER_EXPIRATION = 80*60*1000L;
|
||||
private static final String MIN_IPV6_INTRODUCER_VERSION = "0.9.50";
|
||||
private static final long MAX_SKEW = 2*60*1000;
|
||||
/** testing */
|
||||
private static final String PROP_PREFER_SSU2 = "i2np.ssu2.preferSSU2Introducers";
|
||||
private static final boolean DEFAULT_PREFER_SSU2 = true;
|
||||
|
||||
public IntroductionManager(RouterContext ctx, UDPTransport transport) {
|
||||
_context = ctx;
|
||||
_log = ctx.logManager().getLog(IntroductionManager.class);
|
||||
_transport = transport;
|
||||
_builder = transport.getBuilder();
|
||||
_builder2 = transport.getBuilder2();
|
||||
_outbound = new ConcurrentHashMap<Long, PeerState>(MAX_OUTBOUND);
|
||||
_inbound = new ConcurrentHashMap<Long, PeerState>(MAX_INBOUND);
|
||||
@@ -150,9 +145,7 @@ class IntroductionManager {
|
||||
if (added)
|
||||
_outbound.put(Long.valueOf(id), peer);
|
||||
long id2 = peer.getTheyRelayToUsAs();
|
||||
//if (id2 > 0 && _inbound.size() < MAX_INBOUND) {
|
||||
// test
|
||||
if (id2 > 0 && (_inbound.size() < MAX_INBOUND || peer.getVersion() == 2)) {
|
||||
if (id2 > 0 && _inbound.size() < MAX_INBOUND) {
|
||||
added = true;
|
||||
_inbound.put(Long.valueOf(id2), peer);
|
||||
}
|
||||
@@ -210,8 +203,7 @@ class IntroductionManager {
|
||||
if (_inbound.isEmpty()) return 0;
|
||||
List<PeerState> peers = new ArrayList<PeerState>(_inbound.values());
|
||||
int sz = peers.size();
|
||||
boolean preferV2 = _builder2 != null && _context.getProperty(PROP_PREFER_SSU2, DEFAULT_PREFER_SSU2);
|
||||
Collections.sort(peers, new PeerStateComparator(preferV2));
|
||||
Collections.sort(peers, new PeerStateComparator());
|
||||
int found = 0;
|
||||
long now = _context.clock().now();
|
||||
long inactivityCutoff = now - (UDPTransport.EXPIRE_TIMEOUT / 2); // 15 min
|
||||
@@ -221,8 +213,6 @@ class IntroductionManager {
|
||||
List<Introducer> introducers = new ArrayList<Introducer>(howMany);
|
||||
String exp = Long.toString((now + INTRODUCER_EXPIRATION) / 1000);
|
||||
|
||||
// try to keep a mix of v1 and v2
|
||||
int ssu1count = 0;
|
||||
int ssu2count = 0;
|
||||
// reuse old ones if ok
|
||||
if (current != null) {
|
||||
@@ -235,29 +225,10 @@ class IntroductionManager {
|
||||
if (!isInboundTagValid(tag))
|
||||
continue;
|
||||
String sexp = Long.toString(ua.getIntroducerExpiration(i) / 1000);
|
||||
Introducer intro;
|
||||
byte[] key = ua.getIntroducerKey(i);
|
||||
if (key != null) {
|
||||
// SSU 1
|
||||
//// Replace SSU 1 with SSU 2 if available for slot 2
|
||||
//// leave slots 0 and 1 for SSU 1
|
||||
//// this will churn the SSU 1 introducers, oh well
|
||||
if (preferV2 && ssu1count >= 2)
|
||||
continue;
|
||||
intro = new Introducer(ua.getIntroducerHost(i).getAddress(),
|
||||
ua.getIntroducerPort(i), key, tag, sexp);
|
||||
ssu1count++;
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Reusing introducer: " + ua.getIntroducerHost(i));
|
||||
} else {
|
||||
// SSU 2
|
||||
if (_builder != null && ssu2count >= 2)
|
||||
continue;
|
||||
intro = new Introducer(ua.getIntroducerHash(i), tag, sexp);
|
||||
ssu2count++;
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Reusing introducer: " + ua.getIntroducerHash(i));
|
||||
}
|
||||
Introducer intro = new Introducer(ua.getIntroducerHash(i), tag, sexp);
|
||||
ssu2count++;
|
||||
if (_log.shouldInfo())
|
||||
_log.info("Reusing introducer: " + ua.getIntroducerHash(i));
|
||||
introducers.add(intro);
|
||||
found++;
|
||||
}
|
||||
@@ -270,14 +241,10 @@ class IntroductionManager {
|
||||
continue;
|
||||
Hash hash = cur.getRemotePeer();
|
||||
// dup check of reused SSU2 introducers
|
||||
if (cur.getVersion() > 1) {
|
||||
String b64 = hash.toBase64();
|
||||
for (Introducer intro : introducers) {
|
||||
if (b64.equals(intro.shash))
|
||||
continue outerloop;
|
||||
}
|
||||
if (_builder != null && ssu2count >= 2)
|
||||
continue;
|
||||
String b64 = hash.toBase64();
|
||||
for (Introducer intro : introducers) {
|
||||
if (b64.equals(intro.shash))
|
||||
continue outerloop;
|
||||
}
|
||||
RouterInfo ri = _context.netDb().lookupRouterInfoLocally(hash);
|
||||
if (ri == null) {
|
||||
@@ -318,7 +285,6 @@ class IntroductionManager {
|
||||
continue;
|
||||
}
|
||||
int oldFound = found;
|
||||
loop:
|
||||
for (RouterAddress ra : ras) {
|
||||
byte[] ip = ra.getIP();
|
||||
if (ip == null)
|
||||
@@ -327,11 +293,6 @@ class IntroductionManager {
|
||||
String host = ip.length == 4 ? ra.getHost() : Addresses.toString(ip);
|
||||
if (host == null)
|
||||
continue;
|
||||
// dup check of reused introducers
|
||||
for (Introducer intro : introducers) {
|
||||
if (host.equals(intro.sip))
|
||||
continue loop;
|
||||
}
|
||||
int port = ra.getPort();
|
||||
if (!isValid(ip, port, true))
|
||||
continue;
|
||||
@@ -343,23 +304,11 @@ class IntroductionManager {
|
||||
continue;
|
||||
}
|
||||
cur.setIntroducerTime();
|
||||
Introducer intro;
|
||||
if (cur.getVersion() == 1) {
|
||||
UDPAddress ura = new UDPAddress(ra);
|
||||
byte[] ikey = ura.getIntroKey();
|
||||
if (ikey == null)
|
||||
continue;
|
||||
intro = new Introducer(ip, port, ikey, cur.getTheyRelayToUsAs(), exp);
|
||||
ssu1count++;
|
||||
} else {
|
||||
intro = new Introducer(hash, cur.getTheyRelayToUsAs(), exp);
|
||||
ssu2count++;
|
||||
}
|
||||
Introducer intro = new Introducer(hash, cur.getTheyRelayToUsAs(), exp);
|
||||
ssu2count++;
|
||||
introducers.add(intro);
|
||||
found++;
|
||||
// two per router max, one for SSU 2
|
||||
if (found - oldFound >= 2 || cur.getVersion() > 1)
|
||||
break;
|
||||
break;
|
||||
}
|
||||
if (oldFound != found && _log.shouldLog(Log.INFO))
|
||||
_log.info("Picking introducer: " + cur);
|
||||
@@ -369,13 +318,7 @@ class IntroductionManager {
|
||||
Collections.sort(introducers);
|
||||
for (int i = 0; i < found; i++) {
|
||||
Introducer in = introducers.get(i);
|
||||
if (in.version == 1) {
|
||||
ssuOptions.setProperty(UDPAddress.PROP_INTRO_HOST_PREFIX + i, in.sip);
|
||||
ssuOptions.setProperty(UDPAddress.PROP_INTRO_PORT_PREFIX + i, in.sport);
|
||||
ssuOptions.setProperty(UDPAddress.PROP_INTRO_KEY_PREFIX + i, in.skey);
|
||||
} else {
|
||||
ssuOptions.setProperty(UDPAddress.PROP_INTRO_HASH_PREFIX + i, in.shash);
|
||||
}
|
||||
ssuOptions.setProperty(UDPAddress.PROP_INTRO_HASH_PREFIX + i, in.shash);
|
||||
ssuOptions.setProperty(UDPAddress.PROP_INTRO_TAG_PREFIX + i, in.stag);
|
||||
String sexp = in.sexp;
|
||||
// look for existing expiration in current published
|
||||
@@ -383,20 +326,10 @@ class IntroductionManager {
|
||||
if (current != null) {
|
||||
for (int j = 0; j < UDPTransport.PUBLIC_RELAY_COUNT; j++) {
|
||||
String oexp = null;
|
||||
if (in.version == 1) {
|
||||
if (in.sip.equals(current.getOption(UDPAddress.PROP_INTRO_HOST_PREFIX + j)) &&
|
||||
in.sport.equals(current.getOption(UDPAddress.PROP_INTRO_PORT_PREFIX + j)) &&
|
||||
in.skey.equals(current.getOption(UDPAddress.PROP_INTRO_KEY_PREFIX + j)) &&
|
||||
in.stag.equals(current.getOption(UDPAddress.PROP_INTRO_TAG_PREFIX + j))) {
|
||||
// found old one
|
||||
oexp = current.getOption(UDPAddress.PROP_INTRO_EXP_PREFIX + j);
|
||||
}
|
||||
} else {
|
||||
if (in.shash.equals(current.getOption(UDPAddress.PROP_INTRO_HASH_PREFIX + j)) &&
|
||||
in.stag.equals(current.getOption(UDPAddress.PROP_INTRO_TAG_PREFIX + j))) {
|
||||
// found old one
|
||||
oexp = current.getOption(UDPAddress.PROP_INTRO_EXP_PREFIX + j);
|
||||
}
|
||||
if (in.shash.equals(current.getOption(UDPAddress.PROP_INTRO_HASH_PREFIX + j)) &&
|
||||
in.stag.equals(current.getOption(UDPAddress.PROP_INTRO_TAG_PREFIX + j))) {
|
||||
// found old one
|
||||
oexp = current.getOption(UDPAddress.PROP_INTRO_EXP_PREFIX + j);
|
||||
}
|
||||
if (oexp != null) {
|
||||
try {
|
||||
@@ -421,25 +354,14 @@ class IntroductionManager {
|
||||
|
||||
/**
|
||||
* For picking introducers.
|
||||
* Reverse sort, version 2 first, for testing
|
||||
* Then lowest uptime first, to reduce idle timeout and disconnect,
|
||||
* Lowest uptime first, to reduce idle timeout and disconnect,
|
||||
* and ensure variety.
|
||||
*
|
||||
* @since 0.9.55
|
||||
*/
|
||||
private static class PeerStateComparator implements Comparator<PeerState> {
|
||||
private final boolean _v2;
|
||||
|
||||
public PeerStateComparator(boolean preferV2) {
|
||||
_v2 = preferV2;
|
||||
}
|
||||
|
||||
public int compare(PeerState l, PeerState r) {
|
||||
if (_v2) {
|
||||
int rv = r.getVersion() - l.getVersion();
|
||||
if (rv != 0)
|
||||
return rv;
|
||||
}
|
||||
long d = r.getKeyEstablishedTime() - l.getKeyEstablishedTime();
|
||||
if (d < 0)
|
||||
return -1;
|
||||
@@ -454,21 +376,7 @@ class IntroductionManager {
|
||||
* @since 0.9.18
|
||||
*/
|
||||
private static class Introducer implements Comparable<Introducer> {
|
||||
public final String sip, sport, skey, stag, sexp, shash;
|
||||
public final int version;
|
||||
|
||||
/**
|
||||
* SSU 1
|
||||
*/
|
||||
public Introducer(byte[] ip, int port, byte[] key, long tag, String exp) {
|
||||
sip = Addresses.toString(ip);
|
||||
sport = String.valueOf(port);
|
||||
skey = Base64.encode(key);
|
||||
stag = String.valueOf(tag);
|
||||
sexp = exp;
|
||||
version = 1;
|
||||
shash = null;
|
||||
}
|
||||
public final String stag, sexp, shash;
|
||||
|
||||
/**
|
||||
* SSU 2
|
||||
@@ -478,18 +386,10 @@ class IntroductionManager {
|
||||
stag = String.valueOf(tag);
|
||||
sexp = exp;
|
||||
shash = h.toBase64();
|
||||
version = 2;
|
||||
sip = null;
|
||||
sport = null;
|
||||
skey = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Introducer i) {
|
||||
// put SSU 2 at the end to not confuse SSU 1
|
||||
int diff = version - i.version;
|
||||
if (diff != 0)
|
||||
return diff;
|
||||
return stag.compareTo(i.stag);
|
||||
}
|
||||
|
||||
@@ -530,10 +430,7 @@ class IntroductionManager {
|
||||
cur.setLastPingTime(now);
|
||||
UDPPacket ping;
|
||||
try {
|
||||
if (cur.getVersion() == 2)
|
||||
ping = _builder2.buildPing((PeerState2) cur);
|
||||
else
|
||||
ping = _builder.buildPing(cur);
|
||||
ping = _builder2.buildPing((PeerState2) cur);
|
||||
_transport.send(ping);
|
||||
} catch (IOException ioe) {
|
||||
iter.remove();
|
||||
@@ -569,233 +466,6 @@ class IntroductionManager {
|
||||
return _outbound.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Charlie and we got this from Bob.
|
||||
* Send a HolePunch to Alice, who will soon be sending us a SessionRequest.
|
||||
* We should already have a session with Bob, but probably not with Alice.
|
||||
*
|
||||
* If we don't have a session with Bob, we removed the relay tag from
|
||||
* our _outbound table, so this won't work.
|
||||
*
|
||||
* We do some throttling here.
|
||||
*
|
||||
* SSU 1 only.
|
||||
*/
|
||||
void receiveRelayIntro(RemoteHostId bob, UDPPacketReader reader) {
|
||||
if (_context.router().isHidden())
|
||||
return;
|
||||
_context.statManager().addRateData("udp.receiveRelayIntro", 1);
|
||||
|
||||
if (!_transport.allowConnection()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping RelayIntro, over conn limit");
|
||||
return;
|
||||
}
|
||||
|
||||
int ipSize = reader.getRelayIntroReader().readIPSize();
|
||||
byte ip[] = new byte[ipSize];
|
||||
reader.getRelayIntroReader().readIP(ip, 0);
|
||||
int port = reader.getRelayIntroReader().readPort();
|
||||
|
||||
// allow IPv6 as of 0.9.50
|
||||
// validate alice IP/port here. We don't need to validate Bob, we have a session with him.
|
||||
if (!isValid(ip, port, true)) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Invalid relay intro for alice " + Addresses.toString(ip, port) + " via bob " + bob);
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Receive relay intro from " + bob + " for " + Addresses.toString(ip, port));
|
||||
|
||||
InetAddress to = null;
|
||||
try {
|
||||
to = InetAddress.getByAddress(ip);
|
||||
} catch (UnknownHostException uhe) {
|
||||
// banlist Bob?
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("IP for alice to hole punch to is invalid", uhe);
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
|
||||
RemoteHostId alice = new RemoteHostId(ip, port);
|
||||
if (_transport.getPeerState(alice) != null) {
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ignoring RelayIntro, already have a session to " + to);
|
||||
return;
|
||||
}
|
||||
EstablishmentManager establisher = _transport.getEstablisher();
|
||||
if (establisher != null) {
|
||||
if (establisher.getInboundState(alice) != null) {
|
||||
// This check may be common, as Alice sends RelayRequests to
|
||||
// several introducers at once.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ignoring RelayIntro, establishment in progress to " + to);
|
||||
return;
|
||||
}
|
||||
if (!establisher.shouldAllowInboundEstablishment()) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping RelayIntro, too many establishments in progress - for " + to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// basic throttle, don't bother saving per-peer send times
|
||||
// we throttle on IP only, ignoring port
|
||||
boolean tooMany = false;
|
||||
boolean already = false;
|
||||
synchronized (_recentHolePunches) {
|
||||
long now = _context.clock().now();
|
||||
if (now > _lastHolePunchClean + PUNCH_CLEAN_TIME) {
|
||||
_recentHolePunches.clear();
|
||||
_lastHolePunchClean = now;
|
||||
_recentHolePunches.add(to);
|
||||
} else {
|
||||
tooMany = _recentHolePunches.size() >= MAX_PUNCHES;
|
||||
if (!tooMany)
|
||||
already = !_recentHolePunches.add(to);
|
||||
}
|
||||
}
|
||||
if (tooMany) {
|
||||
if (_log.shouldLog(Log.WARN))
|
||||
_log.warn("Dropping - too many - RelayIntro for " + to);
|
||||
return;
|
||||
}
|
||||
if (already) {
|
||||
// This check will trigger a lot, as Alice sends RelayRequests to
|
||||
// several introducers at once.
|
||||
if (_log.shouldLog(Log.INFO))
|
||||
_log.info("Ignoring dup RelayIntro for " + to);
|
||||
return;
|
||||
}
|
||||
|
||||
_transport.send(_builder.buildHolePunch(to, port));
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob and we got this from Alice.
|
||||
* Send a RelayIntro to Charlie and a RelayResponse to Alice.
|
||||
* We should already have a session with Charlie, but not necessarily with Alice.
|
||||
*
|
||||
* SSU 1 only.
|
||||
*/
|
||||
void receiveRelayRequest(RemoteHostId alice, UDPPacketReader reader) {
|
||||
if (_context.router().isHidden())
|
||||
return;
|
||||
UDPPacketReader.RelayRequestReader rrReader = reader.getRelayRequestReader();
|
||||
long tag = rrReader.readTag();
|
||||
int ipSize = rrReader.readIPSize();
|
||||
int port = rrReader.readPort();
|
||||
|
||||
byte[] aliceIP = alice.getIP();
|
||||
int alicePort = alice.getPort();
|
||||
boolean ipIncluded = ipSize != 0;
|
||||
// here we allow IPv6
|
||||
if (!isValid(aliceIP, alicePort, true)) {
|
||||
// not necessarily invalid ip/port, could be blocklisted
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Rejecting relay req from " + alice + " for " + Addresses.toString(aliceIP, alicePort));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
// prior to 0.9.24 we rejected any non-zero-length ip
|
||||
// here we reject anything different if it's the same size
|
||||
// As of 0.9.50 we allow relay request over IPv6
|
||||
if (ipIncluded) {
|
||||
byte ip[] = new byte[ipSize];
|
||||
rrReader.readIP(ip, 0);
|
||||
if (ipSize == aliceIP.length && !Arrays.equals(aliceIP, ip)) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad relay req from " + alice + " for " + Addresses.toString(ip, port));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
aliceIP = ip;
|
||||
}
|
||||
// prior to 0.9.24 we rejected any nonzero port
|
||||
// here we reject anything different
|
||||
// As of 0.9.50 we allow it if the IP was included
|
||||
if (port != 0) {
|
||||
if (ipIncluded) {
|
||||
alicePort = port;
|
||||
} else if (port != alicePort) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad relay req from " + alice + " for " + Addresses.toString(aliceIP, port));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// check again if IP was provided
|
||||
// allow IPv6 as of 0.9.50
|
||||
RemoteHostId aliceRelayID;
|
||||
if (ipIncluded) {
|
||||
if (!isValid(aliceIP, alicePort, true)) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Bad relay req from " + alice + " for " + Addresses.toString(aliceIP, alicePort));
|
||||
_context.statManager().addRateData("udp.relayBadIP", 1);
|
||||
return;
|
||||
}
|
||||
aliceRelayID = new RemoteHostId(aliceIP, alicePort);
|
||||
} else {
|
||||
aliceRelayID = alice;
|
||||
}
|
||||
|
||||
PeerState charlie = get(tag);
|
||||
if (charlie == null) {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Receive relay request from " + alice
|
||||
+ " with unknown tag " + tag);
|
||||
_context.statManager().addRateData("udp.receiveRelayRequestBadTag", 1);
|
||||
return;
|
||||
}
|
||||
if (charlie.getVersion() != 1) {
|
||||
if (_log.shouldWarn())
|
||||
_log.warn("Receive SSU1 relay request from " + alice + " for SSU2 " + charlie);
|
||||
return;
|
||||
}
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Receive relay request from " + alice
|
||||
+ " for tag " + tag
|
||||
+ " and relaying with " + charlie);
|
||||
|
||||
// TODO throttle based on alice identity and/or intro tag?
|
||||
|
||||
_context.statManager().addRateData("udp.receiveRelayRequest", 1);
|
||||
|
||||
// send that peer an introduction for alice
|
||||
_transport.send(_builder.buildRelayIntro(aliceRelayID, charlie, rrReader));
|
||||
long now = _context.clock().now();
|
||||
charlie.setLastSendTime(now);
|
||||
|
||||
// send alice back charlie's info
|
||||
// lookup session so we can use session key if available
|
||||
SessionKey cipherKey = null;
|
||||
SessionKey macKey = null;
|
||||
PeerState aliceState = _transport.getPeerState(alice);
|
||||
if (aliceState != null) {
|
||||
// established session (since 0.9.12)
|
||||
cipherKey = aliceState.getCurrentCipherKey();
|
||||
macKey = aliceState.getCurrentMACKey();
|
||||
}
|
||||
if (cipherKey == null || macKey == null) {
|
||||
// no session, use intro key (was only way before 0.9.12)
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
reader.getRelayRequestReader().readAliceIntroKey(key, 0);
|
||||
cipherKey = new SessionKey(key);
|
||||
macKey = cipherKey;
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Sending relay response (w/ intro key) to " + alice);
|
||||
} else {
|
||||
if (_log.shouldDebug())
|
||||
_log.debug("Sending relay response (in-session) to " + alice);
|
||||
aliceState.setLastSendTime(now);
|
||||
}
|
||||
_transport.send(_builder.buildRelayResponse(alice, charlie, rrReader.readNonce(),
|
||||
cipherKey, macKey));
|
||||
}
|
||||
|
||||
/**
|
||||
* We are Bob and we got this from Alice.
|
||||
* Send Alice's RI and a RelayIntro to Charlie, or reject with a RelayResponse to Alice.
|
||||
|
||||
Reference in New Issue
Block a user