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

TorControlProtocol: Implement HSFETCH #2275

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,16 @@
import java.util.Optional;

public class BootstrapEventParser {
public static Optional<BootstrapEvent> tryParse(String line) {
public static Optional<BootstrapEvent> tryParse(String[] parts) {
// 650 STATUS_CLIENT NOTICE BOOTSTRAP PROGRESS=50 TAG=loading_descriptors SUMMARY="Loading relay descriptors"
if (isStatusClientEvent(line)) {
String[] parts = line.split(" ");

if (isBootstrapEvent(parts)) {
BootstrapEvent bootstrapEvent = parseBootstrapEvent(parts);
return Optional.of(bootstrapEvent);
}
if (isBootstrapEvent(parts)) {
BootstrapEvent bootstrapEvent = parseBootstrapEvent(parts);
return Optional.of(bootstrapEvent);
}

return Optional.empty();
}

private static boolean isStatusClientEvent(String line) {
// 650 STATUS_CLIENT NOTICE CIRCUIT_ESTABLISHED
return line.startsWith("650 STATUS_CLIENT");
}

private static boolean isBootstrapEvent(String[] parts) {
// 650 STATUS_CLIENT NOTICE BOOTSTRAP PROGRESS=50 TAG=loading_descriptors SUMMARY="Loading relay descriptors"
return parts.length >= 7 && parts[3].equals("BOOTSTRAP");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package bisq.tor.controller;

import bisq.tor.controller.events.events.HsDescCreatedEvent;
import bisq.tor.controller.events.events.HsDescEvent;
import bisq.tor.controller.events.events.HsDescUploadEvent;
import bisq.tor.controller.events.events.HsDescUploadedEventV2;

import java.util.Optional;

public class HsDescEventParser {
public static Optional<HsDescEvent> tryParse(String[] parts) {
if (HsDescEvent.Action.CREATED.isAction(parts)) {
// 650 HS_DESC CREATED <onion_address> UNKNOWN UNKNOWN <descriptor_id>
HsDescCreatedEvent hsDescEvent = HsDescCreatedEvent.builder()
.action(HsDescEvent.Action.CREATED)
.hsAddress(parts[3])
.authType(parts[4])
.hsDir(parts[5])
.descriptorId(parts[6])
.build();
return Optional.of(hsDescEvent);

} else if (HsDescEvent.Action.UPLOAD.isAction(parts)) {
// 650 HS_DESC UPLOAD <onion_address> UNKNOWN <hs_dir> <descriptor_id> HSDIR_INDEX=<index>
HsDescUploadEvent hsDescEvent = HsDescUploadEvent.builder()
.action(HsDescEvent.Action.UPLOAD)
.hsAddress(parts[3])
.authType(parts[4])
.hsDir(parts[5])
.descriptorId(parts[6])
.hsDirIndex(parts[7])
.build();
return Optional.of(hsDescEvent);

} else if (HsDescEvent.Action.UPLOADED.isAction(parts)) {
// 650 HS_DESC UPLOADED <onion_address> UNKNOWN <hs_dir>
HsDescUploadedEventV2 hsDescEvent = HsDescUploadedEventV2.builder()
.action(HsDescEvent.Action.UPLOADED)
.hsAddress(parts[3])
.authType(parts[4])
.hsDir(parts[5])
.build();
return Optional.of(hsDescEvent);
} else {
return Optional.empty();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import bisq.common.encoding.Hex;
import bisq.security.keys.TorKeyPair;
import bisq.tor.controller.events.listener.BootstrapEventListener;
import bisq.tor.controller.events.listener.HsDescEventListener;
import net.freehaven.tor.control.PasswordDigest;

import java.io.IOException;
Expand All @@ -20,9 +21,8 @@ public class TorControlProtocol implements AutoCloseable {
private final OutputStream outputStream;

// MidReplyLine = StatusCode "-" ReplyLine
private final Pattern midReplyLinePattern = Pattern.compile("^\\d+-.+");
// DataReplyLine = StatusCode "+" ReplyLine CmdData
private final Pattern dataReplyLinePattern = Pattern.compile("^\\d+\\+.+");
private final Pattern multiLineReplyPattern = Pattern.compile("^\\d+[-+].+");

public TorControlProtocol(int port) throws IOException {
controlSocket = new Socket("127.0.0.1", port);
Expand Down Expand Up @@ -72,6 +72,15 @@ public String getInfo(String keyword) throws IOException {
return assertTwoLineOkReply(replyStream, "GETINFO");
}

public void hsFetch(String hsAddress) throws IOException {
String command = "HSFETCH " + hsAddress + "\r\n";
sendCommand(command);
String reply = receiveReply().findFirst().orElseThrow();
if (!reply.equals("250 OK")) {
throw new ControlCommandFailedException("Couldn't initiate HSFETCH for : " + hsAddress);
}
}

public void resetConf(String configName) throws IOException {
String command = "RESETCONF " + configName + "\r\n";
sendCommand(command);
Expand Down Expand Up @@ -120,6 +129,14 @@ public void removeBootstrapEventListener(BootstrapEventListener listener) {
whonixTorControlReader.removeBootstrapEventListener(listener);
}

public void addHsDescEventListener(HsDescEventListener listener) {
whonixTorControlReader.addHsDescEventListener(listener);
}

public void removeHsDescEventListener(HsDescEventListener listener) {
whonixTorControlReader.removeHsDescEventListener(listener);
}

private void sendCommand(String command) throws IOException {
byte[] commandBytes = command.getBytes(StandardCharsets.US_ASCII);
outputStream.write(commandBytes);
Expand Down Expand Up @@ -148,7 +165,7 @@ private String tryReadNextReply() {
}

private boolean isMultilineReply(String reply) {
return midReplyLinePattern.matcher(reply).matches() || dataReplyLinePattern.matcher(reply).matches();
return multiLineReplyPattern.matcher(reply).matches();
}

private String assertTwoLineOkReply(Stream<String> replyStream, String commandName) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
package bisq.tor.controller;

import bisq.common.observable.Observable;
import bisq.security.keys.TorKeyPair;
import bisq.tor.TorrcClientConfigFactory;
import bisq.tor.controller.events.events.BootstrapEvent;
import bisq.tor.controller.events.events.HsDescEvent;
import bisq.tor.controller.events.listener.BootstrapEventListener;
import bisq.tor.controller.events.listener.HsDescEventListener;
import bisq.tor.controller.exceptions.HsDescUploadFailedException;
import bisq.tor.controller.exceptions.TorBootstrapFailedException;
import bisq.tor.process.NativeTorProcess;
import lombok.Getter;
Expand All @@ -15,21 +19,27 @@
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

@Slf4j
public class TorController implements BootstrapEventListener {
private final int bootstrapTimeout;
public class TorController implements BootstrapEventListener, HsDescEventListener {
private final int bootstrapTimeout; // in ms
private final int hsUploadTimeout; // in ms
private final CountDownLatch isBootstrappedCountdownLatch = new CountDownLatch(1);
@Getter
private final Observable<BootstrapEvent> bootstrapEvent = new Observable<>();

private final Map<String, CountDownLatch> pendingOnionServicePublishLatchMap = new ConcurrentHashMap<>();

private Optional<TorControlProtocol> torControlProtocol = Optional.empty();

public TorController(int bootstrapTimeout) {
public TorController(int bootstrapTimeout, int hsUploadTimeout) {
this.bootstrapTimeout = bootstrapTimeout;
this.hsUploadTimeout = hsUploadTimeout;
}

public void initialize(int controlPort) throws IOException {
Expand Down Expand Up @@ -57,6 +67,24 @@ public void bootstrapTor() throws IOException {
waitUntilBootstrapped();
}

public void publish(TorKeyPair torKeyPair, int onionServicePort, int localPort) throws IOException, InterruptedException {
String onionAddress = torKeyPair.getOnionAddress();
var onionServicePublishedLatch = new CountDownLatch(1);
pendingOnionServicePublishLatchMap.put(onionAddress, onionServicePublishedLatch);

subscribeToHsDescEvents();
TorControlProtocol torControlProtocol = getTorControlProtocol();
torControlProtocol.addOnion(torKeyPair, onionServicePort, localPort);

boolean isSuccess = onionServicePublishedLatch.await(hsUploadTimeout, TimeUnit.MILLISECONDS);
if (!isSuccess) {
throw new HsDescUploadFailedException("HS_DESC upload timer triggered.");
}

torControlProtocol.removeHsDescEventListener(this);
torControlProtocol.setEvents(Collections.emptyList());
}

public Optional<Integer> getSocksPort() {
try {
TorControlProtocol torControlProtocol = getTorControlProtocol();
Expand Down Expand Up @@ -90,6 +118,19 @@ public void onBootstrapStatusEvent(BootstrapEvent bootstrapEvent) {
}
}

@Override
public void onHsDescEvent(HsDescEvent hsDescEvent) {
log.info("Tor HS_DESC event: {}", hsDescEvent);
if (hsDescEvent.getAction() == HsDescEvent.Action.UPLOADED) {
String onionAddress = hsDescEvent.getHsAddress() + ".onion";
CountDownLatch countDownLatch = pendingOnionServicePublishLatchMap.get(onionAddress);
if (countDownLatch != null) {
countDownLatch.countDown();
pendingOnionServicePublishLatchMap.remove(onionAddress);
}
}
}

private void initialize(int controlPort, Optional<PasswordDigest> hashedControlPassword) throws IOException {
var torControlProtocol = new TorControlProtocol(controlPort);
this.torControlProtocol = Optional.of(torControlProtocol);
Expand All @@ -112,6 +153,12 @@ private void subscribeToBootstrapEvents() throws IOException {
torControlProtocol.setEvents(List.of("STATUS_CLIENT"));
}

private void subscribeToHsDescEvents() throws IOException {
TorControlProtocol torControlProtocol = getTorControlProtocol();
torControlProtocol.addHsDescEventListener(this);
torControlProtocol.setEvents(List.of("HS_DESC"));
}

private void enableNetworking() throws IOException {
TorControlProtocol torControlProtocol = getTorControlProtocol();
torControlProtocol.setConfig(TorrcClientConfigFactory.DISABLE_NETWORK_CONFIG_KEY, "0");
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package bisq.tor.controller;

import bisq.tor.controller.events.events.BootstrapEvent;
import bisq.tor.controller.events.events.HsDescEvent;
import bisq.tor.controller.events.listener.BootstrapEventListener;
import bisq.tor.controller.events.listener.HsDescEventListener;
import lombok.extern.slf4j.Slf4j;

import java.io.BufferedReader;
Expand All @@ -20,6 +22,7 @@ public class WhonixTorControlReader implements AutoCloseable {
private final BufferedReader bufferedReader;
private final BlockingQueue<String> replies = new LinkedBlockingQueue<>();
private final List<BootstrapEventListener> bootstrapEventListeners = new CopyOnWriteArrayList<>();
private final List<HsDescEventListener> hsDescEventListeners = new CopyOnWriteArrayList<>();

private Optional<Thread> workerThread = Optional.empty();

Expand All @@ -34,11 +37,31 @@ public void start() {
while ((line = bufferedReader.readLine()) != null) {

if (isEvent(line)) {
Optional<BootstrapEvent> bootstrapEventOptional = BootstrapEventParser.tryParse(line);
if (bootstrapEventOptional.isPresent()) {
BootstrapEvent bootstrapEvent = bootstrapEventOptional.get();
bootstrapEventListeners.forEach(listener -> listener.onBootstrapStatusEvent(bootstrapEvent));
} else {
String[] parts = line.split(" ");

boolean parsedEvent = false;
if (parts.length > 2) {
String eventType = parts[1];

if (isStatusClientEvent(eventType)) {
Optional<BootstrapEvent> bootstrapEventOptional = BootstrapEventParser.tryParse(parts);
if (bootstrapEventOptional.isPresent()) {
parsedEvent = true;
BootstrapEvent bootstrapEvent = bootstrapEventOptional.get();
bootstrapEventListeners.forEach(listener -> listener.onBootstrapStatusEvent(bootstrapEvent));
}

} else if (isHsDescEvent(eventType)) {
Optional<HsDescEvent> hsDescEventOptional = HsDescEventParser.tryParse(parts);
if (hsDescEventOptional.isPresent()) {
parsedEvent = true;
HsDescEvent hsDescEvent = hsDescEventOptional.get();
hsDescEventListeners.forEach(listener -> listener.onHsDescEvent(hsDescEvent));
}
}
}

if (!parsedEvent) {
log.info("Unknown Tor event: {}", line);
}

Expand Down Expand Up @@ -80,8 +103,26 @@ public void removeBootstrapEventListener(BootstrapEventListener listener) {
bootstrapEventListeners.remove(listener);
}

public void addHsDescEventListener(HsDescEventListener listener) {
hsDescEventListeners.add(listener);
}

public void removeHsDescEventListener(HsDescEventListener listener) {
hsDescEventListeners.remove(listener);
}

private boolean isEvent(String line) {
// 650 STATUS_CLIENT NOTICE CIRCUIT_ESTABLISHED
return line.startsWith("650");
}

private static boolean isStatusClientEvent(String eventType) {
// 650 STATUS_CLIENT NOTICE CIRCUIT_ESTABLISHED
return eventType.equals("STATUS_CLIENT");
}

private static boolean isHsDescEvent(String eventType) {
// 650 HS_DESC CREATED <onion_address> UNKNOWN UNKNOWN <descriptor_id>
return eventType.equals("HS_DESC");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package bisq.tor.controller.events.events;

import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;


@SuperBuilder
@Getter
@ToString(callSuper = true)
public class HsDescCreatedEvent extends HsDescEvent {
private final String descriptorId;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package bisq.tor.controller.events.events;

import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@ToString
public abstract class HsDescEvent {

@Getter
public enum Action {
CREATED(7),
UPLOAD(8),
UPLOADED(6);

private final int numberOfArgs;

Action(int numberOfArgs) {
this.numberOfArgs = numberOfArgs;
}

public boolean isAction(String[] parts) {
// Example: 650 HS_DESC CREATED <onion_address> UNKNOWN UNKNOWN <descriptor_id>
return parts[2].equals(this.toString()) && parts.length == numberOfArgs;
}
}

protected final Action action;
protected final String hsAddress;
protected final String authType;
protected final String hsDir;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package bisq.tor.controller.events.events;

import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@ToString(callSuper = true)
public class HsDescUploadEvent extends HsDescCreatedEvent {
private final String hsDirIndex;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package bisq.tor.controller.events.events;

import lombok.Getter;
import lombok.ToString;
import lombok.experimental.SuperBuilder;

@SuperBuilder
@Getter
@ToString(callSuper = true)
public class HsDescUploadedEventV2 extends HsDescEvent {
}
Loading
Loading