Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Socks proxy support #24

Open
winterwings opened this issue Jun 9, 2020 · 9 comments
Open

Socks proxy support #24

winterwings opened this issue Jun 9, 2020 · 9 comments

Comments

@winterwings
Copy link

In my project, I need to hide server IP from users (they are able to register their own TS servers), so I ended up with this modification.
Would be nice to have it from a box :)

Same as Raw https://github.com/schroffl/teamspeak-query/blob/master/lib/raw.js but with socks proxy support (I bet it could be done better). Note an additional proxy argument on init.

const socks = require("socks").SocksClient;

class RawTSQ extends tsquery.Base {
    constructor() {
        super();
    }
    async init(options, proxy=null) {
        // proxy = { host, port, type: 4|5, [userId], [password] }

        let connectOptions = Object.assign({
            host: "127.0.0.1",
            port: 10011, // default serverquery port
        }, options);
        connectOptions.port *= 1;
        this.host = connectOptions.host;
        this.port = connectOptions.port;

        if (proxy) {
            if (this.host === proxy.host)
                connectOptions.host = "127.0.0.1";
            this.proxy = proxy.host + ":" + proxy.port;
            let proxyOptions = {
                destination: {
                    host: connectOptions.host,
                    port: connectOptions.port
                },
                proxy,
                command: "connect",
                timeout: 15000,
                //set_tcp_nodelay: true,
            };

            let sock = await socks.createConnection(proxyOptions);
            super.streamSetup(sock.socket);
            this.sock = sock.socket;

            // ready event does not fire with awaited socks, so we'll fire it manually
            process.nextTick(() => {
                if (sock.socket._events.ready)
                    sock.socket._events.ready();
            });

        } else { // w/o proxy
            let sock = new net.Socket();
            sock.on("connect", () => super.streamSetup(sock));
            sock.connect(connectOptions);
            this.sock = sock;
        }
    }
    disconnect() {
        return super.disconnect().then(() => this.sock.end());
    }
}

Usage:

const tsq = new RawTSQ();
const options = { usual options };
const proxy = { host: "1.2.3.4", port: 1234, type: 5, userId: "username", password: "pwd" };
// userId and password are optional
await tsq.init(options, proxy);

Docs for socks client: https://github.com/JoshGlazebrook/socks
Socks proxy server used for testing: https://github.com/z3APA3A/3proxy

@schroffl
Copy link
Owner

schroffl commented Jun 9, 2020

Not so sure about this, I'm really wary about adding things to a library that can easily be done outside. First of all, you avoid unnecessary dependencies that only a few people might actually use. And secondly, you don't have to handle all possible, weird edge cases (in your example, when the destination is the same as the proxy). Because if you do forget one case, the API consumer doesn't have a way of quickly fixing that without having to fork the package or submit a pull request, etc.

However, I'm thinking about an implementation that allows you to just write some glue code and have the whole thing run over a custom stream. Then you could just do something like this:

// The first argument is a function which returns a stream via Promise
// The second argument is a function that gets called on disconnect
const query = new TeamspeakQuery.Custom(async query => {
  query.socks = await socks.createConnection({
     destination: { host: '127.0.0.1', port: 10011 },
     proxy: 'my.proxy.org:873'
  }

  query.socket = sock.socket;
  return sock.socket;
}, query => {
  query.socket.end();
});

I guess this is just a glorified way to extend the Base class, but it makes a lot simpler, in my opinion. What do you think?

@winterwings
Copy link
Author

It'll be very nice if it is possible to achieve this without dependencies.

As I noted earlier, I don't know much about networking. I tend to focus on stuff directly affecting other parts of my projects, while TS is the only case I need networking for :) Therefore I took a socks library (and yours too), thinking it handles all the pitfalls I don't know about.
The case of an IP match is needed, but it can easily be done outside of the class. I left it here just as a demonstration of a case where something may go wrong (I remember there is an issue with a self-connection made on external IP when a machine is under NAT).

Yes, your suggestion is fine and I'd play with it as soon as it comes to life, but it does not avoid the need of socks library yet ;)
I'll keep an eye on your progress. Thanks!

@schroffl
Copy link
Owner

Yes, your suggestion is fine and I'd play with it as soon as it comes to life, but it does not avoid the need of socks library yet ;)

I meant that it avoids the need to add a dependency to this library.

@winterwings
Copy link
Author

So you're saying that Custom was available already? Have I missed it in docs? Because I don't see it in exported definitions of your library.
In any case, I meant that I'll be waiting for both :)

@schroffl
Copy link
Owner

No, no, I wasn't saying that it's already available. It was just a proposal. I'm just gonna go ahead and create something that you can try out :)

schroffl added a commit that referenced this issue Jun 11, 2020
@schroffl
Copy link
Owner

schroffl commented Jun 11, 2020

Alright, so I implemented a first iteration as well as some helper methods, which might be useful for custom connections (4d2c3f4, ffef9a5).

I will create a pre-release in the next few minutes and then you could use SOCKS like this:

const socks = require("socks").SocksClient;
const TeamspeakQuery = require('teamspeak-query');

const query = new TeamspeakQuery.Custom(async (instance, callback) => {
  instance.socks = await socks.createConnection({
     destination: { host: '127.0.0.1', port: 10011 },
     proxy: 'my.proxy.org:873'
  }

  // ready event does not fire with awaited socks, so we'll fire it manually
  process.nextTick(() => {
    if (sock.socket._events.ready)
      sock.socket._events.ready();
  });

  const socket = instance.socks.socket;
  callback(socket, socket);
}, instance => {
  // Do any cleanup you might have to do for SOCKS
});

Off-Topic: If you wanna have some fun you can play teamspeak-server like this:

const query = new TeamspeakQuery.Custom((inst, cb) => {
  cb(process.stdin, process.stdout);
});

query.send('login', 'username', 'password')
  .then(response => response.foo === 'bar' ? Promise.resolve() : Promise.reject('Oh no!'))
  .catch(console.error);

Everything you type into stdin will get processed as if it's coming from an actual server.

user@device ~/D/p/teamspeak-query (master)> node example.js


login username password
foo=bar
error id=0
Oh yes!
^C⏎
user@device ~/D/p/teamspeak-query (master)> node example.js


login username password
foo=baz
error id=0
Oh no!
^C⏎
user@device ~/D/p/teamspeak-query (master)> 

I guess that this can be used for testing quite well.

@schroffl
Copy link
Owner

schroffl commented Jun 11, 2020

Alright, I created a pre-release. Install it via npm install [email protected] and check it out :)

@schroffl
Copy link
Owner

Hey, I don't mean to bother you, but did it work out fine?

@winterwings
Copy link
Author

Sorry, I'm not always available - working on a number of projects doesn't help... From what you wrote so far, I'm excited to try this out and will definitely do as soon as I find the time in a few days, then write you back. Thanks! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants