diff --git a/client/Configurator.c b/client/Configurator.c index ea3ecf76..d24a62ef 100644 --- a/client/Configurator.c +++ b/client/Configurator.c @@ -432,12 +432,29 @@ static void seeders(List* seeders, struct Allocator* tempAlloc, struct Context* } } -static void publicPeer(String_t* peerID, Allocator_t* tempAlloc, struct Context* ctx) +static void publicPeer(Dict_t* routerConf, Allocator_t* tempAlloc, struct Context* ctx) { + String_t* peerID = NULL; + String_t* ipv4 = NULL; + String_t* ipv6 = NULL; + Dict_t* peerInfo = Dict_getDictC(routerConf, "publicPeer"); + if (peerInfo) { + peerID = Dict_getStringC(peerInfo, "id"); + ipv4 = Dict_getStringC(peerInfo, "ipv4"); + ipv6 = Dict_getStringC(peerInfo, "ipv6"); + } else { + peerID = Dict_getStringC(routerConf, "publicPeer"); + } if (!peerID) { return; } Log_debug(ctx->logger, "Setting PeerID [%s]", peerID->bytes); Dict* reqDict = Dict_new(tempAlloc); Dict_putStringC(reqDict, "peerID", peerID, tempAlloc); + if (ipv4) { + Dict_putStringC(reqDict, "ipv4", ipv4, tempAlloc); + } + if (ipv6) { + Dict_putStringC(reqDict, "ipv6", ipv6, tempAlloc); + } rpcCall0(String_CONST("PeeringSeeder_publicPeer"), reqDict, ctx, tempAlloc, NULL, true); } @@ -448,7 +465,7 @@ static void routerConfig(Dict* routerConf, struct Allocator* tempAlloc, struct C ipTunnel(Dict_getDictC(routerConf, "ipTunnel"), tempAlloc, ctx); supernodes(Dict_getListC(routerConf, "supernodes"), tempAlloc, ctx); seeders(Dict_getListC(routerConf, "dnsSeeds"), tempAlloc, ctx); - publicPeer(Dict_getStringC(routerConf, "publicPeer"), tempAlloc, ctx); + publicPeer(routerConf, tempAlloc, ctx); } static void ethInterfaceSetBeacon(int ifNum, Dict* eth, struct Context* ctx) diff --git a/client/cjdroute2.c b/client/cjdroute2.c index 9bd08b63..7e61168b 100644 --- a/client/cjdroute2.c +++ b/client/cjdroute2.c @@ -262,15 +262,37 @@ static int genconf(struct Allocator* alloc, struct Random* rand, bool eth, bool " \"seed.cjdns.fr\"\n" " ],\n" "\n" - " // When publicPeer is set, this node will post its public peering credentials\n" + " // When publicPeer id is set, this node will post its public peering credentials\n" " // to its supernode. The specified peerID will be used to identify itself.\n" " // For PKT yielding this must be set to the registered peerID, otherwise\n" " // you can set it to anything. By *convention*, peerIDs that begin with \"PUB_\"\n" " // are shared publicly and those which do not are tested by the snode but not\n" " // shared, allowing you to use the snode's peer tester on an otherwise private\n" - " // node.\n" + " // node. If you leave \"id\" commented, your peering credentials will remain\n" + " // entirely private.\n" " //\n" - " // \"publicPeer\": \"PUB_XXX\",\n" + " \"publicPeer\": {\n" + " // \"id\": \"PUB_XXX\",\n" + "\n" + " // If you set the public peer, you may also hardcode the IPv4 address.\n" + " // By default, cjdns will request its public IP address from its peers, but\n" + " // in cases with non-standard routing, you may have a different IP address\n" + " // for traffic initiated from outside. In this case, you must manually enter\n" + " // the IP address. If the address is entered in the form of \"x.x.x.x\", then\n" + " // the IP address will be used, but the port will be detected. If it is entered\n" + " // as \"0.0.0.0:xxx\" then the port will be used, but the address will be detected\n" + " // finally, if it is in the form of \"x.x.x.x:xxx\" then the address AND port will\n" + " // be used.\n" + " //\n" + " // \"ipv4\": \"1.2.3.4:56789\",\n" + "\n" + " // If you have a public IPv6 address which cannot be detected, you may hard-code\n" + " // it here. The same rules apply as IPv4 addresses: \"xxxx:xxxx::\" means use ip\n" + " // but detect port. \"[::]:xxx\" means use port but detect ip, and\n" + " // \"[xxxx:xxxx::]:xxx\" means use ip and port from configuration.\n" + " //\n" + " // \"ipv6\": \"[1234:5678::]:9012\",\n" + " },\n" "\n" " // supernodes, if none are specified they'll be taken from your peers\n" " \"supernodes\": [\n" diff --git a/rust/cjdns_sys/Cargo.toml b/rust/cjdns_sys/Cargo.toml index 60f4c90e..b7aa72bc 100644 --- a/rust/cjdns_sys/Cargo.toml +++ b/rust/cjdns_sys/Cargo.toml @@ -33,7 +33,7 @@ trust-dns-resolver = { workspace = true } ipnetwork = { workspace = true } num_enum = { workspace = true } -[build_dependencies] +[build-dependencies] cc = { workspace = true } anyhow = { workspace = true } cbindgen = { workspace = true } diff --git a/rust/cjdns_sys/Rffi.h b/rust/cjdns_sys/Rffi.h index 8ad492f0..dded3eab 100644 --- a/rust/cjdns_sys/Rffi.h +++ b/rust/cjdns_sys/Rffi.h @@ -306,6 +306,8 @@ RTypes_Error_t *Rffi_Seeder_public_peer(Rffi_Seeder *seeder, uint16_t user_num, uint64_t passwd, const String_t *code, + const String_t *addr4, + const String_t *addr6, Allocator_t *alloc); RTypes_Error_t *Rffi_Seeder_public_status(Rffi_Seeder *seeder, diff --git a/rust/cjdns_sys/src/rffi/seeder.rs b/rust/cjdns_sys/src/rffi/seeder.rs index 7181e6cb..e4cb0287 100644 --- a/rust/cjdns_sys/src/rffi/seeder.rs +++ b/rust/cjdns_sys/src/rffi/seeder.rs @@ -1,4 +1,6 @@ -use anyhow::anyhow; +use std::{net::{SocketAddrV4, SocketAddrV6}, str::FromStr}; + +use anyhow::{anyhow, Result}; use cjdns_keys::CJDNSPublicKey; use crate::{ @@ -127,6 +129,19 @@ pub unsafe extern "C" fn Rffi_Seeder_mk_creds( std::ptr::null_mut() } +fn parse_addr(s: *const String_t) -> Result> + where ::Err: 'static + std::error::Error + Send + Sync, +{ + let code = match cstr(s) { + Some(code) => String::from_utf8(code.0)?, + None => { + return Ok(None); + } + }; + let out: T = code.parse()?; + Ok(Some(out)) +} + #[no_mangle] pub unsafe extern "C" fn Rffi_Seeder_public_peer( seeder: *mut Rffi_Seeder, @@ -135,6 +150,8 @@ pub unsafe extern "C" fn Rffi_Seeder_public_peer( user_num: u16, passwd: u64, code: *const String_t, + addr4: *const String_t, + addr6: *const String_t, alloc: *mut Allocator_t, ) -> *mut RTypes_Error_t { let s = identity::from_c!(seeder); @@ -142,7 +159,21 @@ pub unsafe extern "C" fn Rffi_Seeder_public_peer( Some(code) => code, None => c_bail!(alloc, anyhow!("code must not be null")), }; - let (user, pass) = s.seeder.public_peer(user_num, passwd, code.0); + let addr4: Option = match parse_addr(addr4) { + Ok(x) => x, + Err(e) => c_bail!(alloc, e), + }; + let addr6: Option = match parse_addr(addr6) { + Ok(x) => x, + Err(e) => c_bail!(alloc, e), + }; + let (user, pass) = s.seeder.public_peer( + user_num, + passwd, + code.0, + addr4, + addr6, + ); *user_out = strc(alloc, user); *pass_out = strc(alloc, pass); std::ptr::null_mut() diff --git a/rust/cjdns_sys/src/subnode/seeder.rs b/rust/cjdns_sys/src/subnode/seeder.rs index d4ab2355..14f5f4a0 100644 --- a/rust/cjdns_sys/src/subnode/seeder.rs +++ b/rust/cjdns_sys/src/subnode/seeder.rs @@ -1,5 +1,6 @@ use std::{collections::HashMap, net::{IpAddr, Ipv4Addr, SocketAddr, SocketAddrV4, SocketAddrV6}, sync::Arc, time::Duration}; use anyhow::{anyhow, bail, Result, Context}; +use boringtun::device::Sock; use cjdns_keys::{CJDNSPublicKey, PublicKey}; use parking_lot::Mutex; use tokio::sync::mpsc; @@ -285,11 +286,56 @@ struct MyPeeringPasswd { code: Vec, } +macro_rules! split_ip { + ($ip:expr) => { + if let Some(ip) = $ip { + ( + if !ip.ip().is_unspecified() { + Some(ip.ip()) + } else { + None + }, + if ip.port() > 0 { + Some(ip.port()) + } else { + None + } + ) + } else { + Default::default() + } + } +} + #[derive(Default)] struct MyPeeringInfo { passwd: Option, v4: Option, v6: Option, + manual_v4: Option, + manual_v6: Option, +} +impl MyPeeringInfo { + fn get_lladdr4(&self) -> Option { + match (split_ip!(&self.manual_v4), split_ip!(&self.v4)) { + ((_, None), (_, None)) => None, + ((None, _), (None, _)) => None, + ((Some(ip), Some(port)), (_, _)) => Some(SocketAddrV4::new(ip.clone(), port)), + ((None, Some(port)), (Some(ip), _)) => Some(SocketAddrV4::new(ip.clone(), port)), + ((Some(ip), None), (_, Some(port))) => Some(SocketAddrV4::new(ip.clone(), port)), + ((None, None), (Some(ip), Some(port))) => Some(SocketAddrV4::new(ip.clone(), port)), + } + } + fn get_lladdr6(&self) -> Option { + match (split_ip!(&self.manual_v6), split_ip!(&self.v6)) { + ((_, None), (_, None)) => None, + ((None, _), (None, _)) => None, + ((Some(ip), Some(port)), (_, _)) => Some(SocketAddrV6::new(ip.clone(), port, 0, 0)), + ((None, Some(port)), (Some(ip), _)) => Some(SocketAddrV6::new(ip.clone(), port, 0, 0)), + ((Some(ip), None), (_, Some(port))) => Some(SocketAddrV6::new(ip.clone(), port, 0, 0)), + ((None, None), (Some(ip), Some(port))) => Some(SocketAddrV6::new(ip.clone(), port, 0, 0)), + } + } } pub struct SeederStatus { @@ -310,7 +356,7 @@ impl Seeder { /// Whether we have an address from got_lladdr pub fn has_lladdr(&self) -> bool { let m = self.mpi.lock(); - m.v4.is_some() || m.v6.is_some() + m.get_lladdr4().is_some() || m.get_lladdr6().is_some() } fn got_lladdr4(&self, lla: &Control_LlAddr_Udp4_t) -> bool { let sa = @@ -392,10 +438,10 @@ impl Seeder { bail!("Missing passwd"); }; let mut addrs = Vec::new(); - if let Some(x) = &m.v4 { + if let Some(x) = &m.get_lladdr4() { addrs.push(SocketAddr::V4(x.clone())); } - if let Some(x) = &m.v6 { + if let Some(x) = &m.get_lladdr6() { addrs.push(SocketAddr::V6(x.clone())); } if addrs.is_empty() { @@ -421,9 +467,13 @@ impl Seeder { user_num: u16, passwd: u64, code: Vec, + sa4: Option, + sa6: Option, ) -> (String, String) { let mut m = self.mpi.lock(); m.passwd = Some(MyPeeringPasswd { user_num, passwd, code }); + m.manual_v4 = sa4; + m.manual_v6 = sa6; let p = cjdns_bytes::dnsseed::CjdnsPeer { address: SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1,1,1,1)), 1), pubkey: [0_u8; 32], @@ -450,8 +500,8 @@ impl Seeder { pub fn get_status(&self) -> SeederStatus { let m = self.mpi.lock(); - let v4 = m.v4.map(|x|x.to_string()); - let v6 = m.v6.map(|x|x.to_string()); + let v4 = m.get_lladdr4().map(|x|x.to_string()); + let v6 = m.get_lladdr6().map(|x|x.to_string()); let peer_id = m.passwd.as_ref().map(|p|p.code.clone()); drop(m); let peer_id = if let Some(peer_id) = peer_id { diff --git a/subnode/PeeringSeeder.c b/subnode/PeeringSeeder.c index e0e4b01c..c5a75170 100644 --- a/subnode/PeeringSeeder.c +++ b/subnode/PeeringSeeder.c @@ -227,7 +227,12 @@ Err_DEFUN PeeringSeeder_publicStatus(PeeringSeeder_PublicStatus_t** outP, Peerin return NULL; } -Err_DEFUN PeeringSeeder_publicPeer(PeeringSeeder_t* self, String_t* code, Allocator_t* reqAlloc) +Err_DEFUN PeeringSeeder_publicPeer( + PeeringSeeder_t* self, + String_t* code, + String_t* addr4, + String_t* addr6, + Allocator_t* reqAlloc) { PeeringSeeder_pvt_t* pq = Identity_check((PeeringSeeder_pvt_t*) self); if (code == NULL) { @@ -250,6 +255,8 @@ Err_DEFUN PeeringSeeder_publicPeer(PeeringSeeder_t* self, String_t* code, Alloca pq->userNum, pq->pass, code, + addr4, + addr6, reqAlloc)); Assert_true(user->len < sizeof pq->login); diff --git a/subnode/PeeringSeeder.h b/subnode/PeeringSeeder.h index 8052b852..0e107d94 100644 --- a/subnode/PeeringSeeder.h +++ b/subnode/PeeringSeeder.h @@ -45,7 +45,12 @@ void PeeringSeeder_setSnode(PeeringSeeder_t* self, struct Address* snode); #define PeeringSeeder_publicPeer_ID_MAX_LEN 30 -Err_DEFUN PeeringSeeder_publicPeer(PeeringSeeder_t* self, String* code, Allocator_t* reqAlloc); +Err_DEFUN PeeringSeeder_publicPeer( + PeeringSeeder_t* self, + String_t* code, + String_t* addr4, + String_t* addr6, + Allocator_t* reqAlloc); // Get as a void pointer so that we don't need to pull in Rffi void* PeeringSeeder_getRsSeeder(PeeringSeeder_t* self); diff --git a/subnode/PeeringSeeder_admin.c b/subnode/PeeringSeeder_admin.c index 39842a48..04b1f7df 100644 --- a/subnode/PeeringSeeder_admin.c +++ b/subnode/PeeringSeeder_admin.c @@ -108,9 +108,11 @@ static void publicPeer(Dict* args, void* vcontext, String* txid, struct Allocato { struct Context* ctx = Identity_check((struct Context*) vcontext); String* peerCode = Dict_getStringC(args, "peerID"); + String* ipv4 = Dict_getStringC(args, "ipv4"); + String* ipv6 = Dict_getStringC(args, "ipv6"); char* err = "none"; RTypes_Error_t* er = - PeeringSeeder_publicPeer(ctx->sp, peerCode, requestAlloc); + PeeringSeeder_publicPeer(ctx->sp, peerCode, ipv4, ipv6, requestAlloc); if (er) { err = Rffi_printError(er, requestAlloc); } @@ -171,7 +173,9 @@ void PeeringSeeder_admin_register(PeeringSeeder_t* sp, Admin_registerFunction("PeeringSeeder_publicPeer", publicPeer, ctx, true, ((struct Admin_FunctionArg[]) { - { .name = "peerID", .required = false, .type = "String" } + { .name = "peerID", .required = false, .type = "String" }, + { .name = "ipv4", .required = false, .type = "String" }, + { .name = "ipv6", .required = false, .type = "String" } }), admin); Admin_registerFunctionNoArgs("PeeringSeeder_publicStatus", publicStatus, ctx, true, admin);