支持stun协议

This commit is contained in:
lubeilin
2023-08-03 21:05:15 +08:00
parent c5398a51e8
commit d1bde297d6
8 changed files with 212 additions and 202 deletions
+43 -19
View File
@@ -31,15 +31,15 @@ async fn main0() {
let args: Vec<String> = std::env::args().collect();
let program = args[0].clone();
let mut opts = Options::new();
opts.optopt("k", "", &format!("{}", green("必选,使用相同的token,就能组建一个局域网络".to_string())), "<token>");
opts.optopt("n", "", "给设备一个名字,默认使用系统版本", "<name>");
opts.optopt("k", "", "必选,使用相同的token,就能组建一个局域网络", "<token>");
opts.optopt("n", "", "给设备一个名字,便于区分不同设备,默认使用系统版本", "<name>");
opts.optopt("d", "", "设备唯一标识符,不使用--ip参数时,服务端凭此参数分配虚拟ip", "<id>");
opts.optflag("c", "", "关闭交互式命令,使用此参数禁用控制台输入");
opts.optopt("s", "", "注册和中继服务器地址", "<server>");
opts.optopt("e", "", "NAT探测服务器地址,使用逗号分隔", "<addr1,addr2>");
opts.optmulti("e", "", "stun服务器,用于探测NAT类型,可多次指定,如-e addr1 -e addr2", "<stun-server>");
opts.optflag("a", "", "使用tap模式,默认使用tun模式");
opts.optmulti("i", "", "配置点对网(IP代理)时使用,-i 192.168.0.0/24,10.26.0.3 \n表示允许接收网段192.168.0.0/24的数据并转发到10.26.0.3", "<in-ip>");
opts.optmulti("o", "", "配置点对网时使用,-o 192.168.0.0/24 \n表示允许将数据转发到192.168.0.0/24", "<out-ip>");
opts.optmulti("i", "", "配置点对网(IP代理)时使用,-i 192.168.0.0/24,10.26.0.3 \n表示允许接收网段192.168.0.0/24的数据并转发到10.26.0.3,可指定多个网段", "<in-ip>");
opts.optmulti("o", "", "配置点对网时使用,-o 192.168.0.0/24 \n表示允许将数据转发到192.168.0.0/24,可指定多个网段", "<out-ip>");
opts.optopt("w", "", "使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信", "<password>");
opts.optflag("m", "", "模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送");
opts.optopt("u", "", "自定义mtu(默认为1430)", "<mtu>");
@@ -47,11 +47,11 @@ async fn main0() {
opts.optopt("", "ip", "指定虚拟ip,指定的ip不能和其他设备重复,必须有效并且在服务端所属网段下,默认情况由服务端分配", "<IP>");
opts.optflag("", "relay", "仅使用服务器转发,不使用p2p,默认情况允许使用p2p");
//"后台运行时,查看其他设备列表"
opts.optflag("", "list", &format!("{}", yellow("后台运行时,查看其他设备列表".to_string())));
opts.optflag("", "all", &format!("{}", yellow("后台运行时,查看其他设备完整信息".to_string())));
opts.optflag("", "info", &format!("{}", yellow("后台运行时,查看当前设备信息".to_string())));
opts.optflag("", "route", &format!("{}", yellow("后台运行时,查看数据转发路径".to_string())));
opts.optflag("", "stop", &format!("{}", yellow("停止后台运行".to_string())));
opts.optflag("", "list", "后台运行时,查看其他设备列表");
opts.optflag("", "all", "后台运行时,查看其他设备完整信息");
opts.optflag("", "info", "后台运行时,查看当前设备信息");
opts.optflag("", "route", "后台运行时,查看数据转发路径");
opts.optflag("", "stop", "停止后台运行");
opts.optflag("h", "help", "帮助");
let matches = match opts.parse(&args[1..]) {
Ok(m) => { m }
@@ -132,11 +132,12 @@ async fn main0() {
return;
}
};
let nat_test_server = matches.opt_get_default("e",
"nat1.wherewego.top:35061,nat1.wherewego.top:35062,nat2.wherewego.top:35061,nat2.wherewego.top:35062".to_string()).unwrap();
let nat_test_server = nat_test_server.split(",").flat_map(|a| a.to_socket_addrs()).flatten()
.collect::<Vec<_>>();
let mut stun_server = matches.opt_strs("e");
if stun_server.is_empty() {
stun_server.push("stun1.l.google.com:19302".to_string());
stun_server.push("stun2.l.google.com:19302".to_string());
stun_server.push("stun.qq.com:3478".to_string());
}
let in_ip = matches.opt_strs("i");
let in_ip = match ips_parse(&in_ip) {
@@ -192,7 +193,7 @@ async fn main0() {
let config = Config::new(tap,
token, device_id, name,
server_address, server_address_str,
nat_test_server, in_ip,
stun_server, in_ip,
out_ip, password, simulate_multicast, mtu, tcp_channel, virtual_ip, relay);
let mut vnt_util = VntUtil::new(config).await.unwrap();
let response = loop {
@@ -309,10 +310,33 @@ async fn main0() {
vnt.wait_stop().await;
}
fn print_usage(program: &str, opts: Options) {
let brief = format!("Usage: {} [options]", program);
fn print_usage(program: &str, _opts: Options) {
println!("Usage: {} [options]", program);
println!("version:1.1.2");
println!("{}", opts.usage(&brief));
println!("Options:");
println!(" -k <token> {}", green("必选,使用相同的token,就能组建一个局域网络".to_string()));
println!(" -n <name> 给设备一个名字,便于区分不同设备,默认使用系统版本");
println!(" -d <id> 设备唯一标识符,不使用--ip参数时,服务端凭此参数分配虚拟ip");
println!(" -c 关闭交互式命令,使用此参数禁用控制台输入");
println!(" -s <server> 注册和中继服务器地址");
println!(" -e <stun-server> stun服务器,用于探测NAT类型,可多次指定,如-e addr1 -e addr2");
println!(" -a 使用tap模式,默认使用tun模式");
println!(" -i <in-ip> 配置点对网(IP代理)时使用,-i 192.168.0.0/24,10.26.0.3表示允许接收网段192.168.0.0/24的数据");
println!(" 并转发到10.26.0.3,可指定多个网段");
println!(" -o <out-ip> 配置点对网时使用,-o 192.168.0.0/24表示允许将数据转发到192.168.0.0/24,可指定多个网段");
println!(" -w <password> 使用该密码生成的密钥对客户端数据进行加密,并且服务端无法解密,使用相同密码的客户端才能通信");
println!(" -m 模拟组播,默认情况下组播数据会被当作广播发送,开启后会模拟真实组播的数据发送");
println!(" -u <mtu> 自定义mtu(默认为1430)");
println!(" --tcp 和服务端使用tcp通信,默认使用udp,遇到udp qos时可指定使用tcp");
println!(" --ip <IP> 指定虚拟ip,指定的ip不能和其他设备重复,必须有效并且在服务端所属网段下,默认情况由服务端分配");
println!(" --relay 仅使用服务器转发,不使用p2p,默认情况允许使用p2p");
println!();
println!(" --list {}",yellow("后台运行时,查看其他设备列表".to_string()));
println!(" --all {}",yellow("后台运行时,查看其他设备完整信息".to_string()));
println!(" --info {}",yellow("后台运行时,查看当前设备信息".to_string()));
println!(" --route {}",yellow("后台运行时,查看数据转发路径".to_string()));
println!(" --stop {}",yellow("停止后台运行".to_string()));
println!(" -h, --help 帮助");
}
fn green(str: String) -> impl std::fmt::Display {
+6 -4
View File
@@ -55,7 +55,7 @@ fn new_sync(env: &mut JNIEnv, config: JObject) -> Result<VntUtilSync, Error> {
let device_id = to_string_not_null(env, &config, "deviceId")?;
let password = to_string(env, &config, "password")?;
let server_address_str = to_string_not_null(env, &config, "server")?;
let nat_test_server = to_string_not_null(env, &config, "natTestServer")?;
// let nat_test_server = to_string_not_null(env, &config, "natTestServer")?;
let server_address = match server_address_str.to_socket_addrs() {
Ok(mut rs) => {
if let Some(addr) = rs.next() {
@@ -72,12 +72,14 @@ fn new_sync(env: &mut JNIEnv, config: JObject) -> Result<VntUtilSync, Error> {
return Err(Error::JavaException);
}
};
let nat_test_server = nat_test_server.split(",").flat_map(|a| a.trim().to_socket_addrs()).flatten()
.collect::<Vec<_>>();
let mut stun_server = Vec::new();
stun_server.push("stun1.l.google.com:19302".to_string());
stun_server.push("stun2.l.google.com:19302".to_string());
stun_server.push("stun.qq.com:3478".to_string());
let config = Config::new(false,
token, device_id, name,
server_address, server_address_str,
nat_test_server, vec![],
stun_server, vec![],
vec![], password, false, None, false, None, false);
match VntUtilSync::new(config) {
Ok(vnt_util) => {
+2
View File
@@ -21,6 +21,8 @@ socket2 ={ version = "0.5.2", features = ["all"] }
tokio = { version = "1.28.1", features = ["full"] }
aes-gcm = {version="0.10.2", optional = true}
ring = {version="0.16.20", optional = true}
stun-format = {version="1.0.1",features=["fmt","rfc3489"]}
[target.'cfg(any(target_os = "linux",target_os = "macos"))'.dependencies]
tun = { path = "./rust-tun" }
+11 -6
View File
@@ -181,16 +181,16 @@ impl VntUtil {
let local_ip = crate::nat::local_ip()?;
let local_port = context.main_local_port()?;
// NAT检测
let nat_test = NatTest::new(config.nat_test_server.clone(), response.public_ip, response.public_port, local_ip, local_port);
let nat_test = NatTest::new(config.stun_server.clone(), response.public_ip, response.public_port, local_ip, local_port).await;
let in_external_route = if config.in_ips.is_empty() {
None
} else {
Some(ExternalRoute::new(config.in_ips))
};
let (tcp_proxy, udp_proxy, ip_proxy_map) = if config.out_ips.is_empty() {
let (tcp_proxy, udp_proxy, ip_proxy_map) = if config.out_ips.is_empty() {
(None, None, None)
} else {
let (tcp_proxy, udp_proxy, ip_proxy_map) = crate::ip_proxy::init_proxy(channel_sender.clone(), current_device.clone()).await?;
let (tcp_proxy, udp_proxy, ip_proxy_map) = crate::ip_proxy::init_proxy(channel_sender.clone(), current_device.clone()).await?;
(Some(tcp_proxy), Some(udp_proxy), Some(ip_proxy_map))
};
let out_external_route = AllowExternalRoute::new(config.out_ips);
@@ -335,7 +335,7 @@ pub struct Config {
pub name: String,
pub server_address: SocketAddr,
pub server_address_str: String,
pub nat_test_server: Vec<SocketAddr>,
pub stun_server: Vec<String>,
pub in_ips: Vec<(u32, u32, Ipv4Addr)>,
pub out_ips: Vec<(u32, u32)>,
pub password: Option<String>,
@@ -353,11 +353,16 @@ impl Config {
name: String,
server_address: SocketAddr,
server_address_str: String,
nat_test_server: Vec<SocketAddr>,
mut stun_server: Vec<String>,
in_ips: Vec<(u32, u32, Ipv4Addr)>, out_ips: Vec<(u32, u32)>,
password: Option<String>, simulate_multicast: bool, mtu: Option<u16>, tcp: bool,
ip: Option<Ipv4Addr>,
relay: bool, ) -> Self {
for x in stun_server.iter_mut() {
if !x.contains(":") {
x.push_str(":3478");
}
}
Self {
tap,
token,
@@ -365,7 +370,7 @@ impl Config {
name,
server_address,
server_address_str,
nat_test_server,
stun_server,
in_ips,
out_ips,
password,
+1 -1
View File
@@ -270,7 +270,7 @@ impl ChannelDataHandler {
let local_port = context.main_local_port()?;
let local_ip = nat::local_ip()?;
let nat_info = self.nat_test.re_test(Ipv4Addr::from(response.public_ip),
response.public_port as u16, local_ip, local_port);
response.public_port as u16, local_ip, local_port).await;
context.switch(nat_info.nat_type);
let new_ip = Ipv4Addr::from(response.virtual_ip);
let current_ip = current_device.virtual_ip();
-155
View File
@@ -1,155 +0,0 @@
use std::collections::HashSet;
use std::net::{IpAddr, Ipv4Addr, SocketAddr, UdpSocket};
use std::time::Duration;
use std::{io, thread};
use crate::channel::punch::NatType;
/// 返回所有公网ip和端口变化范围
pub fn public_ip_list(addrs: &Vec<SocketAddr>) -> io::Result<(NatType, Vec<Ipv4Addr>, u16)> {
let mut hash_set = HashSet::new();
let mut max_port_range = 0;
let mut nat_type = NatType::Cone;
let mut port = 88;
for _ in 0..3 {
let udp = loop {
match UdpSocket::bind(SocketAddr::new(IpAddr::from(Ipv4Addr::from(0)), port)) {
Ok(udp) => {
break udp;
}
Err(e) => {
if e.kind() == io::ErrorKind::AddrInUse {
port += 1;
continue;
}
return Err(e);
}
}
};
let (set, min_port, max_port) = public_ip_list_(&udp, addrs)?;
drop(udp);
let port_range = max_port - min_port;
//有多个ip或者端口有变化,说明是对称nat
if nat_type == NatType::Cone && (set.len() > 1 || port_range != 0) {
nat_type = NatType::Symmetric;
}
if max_port_range < port_range {
max_port_range = port_range;
}
for x in set {
hash_set.insert(x);
}
thread::sleep(Duration::from_micros(5));
}
Ok((nat_type, hash_set.into_iter().collect(), max_port_range))
}
/// 测试样本较少,可能不对
///
/// - 移动宽带:锥形网络、一个ip、端口和局域网端口不相同
/// - 电信宽带:锥形网络、一个ip,端口和局域网端口不相同
/// - 联调宽带:对称网络、端口不变ip轮流用
/// - 移动4g:对称网络、ip端口都变 使用小的端口变化量小
/// - 联通4g:对称网络、只有一个ip 端口变化大
/// - 电信4g:对称网络只有一个ip 公网端口比较连续
/// - 综上:客户端使用小端口,针对对称网络 尝试所有ip 公网端口+-变化量的范围
/// - 打通概率 移动宽带=电信宽带>联调宽带>电信4g>移动4g>>联调4g
pub fn public_ip_list_(
udp: &UdpSocket,
addrs: &Vec<SocketAddr>,
) -> io::Result<(HashSet<Ipv4Addr>, u16, u16)> {
udp.set_read_timeout(Some(Duration::from_millis(300)))?;
let mut buf = [0u8; 128];
for addr in addrs {
let _ = udp.send_to(b"NatTest", addr)?;
}
let mut hash_set = HashSet::new();
let mut count = 0;
let mut min_port = 65535;
let mut max_port = 0;
for _ in 0..addrs.len() {
if let Ok(len) = udp.recv(&mut buf) {
if len != 16 || &buf[..10] != &b"NatType213"[..] {
continue;
}
let port = u16::from_be_bytes([buf[14], buf[15]]);
if min_port > port {
min_port = port;
}
if max_port < port {
max_port = port;
}
let ip = Ipv4Addr::new(buf[10], buf[11], buf[12], buf[13]);
hash_set.insert(ip);
count += 1;
}
}
if count <= 1 {
return Err(io::Error::from(io::ErrorKind::TimedOut));
}
Ok((hash_set, min_port, max_port))
}
/// 返回nat类型
pub fn nat_test() -> io::Result<NatType> {
for _ in 0..3 {
if NatType::Symmetric == nat_test_()? {
return Ok(NatType::Symmetric);
}
thread::sleep(Duration::from_micros(5));
}
Ok(NatType::Cone)
}
pub fn nat_test_() -> io::Result<NatType> {
let udp = UdpSocket::bind("0.0.0.0:0")?;
udp.set_read_timeout(Some(Duration::from_millis(300)))?;
let mut buf = [0u8; 128];
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35061")?;
let _ = udp.send_to(b"NatTest", "nat1.wherewego.top:35062")?;
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35061")?;
let _ = udp.send_to(b"NatTest", "nat2.wherewego.top:35062")?;
let mut tmp_ip_port: Option<[u8; 6]> = None;
let mut count = 0;
for _ in 0..4 {
if let Ok(len) = udp.recv(&mut buf) {
if len != 16 || &buf[..10] != &b"NatType213"[..] {
continue;
}
count += 1;
let mut ip_port = [0u8; 6];
ip_port.copy_from_slice(&buf[10..16]);
if let Some(tmp_ip_port) = &tmp_ip_port {
if tmp_ip_port != &ip_port {
return Ok(NatType::Symmetric);
}
} else {
tmp_ip_port = Some(ip_port);
}
}
}
if count <= 1 {
return Err(io::Error::from(io::ErrorKind::TimedOut));
}
Ok(NatType::Cone)
}
#[test]
fn nat_test_run() {
let udp = UdpSocket::bind("0.0.0.0:101").unwrap();
use std::net::{IpAddr, Ipv4Addr, SocketAddr, ToSocketAddrs, UdpSocket};
let addrs = vec![
"nat1.wherewego.top:35062"
.to_socket_addrs()
.unwrap()
.next()
.unwrap(),
"nat2.wherewego.top:35062"
.to_socket_addrs()
.unwrap()
.next()
.unwrap(),
];
let print = public_ip_list_(&udp, &addrs).unwrap();
println!("{:?}", print);
}
+20 -17
View File
@@ -1,13 +1,14 @@
use crate::proto::message::PunchNatType;
use parking_lot::Mutex;
use std::io;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::net::{IpAddr, Ipv4Addr};
use std::net::UdpSocket;
use std::sync::Arc;
pub mod check;
use parking_lot::Mutex;
use std::net::UdpSocket;
use crate::channel::punch::{NatInfo, NatType};
use crate::proto::message::PunchNatType;
mod stun_test;
pub fn local_ip() -> io::Result<Ipv4Addr> {
let socket = UdpSocket::bind("0.0.0.0:0")?;
@@ -25,7 +26,7 @@ pub fn local_ip() -> io::Result<Ipv4Addr> {
#[derive(Clone)]
pub struct NatTest {
nat_test_server: Arc<Vec<SocketAddr>>,
stun_server: Vec<String>,
info: Arc<Mutex<NatInfo>>,
}
@@ -48,22 +49,24 @@ impl Into<NatType> for PunchNatType {
}
impl NatTest {
pub fn new(
nat_test_server: Vec<SocketAddr>,
pub async fn new(
mut stun_server: Vec<String>,
public_ip: Ipv4Addr,
public_port: u16,
local_ip: Ipv4Addr,
local_port: u16,
) -> NatTest {
let server = stun_server[0].clone();
stun_server.resize(3, server);
let info = NatTest::re_test_(
&nat_test_server,
&stun_server,
public_ip,
public_port,
local_ip,
local_port,
);
).await;
NatTest {
nat_test_server: Arc::new(nat_test_server),
stun_server,
info: Arc::new(Mutex::new(info)),
}
}
@@ -77,7 +80,7 @@ impl NatTest {
guard.public_ips.push(ip);
}
}
pub fn re_test(
pub async fn re_test(
&self,
public_ip: Ipv4Addr,
public_port: u16,
@@ -85,23 +88,23 @@ impl NatTest {
local_port: u16,
) -> NatInfo {
let info = NatTest::re_test_(
&self.nat_test_server,
&self.stun_server,
public_ip,
public_port,
local_ip,
local_port,
);
).await;
*self.info.lock() = info.clone();
info
}
fn re_test_(
nat_test_server: &Vec<SocketAddr>,
async fn re_test_(
stun_server: &Vec<String>,
public_ip: Ipv4Addr,
public_port: u16,
local_ip: Ipv4Addr,
local_port: u16,
) -> NatInfo {
return match check::public_ip_list(nat_test_server) {
return match stun_test::stun_test_nat(stun_server.clone()).await {
Ok((nat_type, ips, port_range)) => {
let mut public_ips = Vec::new();
public_ips.push(Ipv4Addr::from(public_ip));
+129
View File
@@ -0,0 +1,129 @@
use std::collections::HashSet;
use std::io;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
use std::time::Duration;
use stun_format::Attr;
use tokio::net::UdpSocket;
use crate::channel::punch::NatType;
pub async fn stun_test_nat(stun_servers: Vec<String>) -> io::Result<(NatType, Vec<Ipv4Addr>, u16)> {
let mut h = Vec::new();
for x in stun_servers {
let handle = tokio::spawn(test_nat(x));
h.push(handle);
}
let mut nat_type = NatType::Cone;
let mut port_range = 0;
let mut hash_set = HashSet::new();
for x in h {
if let Ok(rs) = x.await {
if let Ok((nat_type_t, ip_list_t, port_range_t)) = rs {
if nat_type_t == NatType::Symmetric {
nat_type = NatType::Symmetric;
}
for x in ip_list_t {
hash_set.insert(x);
}
if port_range < port_range_t {
port_range = port_range_t;
}
}
}
}
Ok((nat_type, hash_set.into_iter().collect(), port_range))
}
async fn test_nat(stun_server: String) -> io::Result<(NatType, Vec<Ipv4Addr>, u16)> {
let udp = UdpSocket::bind("0.0.0.0:0").await?;
udp.connect(stun_server).await?;
let mut nat_type = NatType::Cone;
let mut port_range = 0;
let mut hash_set = HashSet::new();
match test_nat_(&udp, true, true).await {
Ok((mapped_addr1, changed_addr1)) => {
match mapped_addr1.ip() {
IpAddr::V4(ip) => {
hash_set.insert(ip);
}
IpAddr::V6(_) => {}
}
if udp.connect(changed_addr1).await.is_ok() {
if let Ok((mapped_addr2, _)) = test_nat_(&udp, false, false).await {
match mapped_addr2.ip() {
IpAddr::V4(ip) => {
hash_set.insert(ip);
}
IpAddr::V6(_) => {}
}
port_range = mapped_addr2.port().abs_diff(mapped_addr1.port());
if mapped_addr1 != mapped_addr2 {
nat_type = NatType::Symmetric;
}
}
}
}
Err(_) => {}
}
Ok((nat_type, hash_set.into_iter().collect(), port_range))
}
async fn test_nat_(udp: &UdpSocket, change_ip: bool, change_port: bool) -> io::Result<(SocketAddr, SocketAddr)> {
for _ in 0..2 {
let mut buf = [0u8; 28];
let mut msg = stun_format::MsgBuilder::from(buf.as_mut_slice());
msg.typ(stun_format::MsgType::BindingRequest).unwrap();
msg.tid(1).unwrap();
msg.add_attr(Attr::ChangeRequest { change_ip, change_port }).unwrap();
udp.send(msg.as_bytes()).await?;
let mut buf = [0; 10240];
let (len, addr) = match tokio::time::timeout(Duration::from_millis(300), udp.recv_from(&mut buf)).await {
Ok(rs) => { rs? }
Err(_) => {
continue;
}
};
let msg = stun_format::Msg::from(&buf[..len]);
let mut mapped_addr = None;
let mut changed_addr = None;
for x in msg.attrs_iter() {
match x {
Attr::MappedAddress(addr) => {
if mapped_addr.is_none() {
let _ = mapped_addr.insert(stun_addr(addr));
}
}
Attr::ChangedAddress(addr) => {
if changed_addr.is_none() {
let _ = changed_addr.insert(stun_addr(addr));
}
}
Attr::XorMappedAddress(addr) => {
if mapped_addr.is_none() {
let _ = mapped_addr.insert(stun_addr(addr));
}
}
_ => {}
}
if changed_addr.is_some() && mapped_addr.is_some() {
return Ok((mapped_addr.unwrap(), changed_addr.unwrap()));
}
}
if mapped_addr.is_some() {
return Ok((mapped_addr.unwrap(), changed_addr.unwrap_or(addr)));
}
}
Err(io::Error::new(io::ErrorKind::Other, "stun response err"))
}
fn stun_addr(addr: stun_format::SocketAddr) -> SocketAddr {
match addr {
stun_format::SocketAddr::V4(ip, port) => {
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(ip), port))
}
stun_format::SocketAddr::V6(ip, port) => {
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(ip), port, 0, 0))
}
}
}