2007-12-09 05:15:24 +01:00
|
|
|
#include "libtorrent/udp_socket.hpp"
|
|
|
|
#include "libtorrent/connection_queue.hpp"
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <boost/bind.hpp>
|
|
|
|
#include <boost/lexical_cast.hpp>
|
|
|
|
#include <boost/array.hpp>
|
2008-05-20 08:03:46 +02:00
|
|
|
#if BOOST_VERSION < 103500
|
|
|
|
#include <asio/read.hpp>
|
|
|
|
#else
|
2008-05-03 18:05:42 +02:00
|
|
|
#include <boost/asio/read.hpp>
|
2008-05-20 08:03:46 +02:00
|
|
|
#endif
|
2007-12-09 05:15:24 +01:00
|
|
|
|
|
|
|
using namespace libtorrent;
|
|
|
|
|
|
|
|
udp_socket::udp_socket(asio::io_service& ios, udp_socket::callback_t const& c
|
|
|
|
, connection_queue& cc)
|
|
|
|
: m_callback(c)
|
|
|
|
, m_ipv4_sock(ios)
|
|
|
|
, m_ipv6_sock(ios)
|
|
|
|
, m_bind_port(0)
|
|
|
|
, m_socks5_sock(ios)
|
|
|
|
, m_connection_ticket(-1)
|
|
|
|
, m_cc(cc)
|
|
|
|
, m_resolver(ios)
|
|
|
|
, m_tunnel_packets(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::send(udp::endpoint const& ep, char const* p, int len, error_code& ec)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
2008-04-22 23:02:05 +02:00
|
|
|
if (ec == asio::error::operation_aborted) return;
|
2008-04-22 22:56:10 +02:00
|
|
|
|
2007-12-09 05:15:24 +01:00
|
|
|
if (m_tunnel_packets)
|
|
|
|
{
|
|
|
|
// send udp packets through SOCKS5 server
|
2008-02-05 07:32:10 +01:00
|
|
|
wrap(ep, p, len, ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ep.address().is_v4() && m_ipv4_sock.is_open())
|
|
|
|
m_ipv4_sock.send_to(asio::buffer(p, len), ep, 0, ec);
|
|
|
|
else
|
|
|
|
m_ipv6_sock.send_to(asio::buffer(p, len), ep, 0, ec);
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::on_read(udp::socket* s, error_code const& e, std::size_t bytes_transferred)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
2008-04-22 22:56:10 +02:00
|
|
|
if (e == asio::error::operation_aborted) return;
|
|
|
|
|
2008-03-29 05:38:00 +01:00
|
|
|
if (!m_callback) return;
|
|
|
|
|
2008-03-25 05:46:18 +01:00
|
|
|
if (e)
|
|
|
|
{
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
try {
|
|
|
|
#endif
|
|
|
|
if (s == &m_ipv4_sock)
|
|
|
|
m_callback(e, m_v4_ep, 0, 0);
|
|
|
|
else
|
|
|
|
m_callback(e, m_v6_ep, 0, 0);
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
} catch(std::exception&) {}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// don't stop listening on recoverable errors
|
|
|
|
if (e != asio::error::host_unreachable
|
|
|
|
&& e != asio::error::fault
|
|
|
|
&& e != asio::error::connection_reset
|
|
|
|
&& e != asio::error::connection_refused
|
|
|
|
&& e != asio::error::connection_aborted
|
|
|
|
&& e != asio::error::message_size)
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (s == &m_ipv4_sock)
|
|
|
|
s->async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
|
|
|
|
, m_v4_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
|
|
|
|
else
|
|
|
|
s->async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
|
|
|
|
, m_v6_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
2007-12-09 05:15:24 +01:00
|
|
|
|
|
|
|
if (s == &m_ipv4_sock)
|
|
|
|
{
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
try {
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (m_tunnel_packets && m_v4_ep == m_proxy_addr)
|
2008-03-25 05:46:18 +01:00
|
|
|
unwrap(e, m_v4_buf, bytes_transferred);
|
2007-12-09 05:15:24 +01:00
|
|
|
else
|
2008-03-25 05:46:18 +01:00
|
|
|
m_callback(e, m_v4_ep, m_v4_buf, bytes_transferred);
|
2007-12-09 05:15:24 +01:00
|
|
|
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
} catch(std::exception&) {}
|
|
|
|
#endif
|
|
|
|
s->async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
|
|
|
|
, m_v4_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
try {
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (m_tunnel_packets && m_v6_ep == m_proxy_addr)
|
2008-03-25 05:46:18 +01:00
|
|
|
unwrap(e, m_v6_buf, bytes_transferred);
|
2007-12-09 05:15:24 +01:00
|
|
|
else
|
2008-03-25 05:46:18 +01:00
|
|
|
m_callback(e, m_v6_ep, m_v6_buf, bytes_transferred);
|
2007-12-09 05:15:24 +01:00
|
|
|
|
|
|
|
#ifndef BOOST_NO_EXCEPTIONS
|
|
|
|
} catch(std::exception&) {}
|
|
|
|
#endif
|
|
|
|
s->async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
|
|
|
|
, m_v6_ep, boost::bind(&udp_socket::on_read, this, s, _1, _2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::wrap(udp::endpoint const& ep, char const* p, int len, error_code& ec)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
char header[20];
|
|
|
|
char* h = header;
|
|
|
|
|
|
|
|
write_uint16(0, h); // reserved
|
|
|
|
write_uint8(0, h); // fragment
|
|
|
|
write_uint8(ep.address().is_v4()?1:4, h); // atyp
|
|
|
|
write_address(ep.address(), h);
|
|
|
|
write_uint16(ep.port(), h);
|
|
|
|
|
|
|
|
boost::array<asio::const_buffer, 2> iovec;
|
|
|
|
iovec[0] = asio::const_buffer(header, h - header);
|
|
|
|
iovec[1] = asio::const_buffer(p, len);
|
|
|
|
|
|
|
|
if (m_proxy_addr.address().is_v4() && m_ipv4_sock.is_open())
|
|
|
|
m_ipv4_sock.send_to(iovec, m_proxy_addr, 0, ec);
|
|
|
|
else
|
|
|
|
m_ipv6_sock.send_to(iovec, m_proxy_addr, 0, ec);
|
|
|
|
}
|
|
|
|
|
|
|
|
// unwrap the UDP packet from the SOCKS5 header
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::unwrap(error_code const& e, char const* buf, int size)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
// the minimum socks5 header size
|
|
|
|
if (size <= 10) return;
|
|
|
|
|
|
|
|
char const* p = buf;
|
|
|
|
p += 2; // reserved
|
|
|
|
int frag = read_uint8(p);
|
|
|
|
// fragmentation is not supported
|
|
|
|
if (frag != 0) return;
|
|
|
|
|
|
|
|
udp::endpoint sender;
|
|
|
|
|
|
|
|
int atyp = read_uint8(p);
|
|
|
|
if (atyp == 1)
|
|
|
|
{
|
|
|
|
// IPv4
|
2008-03-28 23:50:41 +01:00
|
|
|
sender = read_v4_endpoint<udp::endpoint>(p);
|
2007-12-09 05:15:24 +01:00
|
|
|
}
|
|
|
|
else if (atyp == 4)
|
|
|
|
{
|
|
|
|
// IPv6
|
2008-03-28 23:50:41 +01:00
|
|
|
sender = read_v6_endpoint<udp::endpoint>(p);
|
2007-12-09 05:15:24 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// domain name not supported
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2008-03-25 05:46:18 +01:00
|
|
|
m_callback(e, sender, p, size - (p - buf));
|
2007-12-09 05:15:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
void udp_socket::close()
|
|
|
|
{
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-30 00:47:51 +01:00
|
|
|
m_ipv4_sock.close(ec);
|
|
|
|
m_ipv6_sock.close(ec);
|
|
|
|
m_socks5_sock.close(ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
m_callback.clear();
|
|
|
|
if (m_connection_ticket >= 0)
|
|
|
|
{
|
|
|
|
m_cc.done(m_connection_ticket);
|
|
|
|
m_connection_ticket = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::bind(udp::endpoint const& ep, error_code& ec)
|
2008-02-05 07:32:10 +01:00
|
|
|
{
|
|
|
|
if (m_ipv4_sock.is_open()) m_ipv4_sock.close(ec);
|
|
|
|
if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec);
|
|
|
|
|
|
|
|
if (ep.address().is_v4())
|
|
|
|
{
|
|
|
|
m_ipv4_sock.open(udp::v4(), ec);
|
|
|
|
if (ec) return;
|
|
|
|
m_ipv4_sock.bind(ep, ec);
|
|
|
|
if (ec) return;
|
|
|
|
m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
|
|
|
|
, m_v4_ep, boost::bind(&udp_socket::on_read, this, &m_ipv4_sock, _1, _2));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
m_ipv6_sock.set_option(v6only(true), ec);
|
|
|
|
if (ec) return;
|
|
|
|
m_ipv6_sock.bind(ep, ec);
|
|
|
|
if (ec) return;
|
|
|
|
m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
|
|
|
|
, m_v6_ep, boost::bind(&udp_socket::on_read, this, &m_ipv6_sock, _1, _2));
|
|
|
|
}
|
|
|
|
m_bind_port = ep.port();
|
|
|
|
}
|
|
|
|
|
2007-12-09 05:15:24 +01:00
|
|
|
void udp_socket::bind(int port)
|
|
|
|
{
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-09 05:15:24 +01:00
|
|
|
|
2007-12-30 00:47:51 +01:00
|
|
|
if (m_ipv4_sock.is_open()) m_ipv4_sock.close(ec);
|
|
|
|
if (m_ipv6_sock.is_open()) m_ipv6_sock.close(ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
|
|
|
|
m_ipv4_sock.open(udp::v4(), ec);
|
|
|
|
if (!ec)
|
|
|
|
{
|
|
|
|
m_ipv4_sock.bind(udp::endpoint(address_v4::any(), port), ec);
|
|
|
|
m_ipv4_sock.async_receive_from(asio::buffer(m_v4_buf, sizeof(m_v4_buf))
|
|
|
|
, m_v4_ep, boost::bind(&udp_socket::on_read, this, &m_ipv4_sock, _1, _2));
|
|
|
|
}
|
|
|
|
m_ipv6_sock.open(udp::v6(), ec);
|
|
|
|
if (!ec)
|
|
|
|
{
|
|
|
|
m_ipv6_sock.set_option(v6only(true), ec);
|
|
|
|
m_ipv6_sock.bind(udp::endpoint(address_v6::any(), port), ec);
|
|
|
|
m_ipv6_sock.async_receive_from(asio::buffer(m_v6_buf, sizeof(m_v6_buf))
|
|
|
|
, m_v6_ep, boost::bind(&udp_socket::on_read, this, &m_ipv6_sock, _1, _2));
|
|
|
|
}
|
|
|
|
m_bind_port = port;
|
|
|
|
}
|
|
|
|
|
|
|
|
void udp_socket::set_proxy_settings(proxy_settings const& ps)
|
|
|
|
{
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-30 00:47:51 +01:00
|
|
|
m_socks5_sock.close(ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
m_tunnel_packets = false;
|
|
|
|
|
|
|
|
m_proxy_settings = ps;
|
|
|
|
|
|
|
|
if (ps.type == proxy_settings::socks5
|
|
|
|
|| ps.type == proxy_settings::socks5_pw)
|
|
|
|
{
|
|
|
|
// connect to socks5 server and open up the UDP tunnel
|
|
|
|
tcp::resolver::query q(ps.hostname
|
|
|
|
, boost::lexical_cast<std::string>(ps.port));
|
|
|
|
m_resolver.async_resolve(q, boost::bind(
|
|
|
|
&udp_socket::on_name_lookup, this, _1, _2));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::on_name_lookup(error_code const& e, tcp::resolver::iterator i)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
m_proxy_addr.address(i->endpoint().address());
|
|
|
|
m_proxy_addr.port(i->endpoint().port());
|
|
|
|
m_cc.enqueue(boost::bind(&udp_socket::on_connect, this, _1)
|
|
|
|
, boost::bind(&udp_socket::on_timeout, this), seconds(10));
|
|
|
|
}
|
|
|
|
|
|
|
|
void udp_socket::on_timeout()
|
|
|
|
{
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-30 00:47:51 +01:00
|
|
|
m_socks5_sock.close(ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
m_connection_ticket = -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
void udp_socket::on_connect(int ticket)
|
|
|
|
{
|
|
|
|
m_connection_ticket = ticket;
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-09 05:15:24 +01:00
|
|
|
m_socks5_sock.open(m_proxy_addr.address().is_v4()?tcp::v4():tcp::v6(), ec);
|
|
|
|
m_socks5_sock.async_connect(tcp::endpoint(m_proxy_addr.address(), m_proxy_addr.port())
|
|
|
|
, boost::bind(&udp_socket::on_connected, this, _1));
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::on_connected(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
m_cc.done(m_connection_ticket);
|
|
|
|
m_connection_ticket = -1;
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
// send SOCKS5 authentication methods
|
|
|
|
char* p = &m_tmp_buf[0];
|
|
|
|
write_uint8(5, p); // SOCKS VERSION 5
|
|
|
|
if (m_proxy_settings.username.empty()
|
|
|
|
|| m_proxy_settings.type == proxy_settings::socks5)
|
|
|
|
{
|
|
|
|
write_uint8(1, p); // 1 authentication method (no auth)
|
|
|
|
write_uint8(0, p); // no authentication
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
write_uint8(2, p); // 2 authentication methods
|
|
|
|
write_uint8(0, p); // no authentication
|
|
|
|
write_uint8(2, p); // username/password
|
|
|
|
}
|
|
|
|
asio::async_write(m_socks5_sock, asio::buffer(m_tmp_buf, p - m_tmp_buf)
|
|
|
|
, boost::bind(&udp_socket::handshake1, this, _1));
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::handshake1(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
asio::async_read(m_socks5_sock, asio::buffer(m_tmp_buf, 2)
|
|
|
|
, boost::bind(&udp_socket::handshake2, this, _1));
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::handshake2(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
char* p = &m_tmp_buf[0];
|
|
|
|
int version = read_uint8(p);
|
|
|
|
int method = read_uint8(p);
|
|
|
|
|
|
|
|
if (version < 5) return;
|
|
|
|
|
|
|
|
if (method == 0)
|
|
|
|
{
|
|
|
|
socks_forward_udp();
|
|
|
|
}
|
|
|
|
else if (method == 2)
|
|
|
|
{
|
|
|
|
if (m_proxy_settings.username.empty())
|
|
|
|
{
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-30 00:47:51 +01:00
|
|
|
m_socks5_sock.close(ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// start sub-negotiation
|
|
|
|
char* p = &m_tmp_buf[0];
|
|
|
|
write_uint8(1, p);
|
|
|
|
write_uint8(m_proxy_settings.username.size(), p);
|
|
|
|
write_string(m_proxy_settings.username, p);
|
|
|
|
write_uint8(m_proxy_settings.password.size(), p);
|
|
|
|
write_string(m_proxy_settings.password, p);
|
|
|
|
asio::async_write(m_socks5_sock, asio::buffer(m_tmp_buf, p - m_tmp_buf)
|
|
|
|
, boost::bind(&udp_socket::handshake3, this, _1));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-05-03 18:05:42 +02:00
|
|
|
error_code ec;
|
2007-12-30 00:47:51 +01:00
|
|
|
m_socks5_sock.close(ec);
|
2007-12-09 05:15:24 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::handshake3(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
asio::async_read(m_socks5_sock, asio::buffer(m_tmp_buf, 2)
|
|
|
|
, boost::bind(&udp_socket::handshake4, this, _1));
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::handshake4(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
char* p = &m_tmp_buf[0];
|
|
|
|
int version = read_uint8(p);
|
|
|
|
int status = read_uint8(p);
|
|
|
|
|
|
|
|
if (version != 1) return;
|
|
|
|
if (status != 0) return;
|
|
|
|
|
|
|
|
socks_forward_udp();
|
|
|
|
}
|
|
|
|
|
|
|
|
void udp_socket::socks_forward_udp()
|
|
|
|
{
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
// send SOCKS5 UDP command
|
|
|
|
char* p = &m_tmp_buf[0];
|
|
|
|
write_uint8(5, p); // SOCKS VERSION 5
|
|
|
|
write_uint8(3, p); // UDP ASSOCIATE command
|
|
|
|
write_uint8(0, p); // reserved
|
|
|
|
write_uint8(0, p); // ATYP IPv4
|
|
|
|
write_uint32(0, p); // IP any
|
|
|
|
write_uint16(m_bind_port, p);
|
|
|
|
|
|
|
|
asio::async_write(m_socks5_sock, asio::buffer(m_tmp_buf, p - m_tmp_buf)
|
|
|
|
, boost::bind(&udp_socket::connect1, this, _1));
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::connect1(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
asio::async_read(m_socks5_sock, asio::buffer(m_tmp_buf, 10)
|
|
|
|
, boost::bind(&udp_socket::connect2, this, _1));
|
|
|
|
}
|
|
|
|
|
2008-05-03 18:05:42 +02:00
|
|
|
void udp_socket::connect2(error_code const& e)
|
2007-12-09 05:15:24 +01:00
|
|
|
{
|
|
|
|
if (e) return;
|
|
|
|
|
|
|
|
using namespace libtorrent::detail;
|
|
|
|
|
|
|
|
char* p = &m_tmp_buf[0];
|
|
|
|
int version = read_uint8(p); // VERSION
|
|
|
|
int status = read_uint8(p); // STATUS
|
|
|
|
read_uint8(p); // RESERVED
|
|
|
|
int atyp = read_uint8(p); // address type
|
|
|
|
|
|
|
|
if (version != 5) return;
|
|
|
|
if (status != 0) return;
|
|
|
|
|
|
|
|
if (atyp == 1)
|
|
|
|
{
|
|
|
|
m_proxy_addr.address(address_v4(read_uint32(p)));
|
|
|
|
m_proxy_addr.port(read_uint16(p));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// in this case we need to read more data from the socket
|
|
|
|
TORRENT_ASSERT(false && "not implemented yet!");
|
|
|
|
}
|
|
|
|
|
|
|
|
m_tunnel_packets = true;
|
|
|
|
}
|
|
|
|
|