Making my own tun library that will actually work
modified: Cargo.lock modified: Cargo.toml renamed: frida_core/src/config/mod.rs -> frida_cli/src/config/mod.rs modified: frida_cli/src/main.rs renamed: frida_core/src/obfs.rs -> frida_cli/src/obfs.rs renamed: frida_core/src/udp.rs -> frida_cli/src/udp.rs renamed: frida_core/src/client.rs -> frida_client/src/client.rs modified: frida_core/Cargo.toml new file: frida_core/src/device.rs modified: frida_core/src/main.rs new file: frida_core/src/tun.rs new file: frida_core/src/win_tun.rs modified: frida_gui/Cargo.toml renamed: frida_core/src/gui/mod.rs -> frida_gui/src/gui/mod.rs renamed: frida_core/src/gui/tab/mod.rs -> frida_gui/src/gui/tab/mod.rs renamed: frida_core/src/gui/tab_button.rs -> frida_gui/src/gui/tab_button.rs renamed: frida_core/src/gui/tab_panel.rs -> frida_gui/src/gui/tab_panel.rs modified: frida_lib/Cargo.toml renamed: frida_core/src/android.rs -> frida_lib/src/android.rs renamed: frida_core/src/server.rs -> frida_server/src/server.rs new file: wintun.dll
This commit is contained in:
parent
ac30109672
commit
0edc0fef1d
758
Cargo.lock
generated
758
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,2 +1,3 @@
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["frida_core","frida_client","frida_server","frida_cli","frida_gui","frida_lib"]
|
@ -1,3 +1,171 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
|
||||
use std::{fs, net::{Ipv4Addr}, str};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use env_logger::Builder;
|
||||
use log::{error, LevelFilter};
|
||||
use crate::config::{ ServerConfiguration, ClientConfiguration, ObfsProtocol, ServerPeer };
|
||||
use crate::client::{desktop::DesktopClient, general::VpnClient};
|
||||
|
||||
mod obfs;
|
||||
mod server;
|
||||
mod client;
|
||||
mod udp;
|
||||
mod config;
|
||||
|
||||
fn generate_server_config(matches: &ArgMatches, config_path: &str) {
|
||||
let bind_address = matches.value_of("bind-address").expect("No bind address specified");
|
||||
let internal_address = matches.value_of("internal-address").expect("No internal address specified");
|
||||
let broadcast_mode = matches.value_of("broadcast-mode").is_some();
|
||||
let keepalive: u8 = matches.value_of("keepalive").unwrap().parse().expect("Keepalive argument should be a number");
|
||||
let obfs_type = match matches.value_of("obfs-type").expect("Obfs type should be specified") {
|
||||
"dns" => ObfsProtocol::FakeDNS,
|
||||
"veil" => ObfsProtocol::VEIL,
|
||||
"xor" => ObfsProtocol::XOR,
|
||||
_ => ObfsProtocol::NONE
|
||||
};
|
||||
|
||||
let _ = fs::write(config_path, serde_yaml::to_string(&ServerConfiguration::default(bind_address, internal_address, broadcast_mode, keepalive, obfs_type)).unwrap());
|
||||
}
|
||||
|
||||
fn generate_peer_config(matches: &ArgMatches, config_path: &str, cfg_raw: &String) {
|
||||
let keepalive: u8 = matches.value_of("keepalive").unwrap().parse().expect("Keepalive argument should be a number");
|
||||
let grab_endpoint = matches.value_of("grab-endpoint").is_some();
|
||||
let endpoint = matches.value_of("endpoint").or(Some("0.0.0.0:0")).unwrap();
|
||||
let peer_cfg = matches.value_of("peer-cfg").expect("No peer cfg path specified");
|
||||
|
||||
let mut config: ServerConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad server config file structure");
|
||||
|
||||
let prs = &mut config.peers[..];
|
||||
prs.sort_by(|a, b| a.ip.octets()[3].cmp(&b.ip.octets()[3]));
|
||||
|
||||
let mut internal_address = prs.iter()
|
||||
.map(|p| p.ip)
|
||||
.collect::<Vec<Ipv4Addr>>()
|
||||
.first()
|
||||
.or(Some(&config.interface.internal_address.parse::<Ipv4Addr>().unwrap()))
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
internal_address = Ipv4Addr::new(internal_address.octets()[0], internal_address.octets()[1], internal_address.octets()[2], internal_address.octets()[3]+1);
|
||||
|
||||
let cl_cfg = &ClientConfiguration::default(if grab_endpoint { &config.interface.bind_address } else { endpoint },
|
||||
keepalive,
|
||||
&config.interface.public_key,
|
||||
&internal_address.to_string());
|
||||
|
||||
config.peers.push(ServerPeer { public_key: cl_cfg.client.public_key.clone(), ip: internal_address.clone() });
|
||||
|
||||
let _ = fs::write(peer_cfg, serde_yaml::to_string(cl_cfg).unwrap());
|
||||
|
||||
let _ = fs::write(config_path, serde_yaml::to_string(&config).unwrap());
|
||||
}
|
||||
|
||||
async fn init_server(cfg_raw: &str, s_interface: Option<&str>) {
|
||||
let config: ServerConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad server config file structure");
|
||||
server::server_mode(config, s_interface).await;
|
||||
}
|
||||
|
||||
async fn init_client(cfg_raw: &str, s_interface: Option<String>) {
|
||||
let config: ClientConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad client config file structure");
|
||||
//client::client_mode(config, s_interface).await;
|
||||
let client = DesktopClient{client_config: config, s_interface};
|
||||
client.start().await;
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
//console_subscriber::init();
|
||||
|
||||
// Initialize the logger with 'info' as the default level
|
||||
Builder::new()
|
||||
.filter(None, LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
let matches = App::new("Frida")
|
||||
.version("0.1.2")
|
||||
.author("alterwain")
|
||||
.about("VPN software")
|
||||
.arg(Arg::with_name("mode")
|
||||
.required(true)
|
||||
.index(1)
|
||||
.possible_values(&["server", "client", "gen_cfg", "new_peer"])
|
||||
.help("Runs the program in certain mode"))
|
||||
.arg(Arg::with_name("config")
|
||||
.long("config")
|
||||
.required(true)
|
||||
.value_name("FILE")
|
||||
.help("The path to VPN configuration file")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("peer-cfg")
|
||||
.long("peer-cfg")
|
||||
.value_name("FILE")
|
||||
.help("The path to VPN peer configuration file")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("bind-address")
|
||||
.long("bind-address")
|
||||
.value_name("IP:PORT")
|
||||
.help("The ip:port that would be used to bind server (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("endpoint")
|
||||
.long("endpoint")
|
||||
.value_name("IP:PORT")
|
||||
.help("The ip:port that would be used by client to connect (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("internal-address")
|
||||
.long("internal-address")
|
||||
.value_name("IP")
|
||||
.help("The address of VPN server in it's subnet (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("broadcast-mode")
|
||||
.long("broadcast-mode")
|
||||
.help("If set to true, then all incoming traffic with an unknown destination address will be forwarded to all peers (config)")
|
||||
.takes_value(false))
|
||||
.arg(Arg::with_name("grab-endpoint")
|
||||
.long("grab-endpoint")
|
||||
.help("If set to true, the endpoint address for peers will be grabbed from server config (config)")
|
||||
.takes_value(false))
|
||||
.arg(Arg::with_name("keepalive")
|
||||
.long("keepalive")
|
||||
.required(false)
|
||||
.value_name("SECONDS")
|
||||
.default_value("0")
|
||||
.help("Keepalive packets interval (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("obfs-type")
|
||||
.long("obfs-type")
|
||||
.possible_values(&["dns", "veil", "xor"])
|
||||
.takes_value(true)
|
||||
.value_name("OBFS")
|
||||
.help("Obfuscation protocol (config)"))
|
||||
.arg(Arg::with_name("interface")
|
||||
.long("interface")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.value_name("NAME")
|
||||
.help("Explicitly set network interface name for routing"))
|
||||
.get_matches();
|
||||
|
||||
let mode = matches.value_of("mode").unwrap();
|
||||
|
||||
if let Some(config_path) = matches.value_of("config") {
|
||||
|
||||
let data = fs::read(config_path);
|
||||
|
||||
if data.is_err() {
|
||||
match mode {
|
||||
"gen_cfg" => generate_server_config(&matches, config_path),
|
||||
_ => error!("There is no config file.")
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let cfg_raw = &String::from_utf8(data.unwrap()).unwrap();
|
||||
|
||||
match mode {
|
||||
"server" => init_server(cfg_raw, matches.value_of("interface")).await,
|
||||
"client" => init_client(cfg_raw, matches.value_of("interface").map_or(None, |x| Some(String::from(x)))).await,
|
||||
"new_peer" => generate_peer_config(&matches, config_path, cfg_raw),
|
||||
_ => error!("There is config file already")
|
||||
}
|
||||
}
|
||||
}
|
@ -9,18 +9,10 @@ categories = ["network-programming", "asynchronous"]
|
||||
readme = "../README.md"
|
||||
workspace = "../"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
path = "src/android.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "frida-cli"
|
||||
name = "frida-core"
|
||||
path = "src/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "frida-gui"
|
||||
path = "src/gui/mod.rs"
|
||||
|
||||
[dependencies]
|
||||
clap = "2.33"
|
||||
aes-gcm = "0.10.3"
|
||||
@ -50,10 +42,17 @@ tun = { version = "0.7.5", features = ["async"] }
|
||||
iced = { version = "0.13.1", features = ["tokio"] }
|
||||
dirs = "5.0.1"
|
||||
tray-item = "0.10.0"
|
||||
wintun = "0.5.0"
|
||||
|
||||
[target.'cfg(target_os="windows")'.build-dependencies]
|
||||
embed-resource = "2.3"
|
||||
|
||||
[target.'cfg(target_os="darwin")'.dependencies]
|
||||
tun-tap = "0.1.4"
|
||||
|
||||
[target.'cfg(target_os="linux")'.dependencies]
|
||||
tokio-tun = "0.12.1"
|
||||
|
||||
[target.'cfg(target_os="android")'.dependencies]
|
||||
jni = "^0.20"
|
||||
robusta_jni = "0.2.2"
|
||||
|
31
frida_core/src/device.rs
Normal file
31
frida_core/src/device.rs
Normal file
@ -0,0 +1,31 @@
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct AbstractDevice {
|
||||
address: String,
|
||||
netmask: String,
|
||||
destination: String,
|
||||
mtu: u16,
|
||||
tun_name: String
|
||||
}
|
||||
|
||||
impl AbstractDevice {
|
||||
fn address(&mut self, address: String) {
|
||||
self.address = address;
|
||||
}
|
||||
|
||||
fn netmask(&mut self, netmask: String) {
|
||||
self.netmask = netmask;
|
||||
}
|
||||
|
||||
fn destination(&mut self, destination: String) {
|
||||
self.destination = destination;
|
||||
}
|
||||
|
||||
fn mtu(&mut self, mtu: u16) {
|
||||
self.mtu = mtu;
|
||||
}
|
||||
|
||||
fn tun_name(&mut self, tun_name: String) {
|
||||
self.tun_name = tun_name;
|
||||
}
|
||||
}
|
@ -1,171 +1,30 @@
|
||||
|
||||
use std::{fs, net::{Ipv4Addr}, str};
|
||||
use clap::{App, Arg, ArgMatches};
|
||||
use env_logger::Builder;
|
||||
use log::{error, LevelFilter};
|
||||
use crate::config::{ ServerConfiguration, ClientConfiguration, ObfsProtocol, ServerPeer };
|
||||
use crate::client::{desktop::DesktopClient, general::VpnClient};
|
||||
use log::{info, error, LevelFilter};
|
||||
|
||||
mod obfs;
|
||||
mod server;
|
||||
mod client;
|
||||
mod udp;
|
||||
mod config;
|
||||
|
||||
fn generate_server_config(matches: &ArgMatches, config_path: &str) {
|
||||
let bind_address = matches.value_of("bind-address").expect("No bind address specified");
|
||||
let internal_address = matches.value_of("internal-address").expect("No internal address specified");
|
||||
let broadcast_mode = matches.value_of("broadcast-mode").is_some();
|
||||
let keepalive: u8 = matches.value_of("keepalive").unwrap().parse().expect("Keepalive argument should be a number");
|
||||
let obfs_type = match matches.value_of("obfs-type").expect("Obfs type should be specified") {
|
||||
"dns" => ObfsProtocol::FakeDNS,
|
||||
"veil" => ObfsProtocol::VEIL,
|
||||
"xor" => ObfsProtocol::XOR,
|
||||
_ => ObfsProtocol::NONE
|
||||
};
|
||||
|
||||
let _ = fs::write(config_path, serde_yaml::to_string(&ServerConfiguration::default(bind_address, internal_address, broadcast_mode, keepalive, obfs_type)).unwrap());
|
||||
}
|
||||
|
||||
fn generate_peer_config(matches: &ArgMatches, config_path: &str, cfg_raw: &String) {
|
||||
let keepalive: u8 = matches.value_of("keepalive").unwrap().parse().expect("Keepalive argument should be a number");
|
||||
let grab_endpoint = matches.value_of("grab-endpoint").is_some();
|
||||
let endpoint = matches.value_of("endpoint").or(Some("0.0.0.0:0")).unwrap();
|
||||
let peer_cfg = matches.value_of("peer-cfg").expect("No peer cfg path specified");
|
||||
|
||||
let mut config: ServerConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad server config file structure");
|
||||
|
||||
let prs = &mut config.peers[..];
|
||||
prs.sort_by(|a, b| a.ip.octets()[3].cmp(&b.ip.octets()[3]));
|
||||
|
||||
let mut internal_address = prs.iter()
|
||||
.map(|p| p.ip)
|
||||
.collect::<Vec<Ipv4Addr>>()
|
||||
.first()
|
||||
.or(Some(&config.interface.internal_address.parse::<Ipv4Addr>().unwrap()))
|
||||
.unwrap()
|
||||
.clone();
|
||||
|
||||
internal_address = Ipv4Addr::new(internal_address.octets()[0], internal_address.octets()[1], internal_address.octets()[2], internal_address.octets()[3]+1);
|
||||
|
||||
let cl_cfg = &ClientConfiguration::default(if grab_endpoint { &config.interface.bind_address } else { endpoint },
|
||||
keepalive,
|
||||
&config.interface.public_key,
|
||||
&internal_address.to_string());
|
||||
|
||||
config.peers.push(ServerPeer { public_key: cl_cfg.client.public_key.clone(), ip: internal_address.clone() });
|
||||
|
||||
let _ = fs::write(peer_cfg, serde_yaml::to_string(cl_cfg).unwrap());
|
||||
|
||||
let _ = fs::write(config_path, serde_yaml::to_string(&config).unwrap());
|
||||
}
|
||||
|
||||
async fn init_server(cfg_raw: &str, s_interface: Option<&str>) {
|
||||
let config: ServerConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad server config file structure");
|
||||
server::server_mode(config, s_interface).await;
|
||||
}
|
||||
|
||||
async fn init_client(cfg_raw: &str, s_interface: Option<String>) {
|
||||
let config: ClientConfiguration = serde_yaml::from_str(cfg_raw).expect("Bad client config file structure");
|
||||
//client::client_mode(config, s_interface).await;
|
||||
let client = DesktopClient{client_config: config, s_interface};
|
||||
client.start().await;
|
||||
}
|
||||
mod device;
|
||||
mod tun;
|
||||
mod win_tun;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
//console_subscriber::init();
|
||||
|
||||
// Initialize the logger with 'info' as the default level
|
||||
Builder::new()
|
||||
.filter(None, LevelFilter::Info)
|
||||
.init();
|
||||
|
||||
let matches = App::new("Frida")
|
||||
.version("0.1.2")
|
||||
.author("alterwain")
|
||||
.about("VPN software")
|
||||
.arg(Arg::with_name("mode")
|
||||
.required(true)
|
||||
.index(1)
|
||||
.possible_values(&["server", "client", "gen_cfg", "new_peer"])
|
||||
.help("Runs the program in certain mode"))
|
||||
.arg(Arg::with_name("config")
|
||||
.long("config")
|
||||
.required(true)
|
||||
.value_name("FILE")
|
||||
.help("The path to VPN configuration file")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("peer-cfg")
|
||||
.long("peer-cfg")
|
||||
.value_name("FILE")
|
||||
.help("The path to VPN peer configuration file")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("bind-address")
|
||||
.long("bind-address")
|
||||
.value_name("IP:PORT")
|
||||
.help("The ip:port that would be used to bind server (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("endpoint")
|
||||
.long("endpoint")
|
||||
.value_name("IP:PORT")
|
||||
.help("The ip:port that would be used by client to connect (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("internal-address")
|
||||
.long("internal-address")
|
||||
.value_name("IP")
|
||||
.help("The address of VPN server in it's subnet (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("broadcast-mode")
|
||||
.long("broadcast-mode")
|
||||
.help("If set to true, then all incoming traffic with an unknown destination address will be forwarded to all peers (config)")
|
||||
.takes_value(false))
|
||||
.arg(Arg::with_name("grab-endpoint")
|
||||
.long("grab-endpoint")
|
||||
.help("If set to true, the endpoint address for peers will be grabbed from server config (config)")
|
||||
.takes_value(false))
|
||||
.arg(Arg::with_name("keepalive")
|
||||
.long("keepalive")
|
||||
.required(false)
|
||||
.value_name("SECONDS")
|
||||
.default_value("0")
|
||||
.help("Keepalive packets interval (config)")
|
||||
.takes_value(true))
|
||||
.arg(Arg::with_name("obfs-type")
|
||||
.long("obfs-type")
|
||||
.possible_values(&["dns", "veil", "xor"])
|
||||
.takes_value(true)
|
||||
.value_name("OBFS")
|
||||
.help("Obfuscation protocol (config)"))
|
||||
.arg(Arg::with_name("interface")
|
||||
.long("interface")
|
||||
.required(false)
|
||||
.takes_value(true)
|
||||
.value_name("NAME")
|
||||
.help("Explicitly set network interface name for routing"))
|
||||
.get_matches();
|
||||
let (reader, writer) = tun::create_tun();
|
||||
|
||||
let mode = matches.value_of("mode").unwrap();
|
||||
|
||||
if let Some(config_path) = matches.value_of("config") {
|
||||
|
||||
let data = fs::read(config_path);
|
||||
|
||||
if data.is_err() {
|
||||
match mode {
|
||||
"gen_cfg" => generate_server_config(&matches, config_path),
|
||||
_ => error!("There is no config file.")
|
||||
let a = tokio::spawn(async move {
|
||||
let mut buf = Vec::new();
|
||||
info!("Started!");
|
||||
loop {
|
||||
// info!("We've got {} bytes of data!", c)
|
||||
let r = reader.read(&mut buf).await;
|
||||
match r {
|
||||
Ok(c) => {},
|
||||
Err(e) => error!("We've got a nasty error message!")
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
let cfg_raw = &String::from_utf8(data.unwrap()).unwrap();
|
||||
|
||||
match mode {
|
||||
"server" => init_server(cfg_raw, matches.value_of("interface")).await,
|
||||
"client" => init_client(cfg_raw, matches.value_of("interface").map_or(None, |x| Some(String::from(x)))).await,
|
||||
"new_peer" => generate_peer_config(&matches, config_path, cfg_raw),
|
||||
_ => error!("There is config file already")
|
||||
}
|
||||
}
|
||||
a.await;
|
||||
}
|
7
frida_core/src/tun.rs
Normal file
7
frida_core/src/tun.rs
Normal file
@ -0,0 +1,7 @@
|
||||
#[cfg(target_os = "windows")]
|
||||
use crate::win_tun::{DeviceReader, DeviceWriter, create};
|
||||
|
||||
pub(crate) fn create_tun() -> (DeviceReader, DeviceWriter) {
|
||||
#[cfg(target_os = "windows")]
|
||||
create()
|
||||
}
|
50
frida_core/src/win_tun.rs
Normal file
50
frida_core/src/win_tun.rs
Normal file
@ -0,0 +1,50 @@
|
||||
use wintun::Session;
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::error::Error;
|
||||
|
||||
pub fn create() -> (DeviceReader, DeviceWriter) {
|
||||
//Unsafe because we are loading an arbitrary dll file
|
||||
let wintun = unsafe { wintun::load_from_path("wintun.dll") }
|
||||
.expect("Failed to load wintun dll");
|
||||
|
||||
//Try to open an adapter with the name "Demo"
|
||||
let adapter = match wintun::Adapter::open(&wintun, "Demo") {
|
||||
Ok(a) => a,
|
||||
Err(_) => {
|
||||
wintun::Adapter::create(&wintun, "Demo", "Example", None)
|
||||
.expect("Failed to create wintun adapter!")
|
||||
}
|
||||
};
|
||||
|
||||
let session = Arc::new(adapter.start_session(wintun::MAX_RING_CAPACITY).unwrap());
|
||||
let reader_session = session.clone();
|
||||
let writer_session = session.clone();
|
||||
|
||||
(DeviceReader{ session: reader_session }, DeviceWriter{ session: writer_session })
|
||||
}
|
||||
|
||||
pub struct DeviceWriter {
|
||||
session: Arc<Session>
|
||||
}
|
||||
|
||||
pub struct DeviceReader {
|
||||
session: Arc<Session>
|
||||
}
|
||||
|
||||
impl DeviceWriter {
|
||||
pub async fn write(&self, buf: &Vec<u8>) -> Result<usize, Box<dyn Error>> {
|
||||
let mut write_pack = self.session.allocate_send_packet(buf.len() as u16)?;
|
||||
write_pack.bytes_mut().copy_from_slice(buf);
|
||||
self.session.send_packet(write_pack);
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
||||
|
||||
impl DeviceReader {
|
||||
pub async fn read(&self, buf: &mut Vec<u8>) -> Result<usize, Box<dyn Error>> {
|
||||
let packet = self.session.receive_blocking()?;
|
||||
*buf = packet.bytes().to_vec();
|
||||
Ok(buf.len())
|
||||
}
|
||||
}
|
@ -9,4 +9,8 @@ categories = ["network-programming", "asynchronous"]
|
||||
readme = "../README.md"
|
||||
workspace = "../"
|
||||
|
||||
[[bin]]
|
||||
name = "frida-gui"
|
||||
path = "src/gui/mod.rs"
|
||||
|
||||
[dependencies]
|
||||
|
@ -9,4 +9,8 @@ categories = ["network-programming", "asynchronous"]
|
||||
readme = "../README.md"
|
||||
workspace = "../"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
path = "src/android.rs"
|
||||
|
||||
[dependencies]
|
||||
|
BIN
wintun.dll
Normal file
BIN
wintun.dll
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user