Skip to content

Commit

Permalink
AmneziaWG Gateway probe (#1416)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmwample committed Oct 30, 2024
1 parent fe7dc5f commit 96f37a7
Show file tree
Hide file tree
Showing 18 changed files with 516 additions and 188 deletions.
1 change: 1 addition & 0 deletions nym-vpn-core/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion nym-vpn-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ prost-types = "0.12.6"
rand = "0.8.5"
rand_chacha = "0.3.1"
reqwest = { version = "0.11.27", default-features = false }
rust2go = "0.3.4"
rust2go = "0.3.16"
serde = "1.0"
serde_json = "1.0"
sha2 = "0.10"
Expand Down
56 changes: 56 additions & 0 deletions nym-vpn-core/crates/nym-gateway-probe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Nym Gateway Probe

Probe IPv4 and IPv6 interfaces of available gateways to check for the
set that passes a set of minumum service guarantees.


## Build

These instructions assume a debian based system. Adjust accordingly for your
preffered platform.

Install required dependencies
```sh
sudo apt install libdbus-1-dev libmnl-dev libnftnl-dev protobuf-compiler clang
```


Build piece by piece
```sh
cd nym-vpn-core/
# build the prober
cargo build -p nym-gateway-probe
```

You may need to adjust your `RUSTFLAGS` or `.cargo/config.toml` to ensure that
the golang wireguard library links properly.

## Usage

```sh
Usage: nym-gateway-probe [OPTIONS]

Options:
-c, --config-env-file <CONFIG_ENV_FILE>
Path pointing to an env file describing the network
-g, --gateway <GATEWAY>
The specific gateway specified by ID
-n, --no-log
Disable logging during probe
-a, --amnezia-args <AMNEZIA_ARGS>
Arguments to be appended to the wireguard config enabling amnezia-wg configuration
-h, --help
Print help
-V, --version
Print version
```

Examples

```sh
# Run a basic probe against the node with id "qj3GgGYg..."
nym-gateway-probe -g "qj3GgGYgGZZ3HkFrtD1GU9UJ5oNXME9eD2xtmPLqYYw"

# Run a probe against the node with id "qj3GgGYg..." using amnezia with junk packets enabled.
nym-gateway-probe -g "qj3GgGYgGZZ3HkFrtD1GU9UJ5oNXME9eD2xtmPLqYYw" -a "Jc=4\nJmin=40\mJmax=70\n"
```
4 changes: 4 additions & 0 deletions nym-vpn-core/crates/nym-gateway-probe/netstack_ping/gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ typedef struct NetstackRequestRef {
uint8_t num_ping;
uint64_t send_timeout_sec;
uint64_t recv_timeout_sec;
struct StringRef awg_args;
} NetstackRequestRef;
// hack from: https://stackoverflow.com/a/69904977
Expand Down Expand Up @@ -225,6 +226,7 @@ type NetstackRequest struct {
num_ping uint8
send_timeout_sec uint64
recv_timeout_sec uint64
awg_args string
}

func newNetstackRequest(p C.NetstackRequestRef) NetstackRequest {
Expand All @@ -239,6 +241,7 @@ func newNetstackRequest(p C.NetstackRequestRef) NetstackRequest {
num_ping: newC_uint8_t(p.num_ping),
send_timeout_sec: newC_uint64_t(p.send_timeout_sec),
recv_timeout_sec: newC_uint64_t(p.recv_timeout_sec),
awg_args: newString(p.awg_args),
}
}
func cntNetstackRequest(s *NetstackRequest, cnt *uint) [0]C.NetstackRequestRef {
Expand All @@ -258,6 +261,7 @@ func refNetstackRequest(p *NetstackRequest, buffer *[]byte) C.NetstackRequestRef
num_ping: refC_uint8_t(&p.num_ping, buffer),
send_timeout_sec: refC_uint64_t(&p.send_timeout_sec, buffer),
recv_timeout_sec: refC_uint64_t(&p.recv_timeout_sec, buffer),
awg_args: refString(&p.awg_args, buffer),
}
}

Expand Down
7 changes: 5 additions & 2 deletions nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.mod
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
module github.com/nymtech/nym-vpn-client/nym-vpn-core/crates/nym-gateway-probe/netstack_ping

go 1.22
go 1.22.3

toolchain go1.23.1

require (
github.com/amnezia-vpn/amneziawg-go v0.2.12
golang.org/x/net v0.23.0
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173
)

require (
github.com/google/btree v1.0.1 // indirect
github.com/tevino/abool/v2 v2.1.0 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 // indirect
Expand Down
6 changes: 4 additions & 2 deletions nym-vpn-core/crates/nym-gateway-probe/netstack_ping/go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
github.com/amnezia-vpn/amneziawg-go v0.2.12 h1:CxIQETy5kZ0ip/dFBpmnDxAcS/KuIQaJkOxDv5OQhVI=
github.com/amnezia-vpn/amneziawg-go v0.2.12/go.mod h1:d7WpNfzCRLy7ufGElJBYpD58WRmNjyLyt3IDHPY8AmM=
github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4=
github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA=
github.com/tevino/abool/v2 v2.1.0 h1:7w+Vf9f/5gmKT4m4qkayb33/92M+Um45F2BkHOR+L/c=
github.com/tevino/abool/v2 v2.1.0/go.mod h1:+Lmlqk6bHDWHqN1cbxqhwEAwMPXgc8I1SDEamtseuXY=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
Expand All @@ -10,7 +14,5 @@ golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0k
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2 h1:B82qJJgjvYKsXS9jeunTOisW56dUokqW/FOteYJJ/yg=
golang.zx2c4.com/wintun v0.0.0-20230126152724-0fa3db229ce2/go.mod h1:deeaetjYA+DHMHg+sMSMI58GrEteJUUzzw7en6TJQcI=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173 h1:/jFs0duh4rdb8uIfPMv78iAJGcPKDeqAFnaLBropIC4=
golang.zx2c4.com/wireguard v0.0.0-20231211153847-12269c276173/go.mod h1:tkCQ4FQXmpAgYVh++1cq16/dH4QJtmvpRv19DWGAHSA=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259 h1:TbRPT0HtzFP3Cno1zZo7yPzEEnfu8EjLfl6IU9VfqkQ=
gvisor.dev/gvisor v0.0.0-20230927004350-cbd86285d259/go.mod h1:AVgIgHMwK63XvmAzWG9vLQ41YnVHN0du0tEC46fI7yY=
38 changes: 26 additions & 12 deletions nym-vpn-core/crates/nym-gateway-probe/netstack_ping/impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ import (
"strings"
"time"

"github.com/amnezia-vpn/amneziawg-go/conn"
"github.com/amnezia-vpn/amneziawg-go/device"
"github.com/amnezia-vpn/amneziawg-go/tun/netstack"
"golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"golang.zx2c4.com/wireguard/conn"
"golang.zx2c4.com/wireguard/device"
"golang.zx2c4.com/wireguard/tun/netstack"
)

type Netstack struct{}
Expand All @@ -36,6 +36,10 @@ func (Netstack) ping(req NetstackRequest) NetstackResponse {

ipc.WriteString("private_key=")
ipc.WriteString(req.private_key)
if req.awg_args != "" {
awg := strings.ReplaceAll(req.awg_args, "\\n", "\n")
ipc.WriteString(fmt.Sprintf("\n%s", awg))
}
ipc.WriteString("\npublic_key=")
ipc.WriteString(req.public_key)
ipc.WriteString("\nendpoint=")
Expand All @@ -45,6 +49,13 @@ func (Netstack) ping(req NetstackRequest) NetstackResponse {
response := NetstackResponse{false, 0, 0, 0, 0, false}

dev.IpcSet(ipc.String())

config, err := dev.IpcGet()
if err != nil {
log.Panic(err)
}
log.Printf("%s", config)

err = dev.Up()
if err != nil {
log.Panic(err)
Expand All @@ -69,15 +80,18 @@ func (Netstack) ping(req NetstackRequest) NetstackResponse {

for _, ip := range req.ping_ips {
for i := uint8(0); i < req.num_ping; i++ {
log.Printf("Pinging %s seq=%d", ip, i)
response.sent_ips += 1
rt, err := sendPing(ip, i, req.send_timeout_sec, req.recv_timeout_sec, tnet)
if err != nil {
log.Printf("Failed to send ping: %v\n", err)
continue
}
response.received_ips += 1
log.Printf("Ping latency: %v\n", rt)
func() {
defer time.Sleep(5 * time.Second)
log.Printf("Pinging %s seq=%d", ip, i)
response.sent_ips += 1
rt, err := sendPing(ip, i, req.send_timeout_sec, req.recv_timeout_sec, tnet)
if err != nil {
log.Printf("Failed to send ping: %v\n", err)
return
}
response.received_ips += 1
log.Printf("Ping latency: %v\n", rt)
}()
}
}

Expand Down
140 changes: 84 additions & 56 deletions nym-vpn-core/crates/nym-gateway-probe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,78 +58,105 @@ pub async fn fetch_gateways_with_ipr() -> anyhow::Result<GatewayList> {
Ok(lookup_gateways().await?.into_exit_gateways())
}

pub async fn probe(entry_point: EntryPoint) -> anyhow::Result<ProbeResult> {
// Setup the entry gateways
let gateways = lookup_gateways().await?;
let entry_gateway = entry_point.lookup_gateway(&gateways).await?;
let exit_router_address = entry_gateway.ipr_address;
let authenticator = entry_gateway.authenticator_address;
let gateway_host = entry_gateway.host.clone().unwrap();
let entry_gateway_id = entry_gateway.identity();

info!("Probing gateway: {entry_gateway:?}");

// Connect to the mixnet
let mixnet_client = MixnetClientBuilder::new_ephemeral()
.request_gateway(entry_gateway_id.to_string())
.network_details(NymNetworkDetails::new_from_env())
.debug_config(mixnet_debug_config())
.build()?
.connect_to_mixnet()
.await;

let mixnet_client = match mixnet_client {
Ok(mixnet_client) => mixnet_client,
Err(err) => {
error!("Failed to connect to mixnet: {err}");
return Ok(ProbeResult {
gateway: entry_gateway_id.to_string(),
outcome: ProbeOutcome {
as_entry: Entry::fail_to_connect(),
as_exit: None,
wg: None,
},
});
pub struct Probe {
entrypoint: EntryPoint,
amnezia_args: String,
}

impl Probe {
pub fn new(entrypoint: EntryPoint) -> Self {
Self {
entrypoint,
amnezia_args: "".into(),
}
};
}

let nym_address = *mixnet_client.nym_address();
let entry_gateway = nym_address.gateway().to_base58_string();
pub fn with_amnezia(&mut self, args: &str) -> &Self {
self.amnezia_args = args.to_string();
self
}

info!("Successfully connected to entry gateway: {entry_gateway}");
info!("Our nym address: {nym_address}");
pub async fn probe(self) -> anyhow::Result<ProbeResult> {
let entry_point = self.entrypoint;

// Setup the entry gateways
let gateways = lookup_gateways().await?;
let entry_gateway = entry_point.lookup_gateway(&gateways).await?;
let exit_router_address = entry_gateway.ipr_address;
let authenticator = entry_gateway.authenticator_address;
let gateway_host = entry_gateway.host.clone().unwrap();
let entry_gateway_id = entry_gateway.identity();

info!("Probing gateway: {entry_gateway:?}");

// Connect to the mixnet
let mixnet_client = MixnetClientBuilder::new_ephemeral()
.request_gateway(entry_gateway_id.to_string())
.network_details(NymNetworkDetails::new_from_env())
.debug_config(mixnet_debug_config())
.build()?
.connect_to_mixnet()
.await;

let mixnet_client = match mixnet_client {
Ok(mixnet_client) => mixnet_client,
Err(err) => {
error!("Failed to connect to mixnet: {err}");
return Ok(ProbeResult {
gateway: entry_gateway_id.to_string(),
outcome: ProbeOutcome {
as_entry: Entry::fail_to_connect(),
as_exit: None,
wg: None,
},
});
}
};

let shared_client = Arc::new(tokio::sync::Mutex::new(Some(mixnet_client)));
let nym_address = *mixnet_client.nym_address();
let entry_gateway = nym_address.gateway().to_base58_string();

// Now that we have a connected mixnet client, we can start pinging
let shared_mixnet_client = SharedMixnetClient::from_shared(&shared_client);
let outcome = do_ping(shared_mixnet_client.clone(), exit_router_address).await;
info!("Successfully connected to entry gateway: {entry_gateway}");
info!("Our nym address: {nym_address}");

let wg_outcome = if let Some(authenticator) = authenticator {
wg_probe(authenticator, shared_client, &gateway_host)
let shared_client = Arc::new(tokio::sync::Mutex::new(Some(mixnet_client)));

// Now that we have a connected mixnet client, we can start pinging
let shared_mixnet_client = SharedMixnetClient::from_shared(&shared_client);
let outcome = do_ping(shared_mixnet_client.clone(), exit_router_address).await;

let wg_outcome = if let Some(authenticator) = authenticator {
wg_probe(
authenticator,
shared_client,
&gateway_host,
self.amnezia_args,
)
.await
.unwrap_or_default()
} else {
WgProbeResults::default()
};
} else {
WgProbeResults::default()
};

let mixnet_client = shared_mixnet_client.lock().await.take().unwrap();
mixnet_client.disconnect().await;
let mixnet_client = shared_mixnet_client.lock().await.take().unwrap();
mixnet_client.disconnect().await;

// Disconnect the mixnet client gracefully
outcome.map(|mut outcome| {
outcome.wg = Some(wg_outcome);
ProbeResult {
gateway: entry_gateway.clone(),
outcome,
}
})
// Disconnect the mixnet client gracefully
outcome.map(|mut outcome| {
outcome.wg = Some(wg_outcome);
ProbeResult {
gateway: entry_gateway.clone(),
outcome,
}
})
}
}

async fn wg_probe(
authenticator: AuthAddress,
shared_mixnet_client: Arc<Mutex<Option<MixnetClient>>>,
gateway_host: &nym_topology::NetworkAddress,
awg_args: String,
) -> anyhow::Result<WgProbeResults> {
let auth_shared_client =
nym_authenticator_client::SharedMixnetClient::from_shared(&shared_mixnet_client);
Expand Down Expand Up @@ -227,6 +254,7 @@ async fn wg_probe(
private_key: private_key_hex,
public_key: public_key_hex,
endpoint: wg_endpoint.clone(),
awg_args,
..Default::default()
};

Expand Down
Loading

0 comments on commit 96f37a7

Please sign in to comment.