Compare commits
4 Commits
4a6ac4a8b9
...
f070bc713b
| Author | SHA1 | Date | |
|---|---|---|---|
| f070bc713b | |||
| ee4d4f9cf6 | |||
| e8f4498806 | |||
| d0ec1e29c6 |
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -1,5 +1,5 @@
|
||||
{
|
||||
// "rust-analyzer.cargo.target": "x86_64-pc-windows-gnu",
|
||||
"rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu",
|
||||
// "rust-analyzer.cargo.target": "x86_64-unknown-linux-gnu",
|
||||
// "rust-analyzer.cargo.features": ["tun"]
|
||||
}
|
||||
@ -551,17 +551,16 @@ impl ArpWaitList {
|
||||
if (now - item.timestamp) > 5 {
|
||||
continue;
|
||||
}
|
||||
let packet = form_ethernet_packet(src_mac, mac, item.origin_data);
|
||||
let mut packet = form_ethernet_packet(src_mac, mac, item.origin_data);
|
||||
|
||||
let pkt_size = packet.len();
|
||||
|
||||
let Ok(encrypted) = edge.encryptor.load().encrypt(&packet) else {
|
||||
// let Ok(encrypted) = edge.encryptor.read().unwrap().encrypt(&packet) else {
|
||||
// let Ok(encrypted) = aes_encrypt(&encrypt_key, &packet) else {
|
||||
error!("failed to encrypt packet request");
|
||||
let encryptor = edge.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut packet) {
|
||||
error!("failed to encrypt packet request: {:?}", e);
|
||||
return;
|
||||
};
|
||||
let data_bytes = Bytes::from(encrypted);
|
||||
}
|
||||
let data_bytes = packet.freeze();
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id,
|
||||
|
||||
@ -304,8 +304,10 @@ async fn get_data_from_tun_with_layer2_zeroed(eee: &Node) -> BytesMut {
|
||||
// let mut temp = BytesMut::with_capacity(1514);
|
||||
let mut data_buf = temp.split_off(14);
|
||||
let Ok(size) = eee.device.recv(&mut data_buf).await else {
|
||||
error!("failed to receive");
|
||||
return BytesMut::new();
|
||||
};
|
||||
warn!("got {} bytes from tun with layer 2", size);
|
||||
|
||||
data_buf.truncate(size);
|
||||
temp.unsplit(data_buf);
|
||||
@ -334,6 +336,7 @@ async fn get_tun_flow(eee: &'static Node, tx: Sender<BytesMut>) {
|
||||
};
|
||||
|
||||
if buf.len() == 0 {
|
||||
error!("buf length is zero, quitting loop");
|
||||
return;
|
||||
}
|
||||
if let Err(e) = tx.send(buf).await {
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
use std::{net::SocketAddr, sync::atomic::Ordering, time::Duration};
|
||||
|
||||
use crate::FiveTuple;
|
||||
use crate::pb::SdlPolicyRequest;
|
||||
use crate::tcp::{NatType, get_quic_write_conn};
|
||||
use crate::tcp::{get_quic_write_conn, NatType};
|
||||
use crate::FiveTuple;
|
||||
use crate::{network::TunTapPacketHandler, utils::mac_to_string};
|
||||
|
||||
use crate::{
|
||||
@ -11,19 +11,19 @@ use crate::{
|
||||
encode_to_tcp_message, encode_to_udp_message, SdlData, SdlEmpty, SdlPeerInfo, SdlQueryInfo,
|
||||
SdlRegister, SdlRegisterAck, SdlStunProbeReply,
|
||||
},
|
||||
tcp::{PacketType},
|
||||
tcp::PacketType,
|
||||
utils::{send_to_sock, Socket},
|
||||
};
|
||||
use bytes::BytesMut;
|
||||
use etherparse::{Ethernet2Header, IpNumber, PacketHeaders, ip_number};
|
||||
use etherparse::{ip_number, Ethernet2Header, IpNumber, PacketHeaders};
|
||||
use prost::Message;
|
||||
use sdlan_sn_rs::utils::{BROADCAST_MAC};
|
||||
use sdlan_sn_rs::utils::BROADCAST_MAC;
|
||||
use sdlan_sn_rs::{
|
||||
config::{AF_INET, AF_INET6},
|
||||
peer::{is_sdlan_sock_equal, SdlanSock, V6Info},
|
||||
utils::{
|
||||
get_current_timestamp, get_sdlan_sock_from_socketaddr, is_multi_broadcast,
|
||||
Mac, Result, SDLanError,
|
||||
get_current_timestamp, get_sdlan_sock_from_socketaddr, is_multi_broadcast, Mac, Result,
|
||||
SDLanError,
|
||||
},
|
||||
};
|
||||
|
||||
@ -550,7 +550,7 @@ pub async fn check_peer_registration_needed(
|
||||
_v6_info: &Option<V6Info>,
|
||||
peer_sock: &SdlanSock,
|
||||
) {
|
||||
let mut p = eee.known_peers.peers.get_mut(&src_mac);
|
||||
let p = eee.known_peers.peers.get(&src_mac);
|
||||
let last_seen;
|
||||
let now;
|
||||
match p {
|
||||
@ -561,7 +561,7 @@ pub async fn check_peer_registration_needed(
|
||||
return;
|
||||
// unimplemented!();
|
||||
}
|
||||
Some(ref mut k) => {
|
||||
Some(k) => {
|
||||
// let mut ipv4_to_ipv6 = false;
|
||||
now = get_current_timestamp();
|
||||
if !from_sn {
|
||||
@ -570,9 +570,13 @@ pub async fn check_peer_registration_needed(
|
||||
let origin_family = k.sock.family;
|
||||
if origin_family != peer_sock.family {
|
||||
if peer_sock.family == AF_INET6 && origin_family == AF_INET {
|
||||
info!("Upgrading peer {} from IPv4 to IPv6 P2P", mac_to_string(&src_mac));
|
||||
k.sock = peer_sock.deepcopy();
|
||||
k.last_seen.store(now, Ordering::Relaxed);
|
||||
info!(
|
||||
"Upgrading peer {} from IPv4 to IPv6 P2P",
|
||||
mac_to_string(&src_mac)
|
||||
);
|
||||
|
||||
// k.sock = peer_sock.deepcopy();
|
||||
// k.last_seen.store(now, Ordering::Relaxed);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@ -839,11 +843,8 @@ async fn renew_identity_request(eee: &Node, identity: u32) {
|
||||
// println!("policy request: {:?}", policy_request);
|
||||
// debug!("send register super: {:?}", register_super);
|
||||
// let packet_id = edge.get_next_packet_id();
|
||||
let data = encode_to_tcp_message(
|
||||
Some(policy_request),
|
||||
PacketType::PolicyRequest as u8,
|
||||
)
|
||||
.unwrap();
|
||||
let data =
|
||||
encode_to_tcp_message(Some(policy_request), PacketType::PolicyRequest as u8).unwrap();
|
||||
|
||||
let stream = get_quic_write_conn();
|
||||
if let Err(e) = stream.send(data).await {
|
||||
@ -861,31 +862,34 @@ async fn handle_tun_packet(
|
||||
//let key = eee.get_encrypt_key();
|
||||
|
||||
// if key.len() == 0 {
|
||||
// check the encrypt key
|
||||
// error!("packet encrypt key not provided");
|
||||
// return;
|
||||
// check the encrypt key
|
||||
// error!("packet encrypt key not provided");
|
||||
// return;
|
||||
// }
|
||||
|
||||
// test_aes(key.as_slice());
|
||||
|
||||
let origin = eee.encryptor.load().decrypt(&payload);
|
||||
// let origin = eee.encryptor.read().unwrap().decrypt(&payload);
|
||||
// let origin = aes_decrypt(&payload);
|
||||
if let Err(_e) = origin {
|
||||
let mut payload = BytesMut::from(payload);
|
||||
let decrypt_res = eee.encryptor.load().decrypt(&mut payload);
|
||||
if let Err(_e) = decrypt_res {
|
||||
error!("failed to decrypt original data");
|
||||
return;
|
||||
}
|
||||
|
||||
let data = origin.unwrap();
|
||||
let data = payload;
|
||||
let Ok(headers) = PacketHeaders::from_ethernet_slice(&data) else {
|
||||
error!("failed to parse packet");
|
||||
return;
|
||||
};
|
||||
|
||||
if _from_sn {
|
||||
eee.stats.rx_sup.fetch_add(data.len() as u64, Ordering::Relaxed);
|
||||
eee.stats
|
||||
.rx_sup
|
||||
.fetch_add(data.len() as u64, Ordering::Relaxed);
|
||||
} else {
|
||||
eee.stats.rx_p2p.fetch_add(data.len() as u64, Ordering::Relaxed);
|
||||
eee.stats
|
||||
.rx_p2p
|
||||
.fetch_add(data.len() as u64, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
if let Some(ip) = headers.net {
|
||||
@ -894,7 +898,6 @@ async fn handle_tun_packet(
|
||||
let protocol = ipv4.protocol;
|
||||
match protocol {
|
||||
ip_number::TCP => {
|
||||
|
||||
let Some(transport) = headers.transport else {
|
||||
error!("failed to get transport header");
|
||||
return;
|
||||
@ -910,9 +913,13 @@ async fn handle_tun_packet(
|
||||
dst_ip: ipv4.source.into(),
|
||||
src_port: tcp_header.destination_port,
|
||||
dst_port: tcp_header.source_port,
|
||||
proto:IpNumber::TCP.0,
|
||||
proto: IpNumber::TCP.0,
|
||||
};
|
||||
let (valid, need_refresh) = eee.rule_cache.is_identity_ok(eee.config.allow_routing.load(Ordering::Relaxed), pkt.identity_id, five_tuple);
|
||||
let (valid, need_refresh) = eee.rule_cache.is_identity_ok(
|
||||
eee.config.allow_routing.load(Ordering::Relaxed),
|
||||
pkt.identity_id,
|
||||
five_tuple,
|
||||
);
|
||||
if need_refresh {
|
||||
renew_identity_request(eee, pkt.identity_id).await;
|
||||
}
|
||||
@ -936,9 +943,13 @@ async fn handle_tun_packet(
|
||||
dst_ip: ipv4.source.into(),
|
||||
src_port: udp_header.destination_port,
|
||||
dst_port: udp_header.source_port,
|
||||
proto:IpNumber::UDP.0,
|
||||
proto: IpNumber::UDP.0,
|
||||
};
|
||||
let (valid, need_refresh) = eee.rule_cache.is_identity_ok(eee.config.allow_routing.load(Ordering::Relaxed), pkt.identity_id, five_tuple);
|
||||
let (valid, need_refresh) = eee.rule_cache.is_identity_ok(
|
||||
eee.config.allow_routing.load(Ordering::Relaxed),
|
||||
pkt.identity_id,
|
||||
five_tuple,
|
||||
);
|
||||
if need_refresh {
|
||||
renew_identity_request(eee, pkt.identity_id).await;
|
||||
}
|
||||
@ -955,17 +966,10 @@ async fn handle_tun_packet(
|
||||
// just ignore, ok
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
debug!("sending packet to tun, {} bytes", data.len());
|
||||
if let Err(e) = eee
|
||||
.device
|
||||
.handle_packet_from_net(&data)
|
||||
.await
|
||||
{
|
||||
if let Err(e) = eee.device.handle_packet_from_net(&data).await {
|
||||
error!("failed to handle packet from net: {}", e.to_string());
|
||||
}
|
||||
/*
|
||||
@ -1318,7 +1322,11 @@ pub async fn update_supernode_reg(eee: &Node) {
|
||||
*/
|
||||
|
||||
#[allow(unused)]
|
||||
pub fn form_ethernet_packet(src_mac: Mac, dst_mac: Mac, mut data_with_zeroed_layer2: BytesMut) -> BytesMut {
|
||||
pub fn form_ethernet_packet(
|
||||
src_mac: Mac,
|
||||
dst_mac: Mac,
|
||||
mut data_with_zeroed_layer2: BytesMut,
|
||||
) -> BytesMut {
|
||||
let mut etherheader = Ethernet2Header::default();
|
||||
etherheader.destination = dst_mac;
|
||||
etherheader.ether_type = etherparse::EtherType::IPV4;
|
||||
|
||||
@ -328,17 +328,16 @@ impl TunTapPacketHandler for Iface {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut data = data;
|
||||
let size = data.len();
|
||||
|
||||
let encrypted = match edge.encryptor.load().encrypt(&data) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
error!("failed to encrypt packet request: {}", e.as_str());
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
let encryptor = edge.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut data) {
|
||||
error!("failed to encrypt packet request: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data_bytes = Bytes::from(encrypted);
|
||||
let data_bytes = data.freeze();
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id: edge.network_id.load(Ordering::Relaxed),
|
||||
@ -444,14 +443,14 @@ impl TunTapPacketHandler for Iface {
|
||||
arp.sipaddr =
|
||||
[((self_ip >> 16) & 0xffff) as u16, (self_ip & 0xffff) as u16];
|
||||
|
||||
let data = arp.marshal_to_bytes();
|
||||
// let Ok(encrypted) = aes_encrypt(key, &data) else {
|
||||
let Ok(encrypted) = edge.encryptor.load().encrypt(&data) else {
|
||||
error!("failed to encrypt arp reply");
|
||||
return Ok(());
|
||||
};
|
||||
let mut data_buf = BytesMut::from(arp.marshal_to_bytes().as_slice());
|
||||
let encryptor = edge.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut data_buf) {
|
||||
error!("failed to encrypt arp reply: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data_bytes = Bytes::from(encrypted);
|
||||
let data_bytes = data_buf.freeze();
|
||||
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
@ -607,71 +606,72 @@ impl TunTapPacketHandler for Iface {
|
||||
}
|
||||
_other => {}
|
||||
}
|
||||
}
|
||||
|
||||
match eee.arp_table.get(dstip) {
|
||||
Some(mac) => {
|
||||
let pkt_size = data.len() + 14;
|
||||
let mut etherheader = Ethernet2Header::default();
|
||||
etherheader.destination = mac;
|
||||
etherheader.ether_type = etherparse::EtherType::IPV4;
|
||||
etherheader.source = src_mac;
|
||||
// let mut packet = Vec::with_capacity(14 + data.len() + 4);
|
||||
match eee.arp_table.get(dstip) {
|
||||
Some(mac) => {
|
||||
let pkt_size = data.len() + 14;
|
||||
let mut etherheader = Ethernet2Header::default();
|
||||
etherheader.destination = mac;
|
||||
etherheader.ether_type = etherparse::EtherType::IPV4;
|
||||
etherheader.source = src_mac;
|
||||
// let mut packet = Vec::with_capacity(14 + data.len() + 4);
|
||||
|
||||
header.copy_from_slice(ðerheader.to_bytes()[..]);
|
||||
header.copy_from_slice(ðerheader.to_bytes()[..]);
|
||||
|
||||
let crc = caculate_crc(&data);
|
||||
header.unsplit(data);
|
||||
let crc = caculate_crc(&data);
|
||||
header.unsplit(data);
|
||||
|
||||
// packet.extend_from_slice(ðerheader.to_bytes()[..]);
|
||||
// packet.extend_from_slice(&data);
|
||||
header.extend_from_slice(&crc.to_be_bytes());
|
||||
// packet.extend_from_slice(&crc.to_be_bytes());
|
||||
// packet.extend_from_slice(ðerheader.to_bytes()[..]);
|
||||
// packet.extend_from_slice(&data);
|
||||
header.extend_from_slice(&crc.to_be_bytes());
|
||||
// packet.extend_from_slice(&crc.to_be_bytes());
|
||||
|
||||
// let pkt_size = packet.len();
|
||||
// println!("sending data with mac");
|
||||
// let pkt_size = packet.len();
|
||||
// println!("sending data with mac");
|
||||
|
||||
// let Ok(encrypted) = aes_encrypt(&encrypt_key, &packet) else {
|
||||
let Ok(encrypted) = eee.encryptor.load().encrypt(&header) else {
|
||||
error!("failed to encrypt packet request");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id: eee.network_id.load(Ordering::Relaxed),
|
||||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||||
src_mac: Vec::from(src_mac),
|
||||
dst_mac: Vec::from(mac),
|
||||
data: Bytes::from(encrypted),
|
||||
session_token: eee.session_token.get(),
|
||||
identity_id: eee.identity_id.load(),
|
||||
};
|
||||
let msg =
|
||||
encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||||
let size = msg.len();
|
||||
send_packet_to_net(eee, mac, &msg, pkt_size as u64).await;
|
||||
let encryptor = eee.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut header) {
|
||||
error!("failed to encrypt packet request: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
None => {
|
||||
header.unsplit(data);
|
||||
eee.arp_table.add_to_arp_wait_list(dstip, header);
|
||||
debug!(
|
||||
"find ip: {:?} => {:?}",
|
||||
src.to_be_bytes(),
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
debug!(
|
||||
"no mac found for ip {:?}, sending arp request",
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
// let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
let data_bytes = header.freeze();
|
||||
|
||||
if eee.device_config.contains(&Ipv4Addr::from_bits(dstip)) {
|
||||
let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
} else {
|
||||
if let Some((_, real_ip)) = eee.route_table.lookup(dstip) {
|
||||
let real_ip = u32::from_be_bytes(real_ip.octets());
|
||||
let _ = eee.send_arp_request(dstip, real_ip).await;
|
||||
}
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id: eee.network_id.load(Ordering::Relaxed),
|
||||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||||
src_mac: Vec::from(src_mac),
|
||||
dst_mac: Vec::from(mac),
|
||||
data: data_bytes,
|
||||
session_token: eee.session_token.get(),
|
||||
identity_id: eee.identity_id.load(),
|
||||
};
|
||||
let msg =
|
||||
encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||||
let size = msg.len();
|
||||
send_packet_to_net(eee, mac, &msg, pkt_size as u64).await;
|
||||
}
|
||||
None => {
|
||||
header.unsplit(data);
|
||||
eee.arp_table.add_to_arp_wait_list(dstip, header);
|
||||
debug!(
|
||||
"find ip: {:?} => {:?}",
|
||||
src.to_be_bytes(),
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
debug!(
|
||||
"no mac found for ip {:?}, sending arp request",
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
// let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
|
||||
if eee.device_config.contains(&Ipv4Addr::from_bits(dstip)) {
|
||||
let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
} else {
|
||||
if let Some((_, real_ip)) = eee.route_table.lookup(dstip) {
|
||||
let real_ip = u32::from_be_bytes(real_ip.octets());
|
||||
let _ = eee.send_arp_request(dstip, real_ip).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -10,11 +10,11 @@ use sdlan_sn_rs::utils::{
|
||||
use std::io::{Error, ErrorKind};
|
||||
use std::net::Ipv4Addr;
|
||||
use std::os::windows::process::CommandExt;
|
||||
use std::process::Command;
|
||||
use std::process::{Command, Stdio};
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::Arc;
|
||||
use tracing::{debug, error, info};
|
||||
use tun_rs::AsyncDevice;
|
||||
use tun_rs::{AsyncDevice, SyncDevice};
|
||||
use wintun;
|
||||
|
||||
use crate::network::{
|
||||
@ -36,16 +36,20 @@ pub struct Iface {
|
||||
}
|
||||
|
||||
impl Iface {
|
||||
fn new(path: &str, name: &str) -> Self {
|
||||
fn new(_path: &str, name: &str) -> Self {
|
||||
println!("layer = {:?}", LAYER);
|
||||
let dev = tun_rs::DeviceBuilder::new()
|
||||
.wintun_file(path.to_string())
|
||||
// .wintun_file(path.to_string())
|
||||
.name(name)
|
||||
.layer(LAYER)
|
||||
// .ipv4(Ipv4Addr::new(10, 10, 4, 39), Ipv4Addr::new(255, 255, 255, 0), None)
|
||||
.layer(tun_rs::Layer::L3)
|
||||
.mtu(1280)
|
||||
// .enable(true)
|
||||
.build_async()
|
||||
.expect("failed to create tun");
|
||||
|
||||
let idx = dev.if_index().expect("failed to get if index");
|
||||
println!("index = {}", idx);
|
||||
Self {
|
||||
device: dev,
|
||||
if_idx: idx,
|
||||
@ -351,14 +355,14 @@ impl TunTapPacketHandler for Iface {
|
||||
arp.sipaddr =
|
||||
[((self_ip >> 16) & 0xffff) as u16, (self_ip & 0xffff) as u16];
|
||||
|
||||
let data = arp.marshal_to_bytes();
|
||||
// let Ok(encrypted) = aes_encrypt(key, &data) else {
|
||||
let Ok(encrypted) = edge.encryptor.load().encrypt(&data) else {
|
||||
error!("failed to encrypt arp reply");
|
||||
return Ok(());
|
||||
};
|
||||
let mut data_buf = BytesMut::from(arp.marshal_to_bytes().as_slice());
|
||||
let encryptor = edge.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut data_buf) {
|
||||
error!("failed to encrypt arp reply: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let data_bytes = Bytes::from(encrypted);
|
||||
let data_bytes = data_buf.freeze();
|
||||
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
@ -451,140 +455,7 @@ impl TunTapPacketHandler for Iface {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// async fn handle_packet_from_device(
|
||||
// &self,
|
||||
// data: BytesMut,
|
||||
// // encrypt_key: &[u8],
|
||||
// ) -> std::io::Result<()> {
|
||||
// let eee = get_edge();
|
||||
|
||||
// let src_mac = eee.device_config.get_mac();
|
||||
|
||||
// match IpHeaders::from_slice(&data) {
|
||||
// Ok((iphdr, _payload)) => {
|
||||
// let Some(ipv4hdr) = iphdr.ipv4() else {
|
||||
// debug!("ipv6 packet ignored");
|
||||
// return Ok(());
|
||||
// };
|
||||
// let dstip = u32::from_be_bytes(ipv4hdr.0.destination);
|
||||
// debug!("packet dst ip: {:?}", ipv4hdr.0.destination);
|
||||
// let src = u32::from_be_bytes(ipv4hdr.0.source);
|
||||
// debug!("packet src ip: {:?}", ipv4hdr.0.source);
|
||||
// // packet should be sent to dev
|
||||
// debug!("got {} bytes from tun", data.len());
|
||||
// if (!eee.config.allow_routing.load(Ordering::Relaxed)) && (src != eee.device_config.get_ip()) {
|
||||
// info!("dropping routed packet");
|
||||
// return Ok(());
|
||||
// }
|
||||
// if !eee.is_authorized() {
|
||||
// debug!("drop tun packet due to not authed");
|
||||
// return Ok(());
|
||||
// }
|
||||
// if dstip == DNS_IP {
|
||||
// // println!("request for dns");
|
||||
// let addr = format!("{}:15353", eee.server_ip);
|
||||
// // println!("send dns to {}", addr);
|
||||
// if let Err(e) = eee.udp_sock_for_dns.send_to(&data, &addr).await {
|
||||
// error!("failed to send request to 15353: {}", e);
|
||||
// }
|
||||
// return Ok(());
|
||||
// }
|
||||
// match send_arp_request(ArpRequestInfo::Lookup { ip: dstip }).await {
|
||||
// ArpResponse::LookupResp {
|
||||
// mac,
|
||||
// ip,
|
||||
// do_arp_request,
|
||||
// } => {
|
||||
// if do_arp_request {
|
||||
// add_to_arp_wait_list(dstip, data);
|
||||
|
||||
// info!(
|
||||
// "find ip: {:?} => {:?}",
|
||||
// src.to_be_bytes(),
|
||||
// dstip.to_be_bytes()
|
||||
// );
|
||||
// let arp_msg =
|
||||
// generate_arp_request(src_mac, ip, eee.device_config.get_ip());
|
||||
|
||||
// let Ok(encrypted) = eee.encryptor.load().encrypt(&arp_msg) else {
|
||||
// // let Ok(encrypted) = aes_encrypt(&encrypt_key, &arp_msg) else {
|
||||
// error!("failed to encrypt arp request");
|
||||
// return Ok(());
|
||||
// };
|
||||
// // println!("arp_msg: {:?}", arp_msg);
|
||||
// let data = SdlData {
|
||||
// network_id: eee.network_id.load(Ordering::Relaxed),
|
||||
// src_mac: Vec::from(src_mac),
|
||||
// dst_mac: Vec::from([0xff; 6]),
|
||||
// is_p2p: true,
|
||||
// ttl: SDLAN_DEFAULT_TTL as u32,
|
||||
// data: Bytes::from(encrypted),
|
||||
|
||||
// session_token: eee.session_token.get(),
|
||||
// identity_id: eee.identity_id.load(),
|
||||
// };
|
||||
// let data =
|
||||
// encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||||
// debug!("sending arp");
|
||||
// // let data = marshal_message(&data);
|
||||
// send_packet_to_net(eee, BROADCAST_MAC, &data, arp_msg.len() as u64)
|
||||
// .await;
|
||||
// // edge.sock.send(data).await;
|
||||
// // println!("should send arp");
|
||||
// return Ok(());
|
||||
// }
|
||||
|
||||
// let packet = form_ethernet_packet(src_mac, mac, &data);
|
||||
// // prepend the ether header
|
||||
// /*
|
||||
// let mut etherheader = Ethernet2Header::default();
|
||||
// etherheader.destination = mac;
|
||||
// etherheader.ether_type = etherparse::EtherType::IPV4;
|
||||
// etherheader.source = src_mac;
|
||||
// let mut packet = Vec::with_capacity(14 + data.len() + 4);
|
||||
// packet.extend_from_slice(ðerheader.to_bytes()[..]);
|
||||
// packet.extend_from_slice(&data);
|
||||
// */
|
||||
// // let crc = CRC_HASH.checksum(&packet);
|
||||
// // packet.extend_from_slice(&crc.to_be_bytes());
|
||||
|
||||
// let pkt_size = packet.len();
|
||||
// // println!("sending data with mac");
|
||||
|
||||
// // let Ok(encrypted) = aes_encrypt(&encrypt_key, &packet) else {
|
||||
// let Ok(encrypted) = eee.encryptor.load().encrypt(&packet) else {
|
||||
// error!("failed to encrypt packet request");
|
||||
// return Ok(());
|
||||
// };
|
||||
// let data = SdlData {
|
||||
// is_p2p: true,
|
||||
// network_id: eee.network_id.load(Ordering::Relaxed),
|
||||
// ttl: SDLAN_DEFAULT_TTL as u32,
|
||||
// src_mac: Vec::from(src_mac),
|
||||
// dst_mac: Vec::from(mac),
|
||||
// data: Bytes::from(encrypted),
|
||||
// session_token: eee.session_token.get(),
|
||||
// identity_id: eee.identity_id.load(),
|
||||
// };
|
||||
// let msg =
|
||||
// encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||||
// let size = msg.len();
|
||||
// send_packet_to_net(eee, mac, &msg, pkt_size as u64).await;
|
||||
// // let dstip = u32::from_be_bytes(ipv4hdr.0.destination);
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
// Err(e) => {
|
||||
// error!("failed to parse ip packet: {}", e.to_string());
|
||||
// }
|
||||
// }
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
async fn handle_packet_from_device(&self, mut header: BytesMut) -> std::io::Result<()> {
|
||||
use etherparse::IpHeaders;
|
||||
|
||||
let eee = get_edge();
|
||||
|
||||
let src_mac = eee.device_config.get_mac();
|
||||
@ -645,78 +516,79 @@ impl TunTapPacketHandler for Iface {
|
||||
}
|
||||
_other => {}
|
||||
}
|
||||
}
|
||||
|
||||
match eee.arp_table.get(dstip) {
|
||||
Some(mac) => {
|
||||
let pkt_size = data.len() + 14;
|
||||
let mut etherheader = Ethernet2Header::default();
|
||||
etherheader.destination = mac;
|
||||
etherheader.ether_type = etherparse::EtherType::IPV4;
|
||||
etherheader.source = src_mac;
|
||||
// let mut packet = Vec::with_capacity(14 + data.len() + 4);
|
||||
match eee.arp_table.get(dstip) {
|
||||
Some(mac) => {
|
||||
let pkt_size = data.len() + 14;
|
||||
let mut etherheader = Ethernet2Header::default();
|
||||
etherheader.destination = mac;
|
||||
etherheader.ether_type = etherparse::EtherType::IPV4;
|
||||
etherheader.source = src_mac;
|
||||
// let mut packet = Vec::with_capacity(14 + data.len() + 4);
|
||||
|
||||
header.copy_from_slice(ðerheader.to_bytes()[..]);
|
||||
header.copy_from_slice(ðerheader.to_bytes()[..]);
|
||||
|
||||
let crc = caculate_crc(&data);
|
||||
header.unsplit(data);
|
||||
let crc = caculate_crc(&data);
|
||||
header.unsplit(data);
|
||||
|
||||
// packet.extend_from_slice(ðerheader.to_bytes()[..]);
|
||||
// packet.extend_from_slice(&data);
|
||||
header.extend_from_slice(&crc.to_be_bytes());
|
||||
// packet.extend_from_slice(&crc.to_be_bytes());
|
||||
// packet.extend_from_slice(ðerheader.to_bytes()[..]);
|
||||
// packet.extend_from_slice(&data);
|
||||
header.extend_from_slice(&crc.to_be_bytes());
|
||||
// packet.extend_from_slice(&crc.to_be_bytes());
|
||||
|
||||
// let pkt_size = packet.len();
|
||||
// println!("sending data with mac");
|
||||
// let pkt_size = packet.len();
|
||||
// println!("sending data with mac");
|
||||
|
||||
// let Ok(encrypted) = aes_encrypt(&encrypt_key, &packet) else {
|
||||
let Ok(encrypted) = eee.encryptor.load().encrypt(&header) else {
|
||||
error!("failed to encrypt packet request");
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id: eee.network_id.load(Ordering::Relaxed),
|
||||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||||
src_mac: Vec::from(src_mac),
|
||||
dst_mac: Vec::from(mac),
|
||||
data: Bytes::from(encrypted),
|
||||
session_token: eee.session_token.get(),
|
||||
identity_id: eee.identity_id.load(),
|
||||
};
|
||||
let msg =
|
||||
encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||||
let size = msg.len();
|
||||
send_packet_to_net(eee, mac, &msg, pkt_size as u64).await;
|
||||
let encryptor = eee.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut header) {
|
||||
error!("failed to encrypt packet request: {:?}", e);
|
||||
return Ok(());
|
||||
}
|
||||
None => {
|
||||
header.unsplit(data);
|
||||
debug!(
|
||||
"find ip: {:?} => {:?}",
|
||||
src.to_be_bytes(),
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
debug!(
|
||||
"no mac found for ip {:?}, sending arp request",
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
// let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
let data_bytes = header.freeze();
|
||||
|
||||
if eee.device_config.contains(&Ipv4Addr::from_bits(dstip)) {
|
||||
debug!("contains dst ip {}", ip_to_string(&dstip));
|
||||
eee.arp_table.add_to_arp_wait_list(dstip, header);
|
||||
let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
} else {
|
||||
debug!("try to lookup ip: {}", ip_to_string(&dstip));
|
||||
if let Some((_, real_ip)) = eee.route_table.lookup(dstip) {
|
||||
eee.arp_table.add_to_arp_wait_list(
|
||||
u32::from_be_bytes(real_ip.octets()),
|
||||
header,
|
||||
);
|
||||
error!("got target route: {}", real_ip);
|
||||
let real_ip = u32::from_be_bytes(real_ip.octets());
|
||||
let _ = eee.send_arp_request(real_ip, dstip).await;
|
||||
}
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id: eee.network_id.load(Ordering::Relaxed),
|
||||
ttl: SDLAN_DEFAULT_TTL as u32,
|
||||
src_mac: Vec::from(src_mac),
|
||||
dst_mac: Vec::from(mac),
|
||||
data: data_bytes,
|
||||
session_token: eee.session_token.get(),
|
||||
identity_id: eee.identity_id.load(),
|
||||
};
|
||||
let msg =
|
||||
encode_to_udp_message(Some(data), PacketType::Data as u8).unwrap();
|
||||
let size = msg.len();
|
||||
send_packet_to_net(eee, mac, &msg, pkt_size as u64).await;
|
||||
}
|
||||
None => {
|
||||
header.unsplit(data);
|
||||
debug!(
|
||||
"find ip: {:?} => {:?}",
|
||||
src.to_be_bytes(),
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
debug!(
|
||||
"no mac found for ip {:?}, sending arp request",
|
||||
dstip.to_be_bytes()
|
||||
);
|
||||
// let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
|
||||
if eee.device_config.contains(&Ipv4Addr::from_bits(dstip)) {
|
||||
debug!("contains dst ip {}", ip_to_string(&dstip));
|
||||
eee.arp_table.add_to_arp_wait_list(dstip, header);
|
||||
let _ = eee.send_arp_request(dstip, dstip).await;
|
||||
} else {
|
||||
debug!("try to lookup ip: {}", ip_to_string(&dstip));
|
||||
if let Some((_, real_ip)) = eee.route_table.lookup(dstip) {
|
||||
eee.arp_table.add_to_arp_wait_list(
|
||||
u32::from_be_bytes(real_ip.octets()),
|
||||
header,
|
||||
);
|
||||
error!("got target route: {}", real_ip);
|
||||
let real_ip = u32::from_be_bytes(real_ip.octets());
|
||||
let _ = eee.send_arp_request(real_ip, dstip).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -887,8 +759,8 @@ fn create_wintun(path: &str, name: &str) -> std::io::Result<Iface> {
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn new_iface(name: &str, _mode: Mode) -> std::io::Result<Iface> {
|
||||
create_wintun("./wintun.dll", name)
|
||||
pub fn new_iface(name: &str) -> std::io::Result<Iface> {
|
||||
create_wintun("wintun.dll", name)
|
||||
// Ok(Box::new(create_wintun("/path/to/file")))
|
||||
}
|
||||
|
||||
|
||||
@ -8,10 +8,10 @@ use sdlan_sn_rs::{
|
||||
utils::{get_current_timestamp, ip_to_string, Mac, Result},
|
||||
};
|
||||
|
||||
#[cfg(feature = "tun")]
|
||||
#[cfg(any(feature = "tun", target_os = "windows"))]
|
||||
pub const LAYER: tun_rs::Layer = tun_rs::Layer::L3;
|
||||
|
||||
#[cfg(not(feature = "tun"))]
|
||||
#[cfg(all(not(feature = "tun"), not(target_os = "windows")))]
|
||||
pub const LAYER: tun_rs::Layer = tun_rs::Layer::L2;
|
||||
|
||||
use tracing::{debug, warn};
|
||||
@ -102,17 +102,16 @@ impl ArpWaitList {
|
||||
if (now - item.timestamp) > 5 {
|
||||
continue;
|
||||
}
|
||||
let packet = form_ethernet_packet(src_mac, mac, item.origin_data);
|
||||
let mut packet = form_ethernet_packet(src_mac, mac, item.origin_data);
|
||||
|
||||
let pkt_size = packet.len();
|
||||
|
||||
let Ok(encrypted) = edge.encryptor.load().encrypt(&packet) else {
|
||||
// let Ok(encrypted) = edge.encryptor.read().unwrap().encrypt(&packet) else {
|
||||
// let Ok(encrypted) = aes_encrypt(&encrypt_key, &packet) else {
|
||||
error!("failed to encrypt packet request");
|
||||
let encryptor = edge.encryptor.load();
|
||||
if let Err(e) = encryptor.encrypt(&mut packet) {
|
||||
error!("failed to encrypt packet request: {:?}", e);
|
||||
return;
|
||||
};
|
||||
let data_bytes = Bytes::from(encrypted);
|
||||
}
|
||||
let data_bytes = packet.freeze();
|
||||
let data = SdlData {
|
||||
is_p2p: true,
|
||||
network_id,
|
||||
|
||||
@ -730,12 +730,14 @@ impl ReadWriteActor {
|
||||
match start_stop_chan.recv().await {
|
||||
Some(v) => {
|
||||
if !v.is_start {
|
||||
error!("stop called1");
|
||||
started = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
_other => {
|
||||
// send chan is closed;
|
||||
error!("stop called2");
|
||||
started = false;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -3,7 +3,8 @@ use std::{
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
use chacha20poly1305::{aead::Aead, ChaCha20Poly1305, KeyInit};
|
||||
use bytes::BytesMut;
|
||||
use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit};
|
||||
use sdlan_sn_rs::utils::{aes_decrypt, aes_encrypt, Result, SDLanError};
|
||||
|
||||
const COUNTER_MASK: u32 = (1 << 24) - 1;
|
||||
@ -11,8 +12,8 @@ const COUNTER_MASK: u32 = (1 << 24) - 1;
|
||||
pub trait Encryptor {
|
||||
fn is_setted(&self) -> bool;
|
||||
fn set_key(&mut self, region_id: u32, key: Vec<u8>);
|
||||
fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>>;
|
||||
fn decrypt(&self, ciphered: &[u8]) -> Result<Vec<u8>>;
|
||||
fn encrypt(&self, data: &mut BytesMut) -> Result<()>;
|
||||
fn decrypt(&self, data: &mut BytesMut) -> Result<()>;
|
||||
}
|
||||
|
||||
pub enum MyEncryptor {
|
||||
@ -46,18 +47,19 @@ impl MyEncryptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
|
||||
pub fn encrypt(&self, data: &mut BytesMut) -> Result<()> {
|
||||
match self {
|
||||
Self::Invalid => Err(SDLanError::EncryptError("invalid encryptor".to_owned())),
|
||||
Self::Aes(aes) => aes.encrypt(data),
|
||||
Self::ChaChao20(cha) => cha.encrypt(data),
|
||||
}
|
||||
}
|
||||
pub fn decrypt(&self, ciphered: &[u8]) -> Result<Vec<u8>> {
|
||||
|
||||
pub fn decrypt(&self, data: &mut BytesMut) -> Result<()> {
|
||||
match self {
|
||||
Self::Invalid => Err(SDLanError::EncryptError("invalid encryptor".to_owned())),
|
||||
Self::Aes(aes) => aes.decrypt(ciphered),
|
||||
Self::ChaChao20(cha) => cha.decrypt(ciphered),
|
||||
Self::Aes(aes) => aes.decrypt(data),
|
||||
Self::ChaChao20(cha) => cha.decrypt(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -89,8 +91,10 @@ impl Encryptor for Chacha20Encryptor {
|
||||
self.region_id = region_id;
|
||||
}
|
||||
|
||||
fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
|
||||
// let cipher = chacha20poly1305::ChaCha20Poly1305::new(self.key.as_slice().into());
|
||||
fn encrypt(&self, data: &mut BytesMut) -> Result<()> {
|
||||
let plaintext_len = data.len();
|
||||
|
||||
// Prepare nonce
|
||||
let now = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
@ -103,36 +107,61 @@ impl Encryptor for Chacha20Encryptor {
|
||||
})
|
||||
.unwrap() as u64;
|
||||
|
||||
let mut nonce = Vec::new();
|
||||
let region_id = self.region_id.to_be_bytes();
|
||||
nonce.extend_from_slice(®ion_id);
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
let region_id_bytes = self.region_id.to_be_bytes();
|
||||
nonce_bytes[0..4].copy_from_slice(®ion_id_bytes);
|
||||
let next_data = (now << 24) | next_counter;
|
||||
nonce.extend_from_slice(&next_data.to_be_bytes());
|
||||
nonce_bytes[4..12].copy_from_slice(&next_data.to_be_bytes());
|
||||
let nonce = chacha20poly1305::Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
match self.cipher.encrypt(nonce.as_slice().into(), data) {
|
||||
Ok(data) => {
|
||||
nonce.extend_from_slice(&data);
|
||||
Ok(nonce)
|
||||
}
|
||||
Err(e) => Err(SDLanError::EncryptError(e.to_string())),
|
||||
}
|
||||
// Make room for tag (16 bytes) at the end
|
||||
data.resize(plaintext_len + 16, 0);
|
||||
let (payload, tag_space) = data.split_at_mut(plaintext_len);
|
||||
|
||||
// Encrypt payload in place and get tag
|
||||
let tag = self
|
||||
.cipher
|
||||
.encrypt_in_place_detached(&nonce, &[], payload)
|
||||
.map_err(|e| SDLanError::EncryptError(e.to_string()))?;
|
||||
|
||||
tag_space[..16].copy_from_slice(&tag);
|
||||
|
||||
// Prepend nonce (12 bytes)
|
||||
let mut final_buf = BytesMut::with_capacity(12 + data.len());
|
||||
final_buf.extend_from_slice(&nonce_bytes);
|
||||
final_buf.unsplit(data.split_off(0));
|
||||
*data = final_buf;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn decrypt(&self, ciphered: &[u8]) -> Result<Vec<u8>> {
|
||||
if ciphered.len() < 12 {
|
||||
fn decrypt(&self, data: &mut BytesMut) -> Result<()> {
|
||||
if data.len() < 28 {
|
||||
return Err(SDLanError::EncryptError(
|
||||
"ciphered text size error".to_owned(),
|
||||
));
|
||||
}
|
||||
// let cipher = chacha20poly1305::ChaCha20Poly1305::new(self.key.as_slice().into());
|
||||
let nonce = &ciphered[0..12];
|
||||
match self.cipher.decrypt(nonce.into(), &ciphered[12..]) {
|
||||
Ok(data) => Ok(data),
|
||||
Err(e) => Err(SDLanError::EncryptError(format!(
|
||||
"failed to decyrpt: {}",
|
||||
e.to_string()
|
||||
))),
|
||||
}
|
||||
|
||||
// Split off the 12-byte Nonce
|
||||
let mut payload = data.split_off(12);
|
||||
let mut nonce_bytes = [0u8; 12];
|
||||
nonce_bytes.copy_from_slice(&data[0..12]);
|
||||
let nonce = chacha20poly1305::Nonce::from_slice(&nonce_bytes);
|
||||
|
||||
// Split off the 16-byte Tag
|
||||
let ciphertext_len = payload.len() - 16;
|
||||
let (ciphertext, tag_space) = payload.split_at_mut(ciphertext_len);
|
||||
let tag = chacha20poly1305::Tag::from_slice(&tag_space[..16]);
|
||||
|
||||
// Decrypt in place
|
||||
self.cipher
|
||||
.decrypt_in_place_detached(&nonce, &[], ciphertext, tag)
|
||||
.map_err(|e| SDLanError::EncryptError(format!("failed to decrypt: {}", e.to_string())))?;
|
||||
|
||||
payload.truncate(ciphertext_len);
|
||||
*data = payload;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_setted(&self) -> bool {
|
||||
@ -155,12 +184,16 @@ impl AesEncryptor {
|
||||
}
|
||||
|
||||
impl Encryptor for AesEncryptor {
|
||||
fn decrypt(&self, ciphered: &[u8]) -> Result<Vec<u8>> {
|
||||
aes_decrypt(&self.key, ciphered)
|
||||
fn decrypt(&self, data: &mut BytesMut) -> Result<()> {
|
||||
let res = aes_decrypt(&self.key, data)?;
|
||||
*data = BytesMut::from(res.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn encrypt(&self, data: &[u8]) -> Result<Vec<u8>> {
|
||||
aes_encrypt(&self.key, data)
|
||||
fn encrypt(&self, data: &mut BytesMut) -> Result<()> {
|
||||
let res = aes_encrypt(&self.key, data)?;
|
||||
*data = BytesMut::from(res.as_slice());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_setted(&self) -> bool {
|
||||
@ -172,3 +205,18 @@ impl Encryptor for AesEncryptor {
|
||||
self.is_setted = true;
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_chacha20_encrypt_decrypt() {
|
||||
let key = vec![0u8; 32];
|
||||
let encryptor = Chacha20Encryptor::new(key, 1);
|
||||
let mut data = BytesMut::from(&b"hello world"[..]);
|
||||
encryptor.encrypt(&mut data).unwrap();
|
||||
encryptor.decrypt(&mut data).unwrap();
|
||||
assert_eq!(&data[..], b"hello world");
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user