Giter Club home page Giter Club logo

tobaru's Introduction

tobaru

Port forwarding tool written in Rust with advanced features, such as:

  • Multiple target addresses: Forwards to different target addresses based on IP and TLS SNI/ALPN
    • IPv4/IPv6 allowlists: Only forwards connections from known IP ranges
    • TLS support:
      • Allow both TLS and non-TLS clients on a single port
      • Connect to TLS and non-TLS endpoints
  • Hot reloading: Updated configs are automatically reloaded
  • iptables support: Automatically configures iptables to drop packets from unallowed ranges
  • IP groups: named groups of IPs that can be reused amongst different server configurations

Here's a quick example:

- address: 0.0.0.0:443
  transport: tcp
  targets:

    # target 1: non-TLS clients from any IP will be forwarded to 127.0.0.1:2999.
    - location: 127.0.0.1:2999
      allowlist: 0.0.0.0/0

    # target 2: TLS clients from specified IP masks asking for SNI example.com and
    # ALPN protocol http/1.1 will be forwarded to a listening UNIX domain socket.
    - location: /run/service.sock
      server_tls:
        cert: cert.pem
        key: key.pem
        sni_hostnames: example.com
        alpn_protocols: http/1.1
      allowlist:
        - 1.2.3.4
        - 2.3.0.0/16

    # target 3: TLS clients from ip 1.2.3.4 asking for SNI example.com or test.com,
    # and any other ALPN protocol, or no ALPN negotiation, will be forwarded here.
    - location: 127.0.0.1:3001
      server_tls:
        cert: cert.pem
        key: key.pem
        sni_hostnames:
          - example.com
          - test.com
        alpn_protocols:
          - any
          - none
      allowlist: 1.2.3.4

Installation

Precompiled binaries for x86_64 and Apple aarch64 are available on Github Releases.

Else, if you have a fairly recent Rust and cargo installation on your system, tobaru can be installed with cargo.

cargo install tobaru

Usage

USAGE:

    tobaru [OPTIONS] <CONFIG PATH or CONFIG URL> [CONFIG PATH or CONFIG URL] [..]

OPTIONS:

    -t, --threads NUM
        Number of worker threads, defaults to an estimated amount of parallelism.

    --clear-iptables-all
        Clear all tobaru-created rules from iptables and exit immediately.

    --clear-iptables-matching
        Clear tobaru-created rules for the addresses specified in the specified
        config files and exit immediately.

    -h, --help
        Show this help screen.

IPTABLES PERMISSIONS:

    To run iptable commands, this binary needs to have CAP_NET_RAW and CAP_NET_ADMIN
    permissions, or else be invoked by root.

EXAMPLES:

    tobaru -t 1 config1.yaml config2.yaml

        Run servers from configs in config1.yaml and config2.yaml on a single thread.

    tobaru tcp://127.0.0.1:1000?target=127.0.0.1:2000

        Run a tcp server on 127.0.0.1 port 1000, forwarding to 127.0.0.1 port 2000.

    sudo tobaru --clear-iptables-matching config1.yaml

        Clear iptable configs only for the config addresses in config1.yaml.

Upgrading from 0.7.1 or lower

See UPGRADING.md.

URL-based configuration

Simple TCP forwarding can be done using the config URL format:

tcp://<bind ip>:<bind port>?target=<target ip>:<target port>

TCP forwarding to a UNIX domain socket can be done with the target-path key:

tcp://<bind ip>:<bind port>?target-path=<unix domain socket path>

File-based configuration

Configuration files are in the YAML or JSON file format. tobaru expects to read an array of objects, where each object is a server configuration, or an IP mask group.

Server object configuration

address: The address to listen on.

transport: The transport protocol, a string of either tcp or udp.

use_iptables (optional, default: false): Whether to enable iptables support.

  • IP masks in allowlists specified in the targets would be added to iptables, and unspecified IP masks would be denied.

targets (or target): An target location object or an array of target location objects that specify where to forward to.

tcp_nodelay (optional, default: true): Specifies whether to disable Nagle's algorithm on the accepted socket.

  • Only accepted when transport is tcp.

Target object configuration (TCP transport)

locations (or location): A single TCP location, or an array of TCP locations.

  • When an array of multiple locations is provided, connections will be forwarded in a round-robin fashion.

allowlist: A single IP mask or IP group name, or an array of IP mask or IP group names.

tcp_nodelay (optional, default: true): Specifies whether to disable Nagle's algorithm on the target connected socket.

server_tls (optional, default: null): A server TLS object. When non-null, only TLS connections will be accepted for this target. The object keys are:

  • cert: A file path to the TLS certificate.
  • key: A file path to the TLS private key.
  • optional (optional, default: false): Specifies whether TLS is optional. When true, this means that non-TLS streams will also be accepted and forwarded.
  • sni_hostnames (optional, default: any, none): A SNI hostname, or an array of SNI hostnames, with special keywords:
    • any: Accept any provided SNI hostname.
    • none: Accept handshakes without SNI negotiation.
  • alpn_protocols (optional: default: any, none): An accepted ALPN protocol, or an array of supported ALPN protocols, with special keywords:
    • any: Accept any provided ALPN protocol.
    • none: Accept handshakes without ALPN protocol selection.

TCP location configuration

A TCP location can be specified as:

  • an address string. eg. 127.0.0.1:1234

  • a UNIX domain socket path. eg. /path/to/something.sock

  • an object with the following keys:

    address: an address string.

    • only one of address or path can be specified.

    path: a UNIX domain socket path.

    • only one of address or path can be specified.

    client_tls (optional, default: false): Specifies whether to handle TLS when connecting to this location. The supported values are:

    • true: Enables client TLS handling, with certificate verification.
    • no-verify: Enables client TLS handling, without certificate verification.
    • false: Disables client TLS handling.

Target object configuration (UDP transport)

addresses (or address): A single address string, or an array of host:port address strings

allowlist: A single IP mask or IP group name, or an array of IP mask or IP group names.

association_timeout_secs (optional, default: 200): Number of seconds before an inactive UDP association times out.

IP group object configuration

group: Name of the IP group

ip_masks (or ip_mask): A single IP mask or IP group name, or an array of IP mask or IP group names.

A default IP group with name all and IP mask 0.0.0.0/0 is automatically added.

Examples

TCP to TCP forwarding, all IPs allowed

- address: 0.0.0.0:8080
  transport: tcp
  target:
    location: 192.168.8.1:80
    allowlist: all

or with multiple servers and specific IP ranges:

# Forward port 8080 to 192.168.8.1 port 80 for some IP ranges.
- address: 0.0.0.0:8080
  transport: tcp
  targets:
    - address: 192.168.8.1:80
      allowlist:
        # Some local IP ranges..
        - 192.168.9.0/24
        - 192.168.10.0/24

        # .. and some specific IPs
        - 12.34.56.78
        - fa71::e09d:92fa:beef:1234

    - address: 192.168.8.2:80
      allowlist:
        - 192.168.11.0/24
        - 192.168.12.0/24

# Forward port 8081 to 192.168.8.2 port 80 for all IPs.
- address: 0.0.0.0:8081
  transport: udp
  target:
    - address: 192.168.8.3:80
      allowlist:
        - all

Connections from addresses that are not specified in allowlist will either be dropped (if iptables is set to true), or be immediately closed after accept.

Round-robin forwarding

{
  // Listen on all interfaces, port 8080.
  "bindAddress": "0.0.0.0:8080",
  "transport": "tcp",
  "target": {
    // Round-robin forward to different addresses.
    "addresses": [
      "192.168.8.1:80",
      "192.168.8.2:80",
      "192.168.8.3:80",
      "192.168.8.4:80"
    ],
    "allowlist": "all"
  }
}

Multiple destinations based on IP address

{
  // Listen on port 8080
  "bindAddress": "0.0.0.0:8080",
  "transport": "tcp",
  "targets": [
    // Forward some IP ranges to 192.168.8.1 port 80.
    {
      "address": "192.168.8.1:80",
      "allowlist": [
        "192.168.1.0/24",
        "192.168.2.0/24"
      ]
    },
    // Forward other IP ranges to 192.168.8.2 port 80.
    {
      "address": "192.168.8.2:80",
      "allowlist": [
        "192.168.3.0/24",
        "192.168.4.0/24"
      ]
    }
  ]
}

TLS support

[
    // Server listening on port 443 (HTTPS).
    {
      "bindAddress": "192.168.0.1:443",
      "target": {
        // All connections need to use TLS.
        // Enable TLS by specifying the path to the certificate and private key.
        "serverTls": {
          "cert": "/path/to/cert.pem",
          "key": "/path/to/key.pem",
          // Allow clients to connect without TLS.
          "optional": true
        },

        // Also connect to the destination HTTPS server using TLS.
        // '+' (plus sign) means to use TLS.
        "address": "192.168.2.1:+443",
        "allowlist": "all"
      }
    },

    // Server listening on port 443 (HTTPS).
    // Forward in a round-robin manner to various HTTP servers that do not have
    // TLS enabled.
    {
      "bindAddress": "192.168.0.2:443",
      "target": {
        "serverTls": {
          "cert": "/path/to/cert.pem",
          "key": "/path/to/key.pem"
        },
        "addresses": [
          "192.168.2.1:80",
          "192.168.2.2:80",
          "192.168.2.3:80"
        ],
        "allowlist": "all"
      }
    }
]

iptables support

Note that tobaru will need root access in order to configure iptables. It might be possible to do this without root by using setcap(8). Please file a pull request with instructions if you are able to do so.

{
  "bindAddress": "0.0.0.0:8080",
  // Enable iptables auto-configuration.
  "iptables": true,
  "target": {
    "address": "192.168.8.1:80",
    // Allow only the following IP ranges. Packets from other IPs will be dropped.
    "allowlist": [
      "192.168.2.2/24",
      "192.168.2.3/24",
      "192.168.100.50"
    ]
  }
}

IP groups

IP groups can be used to quickly specify groups of IPs in multiple servers. Note that IP ranges can be specified in any file and can be reused across different files, for example, it could be convenient invoke tobaru with all IP groups in an individual file: tobaru ip_groups.json http_servers.json ssh_servers.json

{
  "ipGroups": {
    "local": [
      "192.168.0.0/24",
      "192.168.1.0/24",
      "192.168.2.0/24",
      "192.168.3.0/24"
    ],
    "friends": [
      "1.2.3.4",
      "2.3.4.5"
    ]
  },

  "servers": [
    {
      "bindAddress": "0.0.0.0:8080",
      "target": {
        "address": "192.168.5.1:8080",
        // Only allow IP ranges from 'local' and 'friends' to connect.
        "allowlist": [
          "local",
          "friends"
        ]
      }
    },
    {
      "bindAddress": "0.0.0.0:8081",
      "target": {
        "address": "192.168.5.2:8080",
        // Only allow IP ranges from 'local'.
        "allowlist": "@local"
      }
    }
  ]
}

tobaru's People

Contributors

amosjyng avatar cfal avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

tobaru's Issues

Hot renewal

Hello, do you support hot updates, such as allowlist

tcptunnelchecker shows some failures for tobaru

I have developed a special tool that checks corner cases of TCP forwarding regarding TCP resets/hangups while being backpressured.
It shows discrepancy between connecting back through tobaru and, for example, through socat.

$ cat config.json
{
  // Listen on all interfaces, on port 8080.
  "bindAddress": "127.0.0.1:1234",
  "target": {
    // Forward to 192.168.8.1 port 80.
    "address": "127.0.0.1:1235",
    // Allow connections from all addresses. 'all' is a special alias for 0.0.0.0/0.
    "allowlist": "all"
  }
}

$ tobaru ./config.json
Listening (TCP): 127.0.0.1:1234
[2022-03-16T08:31:00Z ERROR tobaru::tcp] 127.0.0.1:33892 finished with error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
[2022-03-16T08:31:01Z ERROR tobaru::tcp] 127.0.0.1:33896 finished with error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
[2022-03-16T08:31:01Z ERROR tobaru::tcp] 127.0.0.1:33908 finished with error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
[2022-03-16T08:31:02Z ERROR tobaru::tcp] 127.0.0.1:33952 finished with error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
[2022-03-16T08:31:03Z ERROR tobaru::tcp] 127.0.0.1:33956 finished with error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
[2022-03-16T08:31:03Z ERROR tobaru::tcp] 127.0.0.1:33960 finished with error: Os { code: 104, kind: ConnectionReset, message: "Connection reset by peer" }
$ ./tcptunnelchecker 127.0.0.1:1235 127.0.0.1:1234
[ OK ] Trivial test 1
[ OK ] Trivial test 2
[ OK ] Clogged close test passed: outDrainCheck_inClose
[ OK ] Clogged close test passed: outDrainCheck_inDrainClose
[ OK ] Clogged close test passed: outClogCheck_inClose
[ OK ] Clogged close test passed: outClogDrainCheck_inClose
[ OK ] Clogged close test passed: outShutDrainCheck_inClose
[ OK ] Clogged close test passed: outShutDrainCheck_inDrainClose
[ OK ] Clogged close test passed: outClogCheck_inClogClose
[ OK ] Clogged close test passed: outDrainCheck_inShutClose
[ OK ] Clogged close test passed: outDrainCheck_inShutDrainClose
Broken pipe (os error 32)
Broken pipe (os error 32)
[ OK ] Clogged close test passed: outShutDrainCheck_inShutClose
[ OK ] Clogged close test passed: outShutDrainCheck_inShutDrainClose
[ OK ] Clogged close test passed: outClose_inDrainCheck
[ OK ] Clogged close test passed: outDrainClose_inDrainCheck
[ OK ] Clogged close test passed: outShutClose_inDrainCheck
[ OK ] Clogged close test passed: outShutDrainClose_inDrainCheck
[ OK ] Clogged close test passed: outClose_inClogCheck
[ OK ] Clogged close test passed: outClose_inClogDrainCheck
[ OK ] Clogged close test passed: outClogClose_inClogCheck
Broken pipe (os error 32)
Broken pipe (os error 32)
[ OK ] Clogged close test passed: outClose_inShutDrainCheck
[ OK ] Clogged close test passed: outDrainClose_inShutDrainCheck
[ OK ] Clogged close test passed: outShutClose_inShutDrainCheck
[ OK ] Clogged close test passed: outShutDrainClose_inShutDrainCheck

In comparison, the tool does not show errors for socat-based forwarder:

$ socat tcp-listen:1234,fork,reuseaddr tcp-connect:127.0.0.1:1235
2022/03/16 11:31:53 socat[21951] E write(5, 0x55b7cf2c7000, 8192): Connection reset by peer
2022/03/16 11:31:53 socat[21954] E write(5, 0x55b7cf2c7000, 8192): Connection reset by peer
2022/03/16 11:31:54 socat[21963] E write(5, 0x55b7cf2c7000, 8192): Connection reset by peer
2022/03/16 11:31:54 socat[21973] E write(5, 0x55b7cf2c7000, 8192): Broken pipe
2022/03/16 11:31:54 socat[21976] E write(5, 0x55b7cf2c7000, 8192): Broken pipe
2022/03/16 11:31:55 socat[22000] E write(6, 0x55b7cf2c7000, 8192): Broken pipe
2022/03/16 11:31:56 socat[22002] E write(6, 0x55b7cf2c7000, 8192): Broken pipe
2022/03/16 11:31:56 socat[22006] E write(6, 0x55b7cf2c7000, 8192): Connection reset by peer
2022/03/16 11:31:56 socat[22010] E write(6, 0x55b7cf2c7000, 8192): Broken pipe
2022/03/16 11:31:57 socat[22012] E write(6, 0x55b7cf2c7000, 8192): Broken pipe
$ tcptunnelchecker 127.0.0.1:1235 127.0.0.1:1234
[ OK ] Trivial test 1
[ OK ] Trivial test 2
[ OK ] Clogged close test passed: outDrainCheck_inClose
[ OK ] Clogged close test passed: outDrainCheck_inDrainClose
[ OK ] Clogged close test passed: outClogCheck_inClose
[ OK ] Clogged close test passed: outClogDrainCheck_inClose
[ OK ] Clogged close test passed: outShutDrainCheck_inClose
[ OK ] Clogged close test passed: outShutDrainCheck_inDrainClose
[ OK ] Clogged close test passed: outClogCheck_inClogClose
[ OK ] Clogged close test passed: outDrainCheck_inShutClose
[ OK ] Clogged close test passed: outDrainCheck_inShutDrainClose
[ OK ] Clogged close test passed: outClogCheck_inShutClose
[ OK ] Clogged close test passed: outClogDrainCheck_inShutClose
[ OK ] Clogged close test passed: outShutDrainCheck_inShutClose
[ OK ] Clogged close test passed: outShutDrainCheck_inShutDrainClose
[ OK ] Clogged close test passed: outClose_inDrainCheck
[ OK ] Clogged close test passed: outDrainClose_inDrainCheck
[ OK ] Clogged close test passed: outShutClose_inDrainCheck
[ OK ] Clogged close test passed: outShutDrainClose_inDrainCheck
[ OK ] Clogged close test passed: outClose_inClogCheck
[ OK ] Clogged close test passed: outClose_inClogDrainCheck
[ OK ] Clogged close test passed: outClogClose_inClogCheck
[ OK ] Clogged close test passed: outShutClose_inClogCheck
[ OK ] Clogged close test passed: outShutClose_inClogDrainCheck
[ OK ] Clogged close test passed: outClose_inShutDrainCheck
[ OK ] Clogged close test passed: outDrainClose_inShutDrainCheck
[ OK ] Clogged close test passed: outShutClose_inShutDrainCheck
[ OK ] Clogged close test passed: outShutDrainClose_inShutDrainCheck

Loopback test tcptunnelchecker 127.0.0.1:1234 127.0.0.1:1234 also shows results similar to socat ones.

Configurable ACCEPT_AND_CONNECT_TOGETHER setting.

Allow configurable ACCEPT_AND_CONNECT_TOGETHER setting. When enabled, this starts connecting to destination addresses even before TLS accept has occurred for accepted connections, which improves connection latency. The downside is that for servers that expect spurious or untrusted connections, this could result in many unnecessary connections to destination addresses.

Connections not closing, runs out of file descriptors

I occasionally run into an issue where after running for a while, connections that haven't been cleanly closed from one end or the other accumulate, and eventually stops accepting more connections. I see a bunch of these in the log:

Aug 22 19:36:25 tobaru[538]: [2023-08-23T02:36:22Z ERROR tobaru::tcp] Accept failed: Os { code: 24, kind: Uncategorized, message: "No file descriptors available" }
Aug 22 19:36:25 tobaru[538]: [2023-08-23T02:36:22Z ERROR tobaru::tcp] Accept failed: Os { code: 24, kind: Uncategorized, message: "No file descriptors available" }
Aug 22 19:36:25 tobaru[538]: [2023-08-23T02:36:22Z ERROR tobaru::tcp] Accept failed: Os { code: 24, kind: Uncategorized, message: "No file descriptors available" }

I think something is causing connections to not properly be cleaned up. For example, right now, the server running Tobaru is showing 250+ TCP connections to/from Tobaru, while the host that Tobaru is redirecting them to is showing only 20 connections on that port. Stopping the service for a few seconds and starting it again fixes the problem.

If the underlying problem can't be fixed, then there are several potential workarounds, including:

  • Timeout for old connections
  • Limit for connections, where new connections will close old connections once full
  • Option to have the service exit if this condition is hit so that it can be restarted automatically via systemd or others

compilation fails with rustc 1.70.0

rustc --version
rustc 1.70.0 (90c541806 2023-05-31)

cargo build:

   Compiling tobaru v0.7.1 (/root/tobaru)
error[E0432]: unresolved import `std::lazy`
 --> src/rustls_util.rs:1:10
  |
1 | use std::lazy::SyncOnceCell;
  |          ^^^^ could not find `lazy` in `std`

error[E0432]: unresolved import `std::lazy`
 --> src/tcp.rs:4:10
  |
4 | use std::lazy::SyncOnceCell;
  |          ^^^^ could not find `lazy` in `std`

error[E0554]: `#![feature]` may not be used on the stable release channel
 --> src/main.rs:1:12
  |
1 | #![feature(build_hasher_simple_hash_one)]
  |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0554]: `#![feature]` may not be used on the stable release channel
 --> src/main.rs:2:12
  |
2 | #![feature(once_cell)]
  |            ^^^^^^^^^

warning: the feature `once_cell` has been stable since 1.70.0 and no longer requires an attribute to enable
 --> src/main.rs:2:12
  |
2 | #![feature(once_cell)]
  |            ^^^^^^^^^
  |
  = note: `#[warn(stable_features)]` on by default

warning: unused import: `BuildHasher`
 --> src/tcp.rs:3:17
  |
3 | use std::hash::{BuildHasher, Hash};
  |                 ^^^^^^^^^^^
  |
  = note: `#[warn(unused_imports)]` on by default

Some errors have detailed explanations: E0432, E0554.
For more information about an error, try `rustc --explain E0432`.
warning: `tobaru` (bin "tobaru") generated 2 warnings
error: could not compile `tobaru` (bin "tobaru") due to 4 previous errors; 2 warnings emitted

Fix rustls support

Currently tobaru defaults to using openssl and native-tls for TLS, but rustls support is already baked in and can be enabled using the tls-rustls feature flag. Unfortunately switching to rustls seems to result in very high CPU usages when forwarding.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.