-
Notifications
You must be signed in to change notification settings - Fork 263
New Feature: DNS over SOCKS #347
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
base: master
Are you sure you want to change the base?
Changes from 1 commit
5ff82ed
5a41225
bee0c76
2a1192c
80dfcb6
156c797
3dae148
8efe515
f9c1feb
9d0df8b
44f4415
4cfaf31
07a6977
c365e36
260c4d5
02c3168
588df3d
46a1b24
66a1e1d
ba1ea1c
baf40f2
ba11e90
8b71b09
c0ffa87
f36df78
240d206
c807b02
d0f3460
1f88de9
02ef998
3970b97
00bdea2
688fe6d
e94d15c
b621b32
a12d004
a8be13b
fe2a79e
73c0db0
99455be
fb3f015
938e726
b255bc3
10141bc
7caf075
fd301ba
3b62593
b97610d
4008b84
d416209
7b37e19
d8dde40
be7245c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,9 @@ private void processPendingRegistrations() { | |
| if (!state.channel.isConnected()) { | ||
| state.channel.register(selector, SelectionKey.OP_CONNECT, state); | ||
| } else { | ||
| if (state.channel.keyFor(selector) == null) { | ||
| state.channel.register(selector, SelectionKey.OP_CONNECT, state); | ||
| } | ||
| state.channel.keyFor(selector).interestOps(SelectionKey.OP_WRITE); | ||
| } | ||
| } catch (IOException e) { | ||
|
|
@@ -285,10 +288,21 @@ private static class ChannelKey { | |
| final InetSocketAddress remote; | ||
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<byte[]> sendAndReceiveTcp( | ||
| InetSocketAddress local, | ||
| InetSocketAddress remote, | ||
| Message query, | ||
| byte[] data, | ||
| Duration timeout) { | ||
| return this.sendAndReceiveTcp(local, remote, null, query, data, timeout); | ||
| } | ||
|
|
||
| @Override | ||
| public CompletableFuture<byte[]> sendAndReceiveTcp( | ||
| InetSocketAddress local, | ||
| InetSocketAddress remote, | ||
| Socks5Proxy proxy, | ||
| Message query, | ||
| byte[] data, | ||
| Duration timeout) { | ||
|
|
@@ -309,7 +323,14 @@ public CompletableFuture<byte[]> sendAndReceiveTcp( | |
| c.bind(local); | ||
| } | ||
|
|
||
| c.connect(remote); | ||
| if (proxy != null) { | ||
| c.configureBlocking(true); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is too easy :-)
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, I probably need to implement it as KeyProcessor. I would like to use the Transaction class from NioTcpClient and make it a separate public class. |
||
| c.connect(proxy.getProxyAddress()); | ||
| proxy.socks5TcpHandshake(c, remote); | ||
| } else { | ||
| c.connect(remote); | ||
| } | ||
| c.configureBlocking(false); | ||
| return new ChannelState(c); | ||
| } catch (IOException e) { | ||
| if (c != null) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,192 @@ | ||
| package org.xbill.DNS; | ||
|
|
||
| import lombok.Getter; | ||
|
|
||
| import java.net.InetSocketAddress; | ||
| import java.net.UnknownHostException; | ||
| import java.nio.ByteBuffer; | ||
| import java.nio.channels.SocketChannel; | ||
|
|
||
| @Getter | ||
| public class Socks5Proxy { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if there's a need for timeout handling?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can set timeouts on the Dante config. It should reply with "TTL expired" then. Don't know, how to do this properly. There might be the need to track and handle this. Or you add a retry mechanism. |
||
| private static final byte SOCKS5_VERSION = 0x05; | ||
|
|
||
| private static final byte SOCKS5_AUTH_NONE = 0x00; | ||
| private static final byte SOCKS5_AUTH_GSSAPI = 0x01; | ||
| private static final byte SOCKS5_AUTH_USER_PASS = 0x02; | ||
| private static final byte SOCKS5_AUTH_NO_ACCEPTABLE_METHODS = (byte) 0xFF; | ||
|
|
||
| private static final byte SOCKS5_CMD_CONNECT = 0x01; | ||
| private static final byte SOCKS5_CMD_BIND = 0x02; | ||
| private static final byte SOCKS5_CMD_UDP_ASSOCIATE = 0x03; | ||
|
|
||
| private static final byte SOCKS5_ATYP_IPV4 = 0x01; | ||
| private static final byte SOCKS5_ATYP_DOMAINNAME = 0x03; | ||
| private static final byte SOCKS5_ATYP_IPV6 = 0x04; | ||
|
|
||
| private static final byte SOCKS5_REP_SUCCEEDED = 0x00; | ||
| private static final byte SOCKS5_REP_GENERAL_FAILURE = 0x01; | ||
| private static final byte SOCKS5_REP_CONNECTION_NOT_ALLOWED = 0x02; | ||
| private static final byte SOCKS5_REP_NETWORK_UNREACHABLE = 0x03; | ||
| private static final byte SOCKS5_REP_HOST_UNREACHABLE = 0x04; | ||
| private static final byte SOCKS5_REP_CONNECTION_REFUSED = 0x05; | ||
| private static final byte SOCKS5_REP_TTL_EXPIRED = 0x06; | ||
| private static final byte SOCKS5_REP_COMMAND_NOT_SUPPORTED = 0x07; | ||
| private static final byte SOCKS5_REP_ADDRESS_TYPE_NOT_SUPPORTED = 0x08; | ||
|
|
||
| private static final byte SOCKS5_RESERVED = 0x00; | ||
|
|
||
| private final InetSocketAddress remoteAddress; | ||
| private final InetSocketAddress localAddress; | ||
| private final InetSocketAddress proxyAddress; | ||
|
|
||
|
|
||
| public Socks5Proxy(InetSocketAddress proxyAddress, InetSocketAddress remoteAddress, InetSocketAddress localAddress) { | ||
| this.remoteAddress = remoteAddress; | ||
| this.localAddress = localAddress; | ||
| this.proxyAddress = proxyAddress; | ||
| } | ||
|
|
||
| public void socks5MethodSelection(SocketChannel c) { | ||
| ByteBuffer buffer = ByteBuffer.allocate(3); | ||
| buffer.put(SOCKS5_VERSION); | ||
| buffer.put((byte) 1); | ||
| buffer.put(SOCKS5_AUTH_NONE); | ||
| buffer.flip(); | ||
|
|
||
| try { | ||
| c.write(buffer); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return the return value, then handle it everywhere the write might have failed but not thrown. |
||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Failed to write to TCP channel", e); | ||
| } | ||
|
|
||
| buffer.clear(); | ||
|
|
||
| try { | ||
| c.read(buffer); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Return the return value and handle it everywhere |
||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Failed to read from TCP channel", e); | ||
| } | ||
|
|
||
| buffer.flip(); | ||
| if (buffer.get() != SOCKS5_VERSION) { | ||
| throw new IllegalArgumentException("Invalid version"); | ||
| } | ||
|
|
||
| if (buffer.get() == SOCKS5_AUTH_NO_ACCEPTABLE_METHODS) { | ||
| throw new IllegalArgumentException("No acceptable methods"); | ||
| } | ||
| } | ||
|
|
||
| public void socks5HeaderExchange(SocketChannel c, InetSocketAddress remote) { | ||
| ByteBuffer buffer = ByteBuffer.allocate(10); | ||
| buffer.put(SOCKS5_VERSION); | ||
| buffer.put(SOCKS5_CMD_CONNECT); | ||
| buffer.put(SOCKS5_RESERVED); | ||
| buffer.put(SOCKS5_ATYP_IPV4); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle IPv6; and remote could also be a hostname. What if the proxy should resolve the hostname because the client isn't even able to?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I adjusted it to |
||
| buffer.put(remote.getAddress().getAddress()); | ||
| buffer.putShort((short) remote.getPort()); | ||
| buffer.flip(); | ||
|
|
||
| try { | ||
| c.write(buffer); | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Failed to write to TCP channel", e); | ||
| } | ||
| buffer.clear(); | ||
|
|
||
| try { | ||
| c.read(buffer); | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Failed to read from TCP channel", e); | ||
| } | ||
| buffer.flip(); | ||
|
|
||
| if (buffer.get() != SOCKS5_VERSION) { | ||
| throw new IllegalArgumentException("Invalid version"); | ||
| } | ||
|
|
||
| byte reply = buffer.get(); | ||
| if (reply != SOCKS5_REP_SUCCEEDED) { | ||
| throw new IllegalArgumentException("Failed to connect to remote server: " + reply); | ||
| } | ||
| } | ||
|
|
||
| public InetSocketAddress socks5UdpAssociateExchange(SocketChannel c) { | ||
| ByteBuffer buffer = ByteBuffer.allocate(10); | ||
| buffer.put(SOCKS5_VERSION); | ||
| buffer.put(SOCKS5_CMD_UDP_ASSOCIATE); | ||
| buffer.put(SOCKS5_RESERVED); | ||
| buffer.put(SOCKS5_ATYP_IPV4); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This must be dynamic and properly handle IPv6, including all the places where only IPv4 is assumed later on too. |
||
| buffer.put(new byte[] {0, 0, 0, 0}); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
| buffer.putShort((short) 0x00); | ||
| buffer.flip(); | ||
|
|
||
| try { | ||
| c.write(buffer); | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Failed to write to TCP channel", e); | ||
| } | ||
| buffer.clear(); | ||
|
|
||
| try { | ||
| c.read(buffer); | ||
| } catch (Exception e) { | ||
| throw new IllegalArgumentException("Failed to read from TCP channel", e); | ||
| } | ||
| buffer.flip(); | ||
|
|
||
| if (buffer.get() != SOCKS5_VERSION) { | ||
| throw new IllegalArgumentException("Invalid version"); | ||
| } | ||
|
|
||
| byte reply = buffer.get(); | ||
| if (reply != SOCKS5_REP_SUCCEEDED) { | ||
| throw new IllegalArgumentException("Failed to connect to remote server: " + reply); | ||
| } | ||
|
|
||
| buffer.get(); // skip RSV byte | ||
|
|
||
| byte atyp = buffer.get(); | ||
| if (atyp != SOCKS5_ATYP_IPV4) { | ||
| throw new IllegalArgumentException("Invalid address type"); | ||
| } | ||
|
|
||
| byte[] addr = new byte[4]; | ||
| buffer.get(addr); | ||
| int port = buffer.getShort() & 0xFFFF; | ||
| return new InetSocketAddress(this.getProxyAddress().getAddress(), port); | ||
| } | ||
|
|
||
| public byte[] addUdpHeader(byte[] in, InetSocketAddress to) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle IPv6 in this method
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With this IPv6 worked for me |
||
| ByteBuffer buffer = ByteBuffer.allocate(in.length + 10); | ||
| buffer.put(SOCKS5_VERSION); | ||
| buffer.put(SOCKS5_RESERVED); | ||
| buffer.put(SOCKS5_RESERVED); | ||
| buffer.put(SOCKS5_ATYP_IPV4); | ||
| buffer.put(to.getAddress().getAddress()); | ||
| buffer.putShort((short) to.getPort()); | ||
| buffer.put(in); | ||
|
|
||
| return buffer.array(); | ||
| } | ||
|
|
||
| public byte[] removeUdpHeader(byte[] in) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Handle IPv6
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It keeps track of the headerLength in case of IPv4, IPv6 or domainname and removes it accordingly |
||
| byte[] out = new byte[in.length - 10]; | ||
| System.arraycopy(in, 10, out, 0, in.length - 10); | ||
| return out; | ||
| } | ||
|
|
||
| public void socks5TcpHandshake( | ||
| SocketChannel c, InetSocketAddress remote) { | ||
| this.socks5MethodSelection(c); | ||
| this.socks5HeaderExchange(c, remote); | ||
| } | ||
|
|
||
| public InetSocketAddress socks5UdpAssociateHandshake( | ||
| SocketChannel c | ||
| ) throws UnknownHostException { | ||
| this.socks5MethodSelection(c); | ||
| return this.socks5UdpAssociateExchange(c); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What did you encounter that this is necessary? Also, would it be possible to merge the condition with the outer if?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I encountered:
The merge with the outer condition didn't work out for me.