This tool can be used to hide your traffic and/or pass certain firewalls. You need a remote machine that is secure and has full internet access. Both local client and the remote server should run Linux as the operating systems (although a windows client is possible too, theoretically). Only one client can connect to the server at a time.
Python 3.6 or newer is required. The Python implementation uses only the standard library.
On the client side, as root or with the required network capabilities:
python3 main.py --key 64BIT_HEX_KEY --connect-to SERVER_IP:SERVER_PORT --tunnel udp \
[--do-random-padding] \
[--mtu TUNNEL_MTU_DEFAULT_TO_1300] \
[--debug] \
[--tun THE_NAME_OF_THE_TUN_DEVICE]
On the server side, as root or with the required network capabilities:
python3 main.py --key SAME_KEY_AS_CLIENT --listen-at SERVER_PORT --tunnel udp \
[--do-random-padding] \
[--mtu SAME_TUNNEL_MTU_AS_CLIENT] \
[--debug] \
[--tun THE_NAME_OF_THE_TUN_DEVICE]
You must turn off reverse path filtering on your server system,
and make sure that the SERVER_PORT is open.
The setup scripts require iproute2, nftables, and sysctl.
They configure a point-to-point link using 10.0.1.1 on the client and
10.0.1.2 on the server, with each peer represented as a /32 route.
The --mtu value is applied to both TUN interfaces and must match on both peers.
The client warns and continues setup if the configured MTU plus the outer
IPv4, UDP, nonce, and end-marker overhead exceeds the route MTU to the server
(or the interface MTU when no route-specific MTU is available). It keeps the
configured tunnel MTU unchanged. A smaller path MTU beyond the directly connected interface may
still require selecting a more conservative value manually.
For example, a 1500-byte underlay allows a tunnel MTU of up to 1456 bytes without exceeding that limit
(20 bytes IPv4 + 8 bytes UDP + 16 bytes framing overhead).
Runtime state is stored under /run/ipudp so cleanup can restore the client's
original default routes and the server's original IPv4 forwarding setting.
Set IPUDP_STATE_DIR to use another state directory. If setup or cleanup fails,
the program exits with an error instead of continuing with partial network
configuration.
Without --debug, you should see traffic statistics every 5 seconds, on both
sides, if ipudp is running normally. With --debug, every forwarded IP
packet produces a line on standard output containing a UTC timestamp, the local
client or server role, the packet-flow event, and the source and destination IP
addresses.
ipudp works as follows:
__________ Local Machine _________ ______________ __________ Remote Server ___________
| | | firewall | | |
| applications |--UDP--|-----|------------|------|-UDP--> script |--|-----> target
| | | | |(encrypted | | | | |
| | routing |encrypt| |UDP traffic)| | decrypt | MASQUERADE | |
| V | | | | | V | |
| TUN virtual device --> script | | | | TUN virtual device --nftables--| |
|__________________________________| |____________| |____________________________________|
(and vice versa)
The "imaginary enemy" of this tool are advanced firewalls with the capability of deep packet inspection (DPI), traffic pattern analysis, or even active sniffing.
Here are some details about the design space of ipudp:
-
the use of TUN virtual device means any IP traffic can be tunneled by
ipudp, including TCP, UDP, or even ICMP, without the need to configure applications. But it also means that only one client can connect to the remote server at a time, as port mapping is impossible at the Networking Layer. -
The protocol for communication between client and server is UDP. As the connectionless property of UDP can reduce tunneling overhead. However, other protocols are possible, too. Again, the tunneling part is designed to be modular. When UDP is not the best option, switching to other protocols like TCP or ICMP should be easy.
Each UDP payload is the encryption of this complete layout:
8-byte random nonce | IP packet | optional random padding | data-end. The nonce is freshly generated from the operating system's random source for every packet. The final marker is exactly eight ASCII bytes (data-end). The cipher resets before each datagram and uses little-endian 64-bit blocks. After decryption, a missing final marker is treated as a decryption failure. The receiver extracts the IP packet using IPv4's Total Length field or 40 plus IPv6's Payload Length field, both in network byte order, discarding padding. Invalid/truncated headers and lengths exceeding the available data are rejected. IPv6 jumbograms cannot fit within the UDP payload limit and are not supported.This format is incompatible with the previous authentication-prefix and explicit-length format. Upgrade both peers together;
-authhas been removed. -
Instead of using complicated ciphers like AES, TLS, etc..
ipudpuses a very simple reactive (stream cipher with key stream dependent on all previous plaintext bytes as well) cipher. This cipher is undoubtedly uncomparable to well-tested industrial ciphers. However, the point is, for any ciphers, once widely used, even if the firewall can't decrypt ciphertext directly, discovering the underlying cipher via various methods, such as machine learning, is possible. However, if every individual user uses a simple but unique cipher, it would be impossible to discover the pattern. The crypto part ofipudp(crypto.py) is designed in such a way, that one can easily implement a different cipher. That's also the encouraged way to use this tool. Just take it as a cryptographic mind-storming ;) -
To avoid traffic pattern analysis (usually based on packet size),
ipudpoffers optional random padding. With--do-random-padding, each IP packet smaller than the tunnel MTU receives a uniformly selected number of padding bytes between zero and the remaining space. The padding bytes come from the operating system's random source, and the padded data area never exceeds the tunnel MTU. Padding is disabled by default. -
The fixed
data-endmarker provides a simple decryption check, not a cryptographic authentication guarantee. The custom cipher and marker are not production-grade cryptography. Current approach completely eliminates the traffic pattern of hand-shaking.
tun.py: interface to TUN virtual devices.crypto.py: cryptographic stuffs. If you want to use an alternative cipher, simply modify the methods of theEncrypterandDecrypterclasses. The interface is as follows:
# class Encrypter
__init__(self, key): initializer
reset(self):
Reset the state of the cipher. Used for stateful (stream) ciphers
encrypt(self, data):
copy and return an encrypted version of [data].
You don't need to modify this, as it is derived from [encrypt_in_place].
encrypt_in_place(self, data):
[data] being a mutable bytes-compatible object,
encrypt [data] in place.
# class Decrypter
__init__(self, key): initializer
reset(self):
Reset the state of the cipher. Used for stateful (stream) ciphers
decrypt(self, data):
copy and return an decrypted version of [data].
You don't need to modify this, as it is derived from [encrypt_in_place].
decrypt_in_place(self, data):
[data] being a mutable bytes-compatible object,
decrypt [data] in place.
logger.py: For logging facilities. Modify this if not satisfied with the UI. Interface:
# module function
packet_event(role, event, packet):
Return a timestamped debug line containing the role, event, and IP addresses.
# class Logger:
__init__(self, freq, target=sys.stdout):
[freq] is the frequency to log traffic, in seconds.
[target] is where the logger logs to
log(self, msg):
Output [msg]. Should output unconditionally.
log_traffic(self):
Output current traffic statistics. Should only output after at least
[freq] seconds since the last output of traffic.
add_traffic(self, mode, size):
[mode] is 'i' for inbound traffic or 'o' for outbound traffic.
[size] is the size of new traffic, in bytes.
Should call [log_traffic] to flush the changes.
udp.py: Implementation of the UDP tunnel. Check this if you want to implement an alternative authentication method or padding strategy. Interface:
# class UDPTun
__init__(
self,
mode, # 'c' for client side, 's' for server side
addr, # (server_ip, server_port) pair for client side, ("", port) for server side
encrypter, decrypter, # with the interface described above
MTU,
do_random_padding, # whether random padding is enabled
logger # as described above
): initializer
socket:
The socket used for the tunnel.
A possible improvement would be to make the whole class selectors-compatible.
send(self, data): Send data over the tunnel. len(data) <= self.MTU.
Should handle nonce generation, encryption, padding, and end-marker insertion
recv(self): Block and return the next received packet.
Should handle decryption, end-marker verification, and IP-length-based unpadding
client.sh: Client initialization script. Would be called after the intialization of TUN device and the tunnel class. Make the TUN virtual device up, configure the10.0.1.1to10.0.1.2point-to-point link, pin the server endpoint to its original physical route, and replace the client system's default routes with the tunnel route. Available environment variables:
$TUN_NAME: name of the TUN virtual device
$TUN_MTU: MTU configured on the TUN device
$MINIMUM_REQUIRED_UNDERLAY_MTU: minimum route MTU including tunnel overhead
$REMOTE_IP: IP of server
$REMOTE_PORT: target port on server
$IPUDP_STATE_DIR: optional state directory, defaults to /run/ipudp
client-cleanup.sh: Would be called on exit ofipudpat client side. Restores the original default routes and any pre-existing exact route to the server, removes the point-to-point address, and takes the TUN device down. With the same set of available environment variables asclient.shserver.sh: Server initialization script. Would be called after the intialization of TUN device and the tunnel class. Make the TUN virtual device up, configure the10.0.1.2to10.0.1.1point-to-point link, enable IP forwarding on the server, and create a dedicatedip ipudpnftables table for IP masquerading. Available environment variables:
$TUN_NAME: name of the TUN virtual device
$TUN_MTU: MTU configured on the TUN device
$LISTEN_PORT: the port which the server listens on
$IPUDP_STATE_DIR: optional state directory, defaults to /run/ipudp
server-cleanup.sh: Called on server side exit ofipudp. Deletes the dedicated nftables table, restores the original IPv4 forwarding setting, removes the point-to-point address, and takes the TUN device down. With the same set of available environment variables asserver.shmain.py: Entry point. Pretty much just combines the above scripts.
The development image runs the source from a read-only bind mount. The launcher
passes every argument unchanged to main.py:
docker build -f Dockerfile.development -t ipudp-development .
./run-development-container.sh --key 0123456789abcdef \
--listen-at 48625 --tunnel udp
./run-development-container.sh --key 0123456789abcdef \
--connect-to SERVER_IP:48625 --tunnel udpAll normal main.py options, including --mtu, --tun, --debug, and
--do-random-padding, can be supplied in the same way. A --listen-at SERVER_PORT
pair also makes the launcher publish SERVER_PORT/udp on the host.
The server container is created with net.ipv4.ip_forward=1; server.sh
changes and restores that setting only when running in a namespace where it was
initially disabled.
Containers intentionally use an isolated Docker network namespace. A client's default-route replacement affects only its container, leaving the host's Internet route unchanged.
Both launchers configure client and server containers to use Google Public DNS
at 8.8.8.8 and 8.8.4.4. These are conventional plaintext DNS resolvers,
using UDP or TCP port 53 rather than DNS over HTTPS or DNS over TLS. Client DNS
traffic crosses the tunnel after the client route is installed, but it leaves
the tunnel server as plaintext DNS.
The deployment image copies the application source and supports either role:
docker build -f Dockerfile.deployment -t ipudp-deployment .
./run-deployment-container.sh --key 0123456789abcdef \
--listen-at 48625 --tunnel udp
./run-deployment-container.sh --key 0123456789abcdef \
--connect-to SERVER_IP:48625 --tunnel udp