Compare commits

...
Sign in to create a new pull request.

1 commit

Author SHA1 Message Date
Jett Chen
cade672806 Add Support for IPV6 and Arbitrary Server Address
Add IPV6 support for Apple Devices
Note: Works in GUI not CLI
Adds Support for Arbitrary Server Address
2024-02-17 10:44:24 -08:00
20 changed files with 276 additions and 56 deletions

View file

@ -31,7 +31,7 @@ struct BurrowStartRequest: Codable {
let no_pi: Bool
let tun_excl: Bool
let tun_retrieve: Bool
let address: String?
let address: [String]
}
struct StartOptions: Codable {
let tun: TunOptions
@ -51,7 +51,7 @@ struct BurrowResult<T>: Codable where T: Codable {
struct ServerConfigData: Codable {
struct InternalConfig: Codable {
let address: String?
let address: [String]
let name: String?
let mtu: Int32?
}

View file

@ -31,7 +31,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
command: BurrowStartRequest(
Start: BurrowStartRequest.StartOptions(
tun: BurrowStartRequest.TunOptions(
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: nil
name: nil, no_pi: false, tun_excl: false, tun_retrieve: true, address: []
)
)
)
@ -46,12 +46,21 @@ class PacketTunnelProvider: NEPacketTunnelProvider {
private func generateTunSettings(from: ServerConfigData) -> NETunnelNetworkSettings? {
let cfig = from.ServerConfig
guard let addr = cfig.address else {
return nil
}
// Using a makeshift remote tunnel address
let nst = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "1.1.1.1")
nst.ipv4Settings = NEIPv4Settings(addresses: [addr], subnetMasks: ["255.255.255.0"])
var v4Addresses = [String]()
var v6Addresses = [String]()
for addr in cfig.address {
if IPv4Address(addr) != nil {
v6Addresses.append(addr)
}
if IPv6Address(addr) != nil {
v4Addresses.append(addr)
}
}
nst.ipv4Settings = NEIPv4Settings(addresses: v4Addresses, subnetMasks: v4Addresses.map { _ in
"255.255.255.0"
})
nst.ipv6Settings = NEIPv6Settings(addresses: v6Addresses, networkPrefixLengths: v6Addresses.map { _ in 64 })
logger.log("Initialized ipv4 settings: \(nst.ipv4Settings)")
return nst
}

16
Cargo.lock generated
View file

@ -1074,7 +1074,7 @@ dependencies = [
"httpdate",
"itoa",
"pin-project-lite",
"socket2 0.5.5",
"socket2",
"tokio",
"tower-service",
"tracing",
@ -2114,16 +2114,6 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6ecd384b10a64542d77071bd64bd7b231f4ed5940fba55e98c3de13824cf3d7"
[[package]]
name = "socket2"
version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "socket2"
version = "0.5.5"
@ -2305,7 +2295,7 @@ dependencies = [
"mio",
"num_cpus",
"pin-project-lite",
"socket2 0.5.5",
"socket2",
"tokio-macros",
"tracing",
"windows-sys 0.48.0",
@ -2547,7 +2537,7 @@ dependencies = [
"reqwest",
"schemars",
"serde",
"socket2 0.4.10",
"socket2",
"ssri",
"tempfile",
"tokio",

View file

@ -1,4 +1,4 @@
FROM docker.io/library/rust:1.74.0-slim-bookworm AS builder
FROM docker.io/library/rust:1.76.0-slim-bookworm AS builder
ARG TARGETPLATFORM
ARG LLVM_VERSION=16

View file

@ -1,4 +1,4 @@
tun_num := $(shell ifconfig | awk -F 'utun|[: ]' '/utun[0-9]/ {print $$2}' | tail -n 1)
tun := $(shell ifconfig -l | sed 's/ /\n/g' | grep utun | tail -n 1)
cargo_console := RUST_BACKTRACE=1 RUST_LOG=debug RUSTFLAGS='--cfg tokio_unstable' cargo run --all-features
cargo_norm := RUST_BACKTRACE=1 RUST_LOG=debug cargo run
@ -19,15 +19,28 @@ start:
test-dns:
@sudo route delete 8.8.8.8
@sudo route add 8.8.8.8 -interface utun$(tun_num)
@sudo route add 8.8.8.8 -interface $(tun)
@dig @8.8.8.8 hackclub.com
test-https:
@sudo route delete 193.183.0.162
@sudo route add 193.183.0.162 -interface utun$(tun_num)
@sudo route add 193.183.0.162 -interface $(tun)
@curl -vv https://search.marginalia.nu
v4_target := 146.190.62.39
test-http:
@sudo route delete 146.190.62.39
@sudo route add 146.190.62.39 -interface utun$(tun_num)
@curl -vv 146.190.62.39:80
@sudo route delete ${v4_target}
@sudo route add ${v4_target} -interface $(tun)
@curl -vv ${v4_target}:80
test-ipv4:
@sudo route delete ${v4_target}
@sudo route add ${v4_target} -interface $(tun)
@ping ${v4_target}
v6_target := 2001:4860:4860::8888
test-ipv6:
@sudo route delete ${v6_target}
@sudo route -n add -inet6 ${v6_target} -interface $(tun)
@echo preparing
@sudo ping6 -v ${v6_target}

38
burrow-server-compose.yml Normal file
View file

@ -0,0 +1,38 @@
version: "2.1"
networks:
wg6:
enable_ipv6: true
ipam:
driver: default
config:
- subnet: "aa:bb:cc:de::/64"
services:
burrow:
image: lscr.io/linuxserver/wireguard:latest
privileged: true
container_name: burrow_server
cap_add:
- NET_ADMIN
- SYS_MODULE
environment:
- PUID=1000
- PGID=1000
- TZ=Asia/Calcutta
- SERVERURL=wg.burrow.rs
- SERVERPORT=51820
- PEERS=10
- PEERDNS=1.1.1.1
- INTERNAL_SUBNET=10.13.13.0
- ALLOWEDIPS=0.0.0.0/0, ::/0
- PERSISTENTKEEPALIVE_PEERS=all
- LOG_CONFS=true #optional
volumes:
- ./config:/config
- /lib/modules:/lib/modules
ports:
- 51820:51820/udp
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv6.conf.eth0.proxy_ndp=1
restart: unless-stopped

View file

@ -57,7 +57,7 @@ impl TryFrom<&TunInterface> for ServerInfo {
#[derive(Clone, Debug, Serialize, Deserialize, JsonSchema)]
pub struct ServerConfig {
pub address: Option<String>,
pub address: Vec<String>,
pub name: Option<String>,
pub mtu: Option<i32>,
}
@ -65,7 +65,7 @@ pub struct ServerConfig {
impl Default for ServerConfig {
fn default() -> Self {
Self {
address: Some("10.13.13.2".to_string()), // Dummy remote address
address: vec!["10.13.13.2".to_string()], // Dummy remote address
name: None,
mtu: None,
}

View file

@ -2,4 +2,4 @@
source: burrow/src/daemon/command.rs
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions {\n tun: TunOptions { ..TunOptions::default() },\n })).unwrap()"
---
{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":null}}}
{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}}

View file

@ -2,4 +2,4 @@
source: burrow/src/daemon/command.rs
expression: "serde_json::to_string(&DaemonCommand::Start(DaemonStartOptions::default())).unwrap()"
---
{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":null}}}
{"Start":{"tun":{"name":null,"no_pi":false,"tun_excl":false,"tun_retrieve":false,"address":[]}}}

View file

@ -2,4 +2,4 @@
source: burrow/src/daemon/response.rs
expression: "serde_json::to_string(&DaemonResponse::new(Ok::<DaemonResponseData,\n String>(DaemonResponseData::ServerConfig(ServerConfig::default()))))?"
---
{"result":{"Ok":{"ServerConfig":{"address":"10.13.13.2","name":null,"mtu":null}}},"id":0}
{"result":{"Ok":{"ServerConfig":{"address":["10.13.13.2"],"name":null,"mtu":null}}},"id":0}

View file

@ -55,7 +55,7 @@ async fn try_start() -> Result<()> {
let mut client = DaemonClient::new().await?;
client
.send_command(DaemonCommand::Start(DaemonStartOptions {
tun: TunOptions::new().address("10.13.13.2"),
tun: TunOptions::new().address(vec!["10.13.13.2", "::2"]),
}))
.await
.map(|_| ())

View file

@ -39,6 +39,7 @@ pub fn initialize() {
tracing_subscriber::fmt::layer()
.with_level(true)
.with_writer(std::io::stderr)
.with_line_number(true)
.compact()
.with_filter(EnvFilter::from_default_env())
});

View file

@ -42,7 +42,7 @@ pub struct Peer {
pub struct Interface {
pub private_key: String,
pub address: String,
pub address: Vec<String>,
pub listen_port: u32,
pub dns: Vec<String>,
pub mtu: Option<u32>,
@ -93,8 +93,8 @@ impl Default for Config {
fn default() -> Self {
Self {
interface: Interface {
private_key: "GNqIAOCRxjl/cicZyvkvpTklgQuUmGUIEkH7IXF/sEE=".into(),
address: "10.13.13.2/24".into(),
private_key: "OEPVdomeLTxTIBvv3TYsJRge0Hp9NMiY0sIrhT8OWG8=".into(),
address: vec!["10.13.13.2/24".into()],
listen_port: 51820,
dns: Default::default(),
mtu: Default::default(),
@ -102,8 +102,8 @@ impl Default for Config {
peers: vec![Peer {
endpoint: "wg.burrow.rs:51820".into(),
allowed_ips: vec!["8.8.8.8/32".into(), "0.0.0.0/0".into()],
public_key: "uy75leriJay0+oHLhRMpV+A5xAQ0hCJ+q7Ww81AOvT4=".into(),
preshared_key: Some("s7lx/mg+reVEMnGnqeyYOQkzD86n2+gYnx1M9ygi08k=".into()),
public_key: "8GaFjVO6c4luCHG4ONO+1bFG8tO+Zz5/Gy+Geht1USM=".into(),
preshared_key: Some("ha7j4BjD49sIzyF9SNlbueK0AMHghlj6+u0G3bzC698=".into()),
persistent_keepalive: Default::default(),
name: Default::default(),
}],

21
server_patch.txt Normal file
View file

@ -0,0 +1,21 @@
# Add this to ~/server/wg0.conf upon regeneration
PostUp = iptables -A FORWARD -i %i -j ACCEPT
PostUp = iptables -A FORWARD -o %i -j ACCEPT
PostUp = iptables -t nat -A POSTROUTING -o eth+ -j MASQUERADE
PostUp = ip6tables -A FORWARD -i %i -j ACCEPT
PostUp = ip6tables -A FORWARD -o %i -j ACCEPT
PostDown = iptables -D FORWARD -i %i -j ACCEPT
PostDown = iptables -D FORWARD -o %i -j ACCEPT
PostDown = iptables -t nat -D POSTROUTING -o eth+ -j MASQUERADE
PostDown = ip6tables -D FORWARD -i %i -j ACCEPT
PostDown = ip6tables -D FORWARD -o %i -j ACCEPT

View file

@ -7,7 +7,7 @@ edition = "2021"
libc = "0.2"
fehler = "1.0"
nix = { version = "0.26", features = ["ioctl"] }
socket2 = "0.4"
socket2 = "0.5"
tokio = { version = "1.28", features = [] }
byteorder = "1.4"
tracing = "0.1"

View file

@ -21,7 +21,7 @@ pub struct TunOptions {
/// (Apple) Retrieve the tun interface
pub tun_retrieve: bool,
/// (Linux) The IP address of the tun interface.
pub address: Option<String>,
pub address: Vec<String>,
}
impl TunOptions {
@ -44,8 +44,8 @@ impl TunOptions {
self
}
pub fn address(mut self, address: impl ToString) -> Self {
self.address = Some(address.to_string());
pub fn address(mut self, address: Vec<impl ToString>) -> Self {
self.address = address.iter().map(|x| x.to_string()).collect();
self
}

View file

@ -21,7 +21,7 @@ impl SysControlSocket for socket2::Socket {
unsafe { sys::resolve_ctl_info(self.as_raw_fd(), &mut info as *mut sys::ctl_info)? };
let (_, addr) = unsafe {
socket2::SockAddr::init(|addr_storage, len| {
socket2::SockAddr::try_init(|addr_storage, len| {
*len = size_of::<sys::sockaddr_ctl>() as u32;
let addr: &mut sys::sockaddr_ctl = &mut *addr_storage.cast();

View file

@ -1,13 +1,11 @@
use std::{
io::{Error, IoSlice},
mem,
net::{Ipv4Addr, SocketAddrV4},
os::fd::{AsRawFd, FromRawFd, RawFd},
};
use std::{io::{Error, IoSlice}, mem, net::{Ipv4Addr, SocketAddrV4}, os::fd::{AsRawFd, FromRawFd, RawFd}, ptr};
use std::net::{IpAddr, Ipv6Addr, SocketAddrV6};
use std::ptr::addr_of;
use byteorder::{ByteOrder, NetworkEndian};
use fehler::throws;
use libc::{c_char, iovec, writev, AF_INET, AF_INET6};
use libc::{c_char, iovec, writev, AF_INET, AF_INET6, sockaddr_in6};
use nix::sys::socket::SockaddrIn6;
use socket2::{Domain, SockAddr, Socket, Type};
use tracing::{self, instrument};
@ -49,7 +47,7 @@ impl TunInterface {
pub fn retrieve() -> Option<TunInterface> {
(3..100)
.filter_map(|fd| unsafe {
let peer_addr = socket2::SockAddr::init(|storage, len| {
let peer_addr = socket2::SockAddr::try_init(|storage, len| {
*len = mem::size_of::<sys::sockaddr_ctl>() as u32;
libc::getpeername(fd, storage as *mut _, len);
Ok(())
@ -71,9 +69,12 @@ impl TunInterface {
#[throws]
fn configure(&self, options: TunOptions) {
if let Some(addr) = options.address {
if let Ok(addr) = addr.parse() {
self.set_ipv4_addr(addr)?;
for addr in options.address{
if let Ok(addr) = addr.parse::<IpAddr>() {
match addr {
IpAddr::V4(addr) => {self.set_ipv4_addr(addr)?}
IpAddr::V6(addr) => {self.set_ipv6_addr(addr)?}
}
}
}
}
@ -117,6 +118,14 @@ impl TunInterface {
iff
}
#[throws]
#[instrument]
fn in6_ifreq(&self) -> sys::in6_ifreq {
let mut iff: sys::in6_ifreq = unsafe { mem::zeroed() };
iff.ifr_name = string_to_ifname(&self.name()?);
iff
}
#[throws]
#[instrument]
pub fn set_ipv4_addr(&self, addr: Ipv4Addr) {
@ -136,6 +145,21 @@ impl TunInterface {
Ipv4Addr::from(u32::from_be(addr.sin_addr.s_addr))
}
#[throws]
pub fn set_ipv6_addr(&self, addr: Ipv6Addr) {
// let addr = SockAddr::from(SocketAddrV6::new(addr, 0, 0, 0));
// println!("addr: {:?}", addr);
// let mut iff = self.in6_ifreq()?;
// let sto = addr.as_storage();
// let ifadddr_ptr: *const sockaddr_in6 = addr_of!(sto).cast();
// iff.ifr_ifru.ifru_addr = unsafe { *ifadddr_ptr };
// println!("ifru addr set");
// println!("{:?}", sys::SIOCSIFADDR_IN6);
// self.perform6(|fd| unsafe { sys::if_set_addr6(fd, &iff) })?;
// tracing::info!("ipv6_addr_set");
tracing::warn!("Setting IPV6 address on MacOS CLI mode is not supported yet.");
}
#[throws]
fn perform<R>(&self, perform: impl FnOnce(RawFd) -> Result<R, nix::Error>) -> R {
let span = tracing::info_span!("perform", fd = self.as_raw_fd());
@ -145,6 +169,15 @@ impl TunInterface {
perform(socket.as_raw_fd())?
}
#[throws]
fn perform6<R>(&self, perform: impl FnOnce(RawFd) -> Result<R, nix::Error>) -> R {
let span = tracing::info_span!("perform6", fd = self.as_raw_fd());
let _enter = span.enter();
let socket = Socket::new(Domain::IPV6, Type::DGRAM, None)?;
perform(socket.as_raw_fd())?
}
#[throws]
#[instrument]
pub fn mtu(&self) -> i32 {

View file

@ -1,6 +1,6 @@
use std::mem;
use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr};
use libc::{c_char, c_int, c_short, c_uint, c_ulong, sockaddr, sockaddr_in6, time_t};
pub use libc::{
c_void,
sockaddr_ctl,
@ -23,6 +23,7 @@ pub const UTUN_CONTROL_NAME: &str = "com.apple.net.utun_control";
pub const UTUN_OPT_IFNAME: libc::c_int = 2;
pub const MAX_KCTL_NAME: usize = 96;
pub const SCOPE6_ID_MAX: usize = 16;
#[repr(C)]
#[derive(Copy, Clone, Debug)]
@ -74,7 +75,107 @@ pub struct ifreq {
pub ifr_ifru: ifr_ifru,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct in6_addrlifetime{
pub ia6t_expire: time_t,
pub ia6t_preferred: time_t,
pub ia6t_vltime: u32,
pub ia6t_pltime: u32,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct in6_ifstat {
pub ifs6_in_receive: u64,
pub ifs6_in_hdrerr: u64,
pub ifs6_in_toobig: u64,
pub ifs6_in_noroute: u64,
pub ifs6_in_addrerr: u64,
pub ifs6_in_protounknown: u64,
pub ifs6_in_truncated: u64,
pub ifs6_in_discard: u64,
pub ifs6_in_deliver: u64,
pub ifs6_out_forward: u64,
pub ifs6_out_request: u64,
pub ifs6_out_discard: u64,
pub ifs6_out_fragok: u64,
pub ifs6_out_fragfail: u64,
pub ifs6_out_fragcreat: u64,
pub ifs6_reass_reqd: u64,
pub ifs6_reass_ok: u64,
pub ifs6_atmfrag_rcvd: u64,
pub ifs6_reass_fail: u64,
pub ifs6_in_mcast: u64,
pub ifs6_out_mcast: u64,
pub ifs6_cantfoward_icmp6: u64,
pub ifs6_addr_expiry_cnt: u64,
pub ifs6_pfx_expiry_cnt: u64,
pub ifs6_defrtr_expiry_cnt: u64,
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct icmp6_ifstat {
pub ifs6_in_msg: u64,
pub ifs6_in_error: u64,
pub ifs6_in_dstunreach: u64,
pub ifs6_in_adminprohib: u64,
pub ifs6_in_timeexceed: u64,
pub ifs6_in_paramprob: u64,
pub ifs6_in_pkttoobig: u64,
pub ifs6_in_echo: u64,
pub ifs6_in_echoreply: u64,
pub ifs6_in_routersolicit: u64,
pub ifs6_in_routeradvert: u64,
pub ifs6_in_neighborsolicit: u64,
pub ifs6_in_neighboradvert: u64,
pub ifs6_in_redirect: u64,
pub ifs6_in_mldquery: u64,
pub ifs6_in_mldreport: u64,
pub ifs6_in_mlddone: u64,
pub ifs6_out_msg: u64,
pub ifs6_out_error: u64,
pub ifs6_out_dstunreach: u64,
pub ifs6_out_adminprohib: u64,
pub ifs6_out_timeexceed: u64,
pub ifs6_out_paramprob: u64,
pub ifs6_out_pkttoobig: u64,
pub ifs6_out_echo: u64,
pub ifs6_out_echoreply: u64,
pub ifs6_out_routersolicit: u64,
pub ifs6_out_routeradvert: u64,
pub ifs6_out_neighborsolicit: u64,
pub ifs6_out_neighboradvert: u64,
pub ifs6_out_redirect: u64,
pub ifs6_out_mldquery: u64,
pub ifs6_out_mldreport: u64,
pub ifs6_out_mlddone: u64,
}
#[repr(C)]
pub union ifr_ifru6 {
pub ifru_addr: sockaddr_in6,
pub ifru_dstaddr: sockaddr_in6,
pub ifru_flags: c_int,
pub ifru_flags6: c_int,
pub ifru_metric: c_int,
pub ifru_intval: c_int,
pub ifru_data: *mut c_char,
pub ifru_lifetime: in6_addrlifetime, // ifru_lifetime
pub ifru_stat: in6_ifstat,
pub ifru_icmp6stat: icmp6_ifstat,
pub ifru_scope_id: [u32; SCOPE6_ID_MAX]
}
#[repr(C)]
pub struct in6_ifreq {
pub ifr_name: [c_char; IFNAMSIZ],
pub ifr_ifru: ifr_ifru6,
}
pub const SIOCSIFADDR: c_ulong = request_code_write!(b'i', 12, mem::size_of::<ifreq>());
pub const SIOCSIFADDR_IN6: c_ulong = request_code_write!(b'i', 12, mem::size_of::<in6_ifreq>());
pub const SIOCGIFMTU: c_ulong = request_code_readwrite!(b'i', 51, mem::size_of::<ifreq>());
pub const SIOCSIFMTU: c_ulong = request_code_write!(b'i', 52, mem::size_of::<ifreq>());
pub const SIOCGIFNETMASK: c_ulong = request_code_readwrite!(b'i', 37, mem::size_of::<ifreq>());
@ -97,5 +198,6 @@ ioctl_read_bad!(if_get_addr, libc::SIOCGIFADDR, ifreq);
ioctl_read_bad!(if_get_mtu, SIOCGIFMTU, ifreq);
ioctl_read_bad!(if_get_netmask, SIOCGIFNETMASK, ifreq);
ioctl_write_ptr_bad!(if_set_addr, SIOCSIFADDR, ifreq);
ioctl_write_ptr_bad!(if_set_addr6, SIOCSIFADDR_IN6, in6_ifreq);
ioctl_write_ptr_bad!(if_set_mtu, SIOCSIFMTU, ifreq);
ioctl_write_ptr_bad!(if_set_netmask, SIOCSIFNETMASK, ifreq);

View file

@ -1,4 +1,5 @@
use std::{io::Error, net::Ipv4Addr};
use std::net::Ipv6Addr;
use fehler::throws;
use tun::TunInterface;
@ -33,3 +34,15 @@ fn write_packets() {
let bytes_written = tun.send(&buf)?;
assert_eq!(bytes_written, 1504);
}
#[test]
#[throws]
#[ignore = "requires interactivity"]
#[cfg(not(target_os = "windows"))]
fn set_ipv6() {
let tun = TunInterface::new()?;
println!("tun name: {:?}", tun.name()?);
let targ_addr: Ipv6Addr = "::1".parse().unwrap();
println!("v6 addr: {:?}", targ_addr);
tun.set_ipv6_addr(targ_addr)?;
}