diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessage.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessage.java new file mode 100755 index 0000000000..13d5604c66 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessage.java @@ -0,0 +1,66 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx; + +import io.netty.buffer.ByteBuf; + +/** + * An abstract class that defines a SocksMessage, providing common properties for + * {@link SocksV5Request} and {@link SocksV5Response}. + * + * @see SocksV5Request + * @see SocksV5Response + */ + +public abstract class SocksMessage { + private final SocksMessageType type; + private final SocksProtocolVersion protocolVersion; + + protected SocksMessage(SocksProtocolVersion protocolVersion, SocksMessageType type) { + if (protocolVersion == null) { + throw new NullPointerException("protocolVersion"); + } + if (type == null) { + throw new NullPointerException("type"); + } + this.protocolVersion = protocolVersion; + this.type = type; + } + + /** + * Returns the {@link SocksMessageType} of this {@link SocksMessage} + * + * @return The {@link SocksMessageType} of this {@link SocksMessage} + */ + public SocksMessageType type() { + return type; + } + + /** + * Returns the {@link SocksProtocolVersion} of this {@link SocksMessage} + * + * @return The {@link SocksProtocolVersion} of this {@link SocksMessage} + */ + public SocksProtocolVersion protocolVersion() { + return protocolVersion; + } + + /** + * @deprecated Do not use; this method was intended for an internal use only. + */ + @Deprecated + public abstract void encodeAsByteBuf(ByteBuf byteBuf); +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageEncoder.java new file mode 100755 index 0000000000..2708bf3a08 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageEncoder.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + +/** + * Encodes an {@link SocksMessage} into a {@link ByteBuf}. + * {@link MessageToByteEncoder} implementation. + * Use this with {@link SocksV4CmdRequest}, + * {@link SocksV4CmdRequest}, + * {@link SocksV5InitRequest}, + * {@link SocksV5InitResponse}, + * {@link SocksV5AuthRequest}, + * {@link SocksV5AuthResponse}, + * {@link SocksV5CmdRequest} and + * {@link SocksV5CmdResponse} + */ +@ChannelHandler.Sharable +public class SocksMessageEncoder extends MessageToByteEncoder { + private static final String name = "SOCKS_MESSAGE_ENCODER"; + + @Override + @SuppressWarnings("deprecation") + protected void encode(ChannelHandlerContext ctx, SocksMessage msg, ByteBuf out) throws Exception { + msg.encodeAsByteBuf(out); + } + + private static class SocksMessageEncoderHolder { + public static final SocksMessageEncoder HOLDER_INSTANCE = new SocksMessageEncoder(); + } + + public static SocksMessageEncoder getInstance() { + return SocksMessageEncoderHolder.HOLDER_INSTANCE; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageType.java new file mode 100755 index 0000000000..ef349f7955 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageType.java @@ -0,0 +1,23 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx; + +public enum SocksMessageType { + REQUEST, + RESPONSE, + UNKNOWN +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksProtocolVersion.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksProtocolVersion.java new file mode 100755 index 0000000000..02b7419548 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksProtocolVersion.java @@ -0,0 +1,50 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx; + +public enum SocksProtocolVersion { + SOCKS4a((byte) 0x04), + SOCKS5((byte) 0x05), + UNKNOWN((byte) 0xff); + + private final byte b; + + SocksProtocolVersion(byte b) { + this.b = b; + } + + /** + * @deprecated Use {@link #valueOf(byte)} instead. + */ + @Deprecated + public static SocksProtocolVersion fromByte(byte b) { + return valueOf(b); + } + + public static SocksProtocolVersion valueOf(byte b) { + for (SocksProtocolVersion code : values()) { + if (code.b == b) { + return code; + } + } + return UNKNOWN; + } + + public byte byteValue() { + return b; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksRequest.java new file mode 100644 index 0000000000..a67290fe82 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksRequest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx; + + + +/** + * An abstract class that defines a SocksRequest, providing common properties for + * {@link SocksV5InitRequest}, + * {@link SocksV5AuthRequest}, + * {@link SocksV5CmdRequest} + * and {@link UnknownSocksV5Request}. + * + * @see io.netty.handler.codec.socks.SocksInitRequest + * @see io.netty.handler.codec.socks.SocksAuthRequest + * @see io.netty.handler.codec.socks.SocksCmdRequest + * @see io.netty.handler.codec.socks.UnknownSocksRequest + */ +public abstract class SocksRequest extends SocksMessage { + + protected SocksRequest(SocksProtocolVersion protocolVersion) { + super(protocolVersion, SocksMessageType.REQUEST); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksResponse.java new file mode 100644 index 0000000000..4299364b51 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksResponse.java @@ -0,0 +1,22 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx; + +public abstract class SocksResponse extends SocksMessage { + protected SocksResponse(SocksProtocolVersion protocolVersion) { + super(protocolVersion, SocksMessageType.RESPONSE); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/package-info.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/package-info.java new file mode 100755 index 0000000000..99151c23fe --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Encoder, decoder and their related message types for Socks. + */ +package io.netty.handler.codec.socksx; diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequest.java new file mode 100755 index 0000000000..edf2a34764 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequest.java @@ -0,0 +1,116 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; +import io.netty.util.internal.SystemPropertyUtil; + +import java.net.IDN; + +/** + * An socksv4a cmd request. + * + * @see SocksV4Response + * @see SocksV4CmdRequestDecoder + */ + +public final class SocksV4CmdRequest extends SocksV4Request { + private final String userId; + private final SocksV4CmdType cmdType; + private final String host; + private final int port; + + private static final byte[] IPv4_DOMAIN_MARKER = {0x00, 0x00, 0x00, 0x01}; + + public SocksV4CmdRequest(String userId, SocksV4CmdType cmdType, String host, int port) { + if (userId == null) { + throw new NullPointerException("username"); + } + if (cmdType == null) { + throw new NullPointerException("cmdType"); + } + if (host == null) { + throw new NullPointerException("host"); + } + if (port <= 0 || port >= 65536) { + throw new IllegalArgumentException(port + " is not in bounds 0 < x < 65536"); + } + this.userId = userId; + this.cmdType = cmdType; + this.host = IDN.toASCII(host); + this.port = port; + } + + public SocksV4CmdRequest(SocksV4CmdType cmdType, String host, int port) { + this("", cmdType, host, port); + } + + /** + * Returns the {@link SocksV4CmdType} of this {@link SocksV4Request} + * + * @return The {@link SocksV4CmdType} of this {@link SocksV4Request} + */ + public SocksV4CmdType cmdType() { + return cmdType; + } + + /** + * Returns host that is used as a parameter in {@link SocksV4CmdType} + * + * @return host that is used as a parameter in {@link SocksV4CmdType} + */ + public String host() { + return IDN.toUnicode(host); + } + + /** + * Returns userId that is used as a parameter in {@link SocksV4CmdType} + * + * @return userId that is used as a parameter in {@link SocksV4CmdType} + */ + public String userId() { + return userId; + } + + /** + * Returns port that is used as a parameter in {@link SocksV4CmdType} + * + * @return port that is used as a parameter in {@link SocksV4CmdType} + */ + public int port() { + return port; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(protocolVersion().byteValue()); + byteBuf.writeByte(cmdType.byteValue()); + byteBuf.writeShort(port); + if (NetUtil.isValidIpV4Address(host)) { + byteBuf.writeBytes(NetUtil.createByteArrayFromIpAddressString(host)); + byteBuf.writeBytes(userId.getBytes()); + byteBuf.writeZero(1); + } else { + byteBuf.writeBytes(IPv4_DOMAIN_MARKER); + byteBuf.writeBytes(userId.getBytes()); + byteBuf.writeZero(1); + byteBuf.writeBytes(host.getBytes(CharsetUtil.US_ASCII)); + byteBuf.writeZero(1); + } + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestDecoder.java new file mode 100755 index 0000000000..747388ea0f --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestDecoder.java @@ -0,0 +1,94 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v4.SocksV4CmdRequestDecoder.State; +import io.netty.util.CharsetUtil; +import io.netty.util.internal.SystemPropertyUtil; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV4CmdRequest}. + * Before returning SocksRequest decoder removes itself from pipeline. + */ +public class SocksV4CmdRequestDecoder extends ReplayingDecoder { + private static final String name = "SOCKS_CMD_REQUEST_DECODER"; + + private SocksProtocolVersion version; + private SocksV4CmdType cmdType; + @SuppressWarnings("UnusedDeclaration") + private byte reserved; + private String host; + private int port; + private String userId; + private SocksV4Request msg = UnknownSocksV4Request.getInstance(); + + public SocksV4CmdRequestDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksProtocolVersion.valueOf(byteBuf.readByte()); + if (version != SocksProtocolVersion.SOCKS4a) { + break; + } + checkpoint(State.READ_CMD_HEADER); + } + case READ_CMD_HEADER: { + cmdType = SocksV4CmdType.valueOf(byteBuf.readByte()); + port = byteBuf.readUnsignedShort(); + host = SocksV4CommonUtils.intToIp(byteBuf.readInt()); + checkpoint(State.READ_CMD_USERID); + } + case READ_CMD_USERID: { + userId = readNullTerminatedString(byteBuf); + checkpoint(State.READ_CMD_DOMAIN); + } + case READ_CMD_DOMAIN: { + // Check for Socks4a protocol marker 0,0,0,x + if (!host.equals("0.0.0.0") && host.startsWith("0.0.0.")) { + host = readNullTerminatedString(byteBuf); + } + msg = new SocksV4CmdRequest(userId, cmdType, host, port); + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + private static String readNullTerminatedString(ByteBuf byteBuf) throws Exception { + byte NULL_BYTE = (byte) 0x00; + // Could be used for DoS + String string = byteBuf.readBytes(byteBuf.bytesBefore(NULL_BYTE)).toString(CharsetUtil.US_ASCII); + // Read NULL-byte + byteBuf.readByte(); + return string; + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_CMD_HEADER, + READ_CMD_USERID, + READ_CMD_DOMAIN + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponse.java new file mode 100755 index 0000000000..dc591dbdb3 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponse.java @@ -0,0 +1,114 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.util.NetUtil; + +import java.net.IDN; + +/** + * A socks cmd response. + * + * @see SocksV4CmdRequest + * @see SocksV4CmdResponseDecoder + */ +public final class SocksV4CmdResponse extends SocksV4Response { + private final SocksV4CmdStatus cmdStatus; + private final String host; + private final int port; + + // All arrays are initialized on construction time to 0/false/null remove array Initialization + private static final byte[] IPv4_HOSTNAME_ZEROED = { 0x00, 0x00, 0x00, 0x00 }; + + public SocksV4CmdResponse(SocksV4CmdStatus cmdStatus) { + this(cmdStatus, null, 0); + } + + /** + * Constructs new response and includes provided host and port as part of it. + * + * @param cmdStatus status of the response + * @param host host (BND.ADDR field) is address that server used when connecting to the target host. + * When null a value of 4/8 0x00 octets will be used for IPv4/IPv6 and a single 0x00 byte will be + * used for domain addressType. Value is converted to ASCII using {@link java.net.IDN#toASCII(String)}. + * @param port port (BND.PORT field) that the server assigned to connect to the target host + * @throws NullPointerException in case cmdStatus or addressType are missing + * @throws IllegalArgumentException in case host or port cannot be validated + * @see java.net.IDN#toASCII(String) + */ + public SocksV4CmdResponse(SocksV4CmdStatus cmdStatus, String host, int port) { + if (cmdStatus == null) { + throw new NullPointerException("cmdStatus"); + } + if (host != null) { + if (!NetUtil.isValidIpV4Address(host)) { + throw new IllegalArgumentException(host + " is not a valid IPv4 address"); + } + } + if (port < 0 || port > 65535) { + throw new IllegalArgumentException(port + " is not in bounds 0 <= x <= 65535"); + } + this.cmdStatus = cmdStatus; + this.host = host; + this.port = port; + } + + /** + * Returns the {@link SocksV4CmdStatus} of this {@link SocksV4Response} + * + * @return The {@link SocksV4CmdStatus} of this {@link SocksV4Response} + */ + public SocksV4CmdStatus cmdStatus() { + return cmdStatus; + } + + /** + * Returns host that is used as a parameter in {@link io.netty.handler.codec.socks.v4.SocksV4CmdType}. + * Host (BND.ADDR field in response) is address that server used when connecting to the target host. + * This is typically different from address which client uses to connect to the SOCKS server. + * + * @return host that is used as a parameter in {@link io.netty.handler.codec.socks.v4.SocksV4CmdType} + * or null when there was no host specified during response construction + */ + public String host() { + if (host != null) { + return IDN.toUnicode(host); + } else { + return null; + } + } + + /** + * Returns port that is used as a parameter in {@link io.netty.handler.codec.socks.v4.SocksV4CmdType}. + * Port (BND.PORT field in response) is port that the server assigned to connect to the target host. + * + * @return port that is used as a parameter in {@link io.netty.handler.codec.socks.v4.SocksV4CmdType} + */ + public int port() { + return port; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeZero(1); + byteBuf.writeByte(cmdStatus.byteValue()); + byteBuf.writeShort(port); + byte[] hostContent = host == null ? + IPv4_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host); + byteBuf.writeBytes(hostContent); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseDecoder.java new file mode 100755 index 0000000000..48055a0e07 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseDecoder.java @@ -0,0 +1,73 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socks.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v4.SocksV4CmdResponseDecoder.State; +import io.netty.util.CharsetUtil; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV4CmdResponse}. + * Before returning SocksResponse decoder removes itself from pipeline. + */ +public class SocksV4CmdResponseDecoder extends ReplayingDecoder { + private static final String name = "SOCKS_CMD_RESPONSE_DECODER"; + + private SocksProtocolVersion version; + private SocksV4CmdStatus cmdStatus; + + private String host; + private int port; + private SocksV4Response msg = UnknownSocksV4Response.getInstance(); + + public SocksV4CmdResponseDecoder() { + super(State.CHECK_NULL_BYTE); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_NULL_BYTE: { + if (byteBuf.readByte() != (byte) 0x00) { + break; + } + checkpoint(State.READ_CMD_HEADER); + } + case READ_CMD_HEADER: { + cmdStatus = SocksV4CmdStatus.valueOf(byteBuf.readByte()); + checkpoint(State.READ_CMD_ADDRESS); + } + case READ_CMD_ADDRESS: { + port = byteBuf.readUnsignedShort(); + host = SocksV4CommonUtils.intToIp(byteBuf.readInt()); + msg = new SocksV4CmdResponse(cmdStatus, host, port); + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_NULL_BYTE, + READ_CMD_HEADER, + READ_CMD_ADDRESS + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdStatus.java new file mode 100755 index 0000000000..765d2a254e --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdStatus.java @@ -0,0 +1,43 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +public enum SocksV4CmdStatus { + SUCCESS((byte) 0x5a), + REJECTED_OR_FAILED((byte) 0x5b), + IDENTD_UNREACHABLE((byte) 0x5c), + IDENTD_AUTH_FAILURE((byte) 0x5d), + UNASSIGNED((byte) 0xff); + + private final byte b; + + SocksV4CmdStatus(byte b) { + this.b = b; + } + + public static SocksV4CmdStatus valueOf(byte b) { + for (SocksV4CmdStatus code : values()) { + if (code.b == b) { + return code; + } + } + return UNASSIGNED; + } + + public byte byteValue() { + return b; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdType.java new file mode 100755 index 0000000000..f7266fdb20 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CmdType.java @@ -0,0 +1,41 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +public enum SocksV4CmdType { + CONNECT((byte) 0x01), + BIND((byte) 0x02), + UNKNOWN((byte) 0xff); + + private final byte b; + + SocksV4CmdType(byte b) { + this.b = b; + } + + public static SocksV4CmdType valueOf(byte b) { + for (SocksV4CmdType code : values()) { + if (code.b == b) { + return code; + } + } + return UNKNOWN; + } + + public byte byteValue() { + return b; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CommonUtils.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CommonUtils.java new file mode 100755 index 0000000000..fdc5f45e2c --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4CommonUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; +import io.netty.util.internal.StringUtil; + +public final class SocksV4CommonUtils { + private static final int SECOND_ADDRESS_OCTET_SHIFT = 16; + private static final int FIRST_ADDRESS_OCTET_SHIFT = 24; + private static final int THIRD_ADDRESS_OCTET_SHIFT = 8; + private static final int XOR_DEFAULT_VALUE = 0xff; + + /** + * A constructor to stop this class being constructed. + */ + private SocksV4CommonUtils() { + // NOOP + } + + public static String intToIp(int i) { + return String.valueOf(i >> FIRST_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + + (i >> SECOND_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + + (i >> THIRD_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + + (i & XOR_DEFAULT_VALUE); + } + + private static final char[] ipv6conseqZeroFiller = {':', ':'}; + private static final char ipv6hextetSeparator = ':'; + + /** + * Convert numeric IPv6 to compressed format, where + * the longest sequence of 0's (with 2 or more 0's) is replaced with "::" + */ + public static String ipv6toCompressedForm(byte[] src) { + assert src.length == 16; + //Find the longest sequence of 0's + //start of compressed region (hextet index) + int cmprHextet = -1; + //length of compressed region + int cmprSize = 0; + for (int hextet = 0; hextet < 8;) { + int curByte = hextet * 2; + int size = 0; + while (curByte < src.length && src[curByte] == 0 + && src[curByte + 1] == 0) { + curByte += 2; + size++; + } + if (size > cmprSize) { + cmprHextet = hextet; + cmprSize = size; + } + hextet = curByte / 2 + 1; + } + if (cmprHextet == -1 || cmprSize < 2) { + //No compression can be applied + return ipv6toStr(src); + } + StringBuilder sb = new StringBuilder(39); + ipv6toStr(sb, src, 0, cmprHextet); + sb.append(ipv6conseqZeroFiller); + ipv6toStr(sb, src, cmprHextet + cmprSize, 8); + return sb.toString(); + } + + /** + * Converts numeric IPv6 to standard (non-compressed) format. + */ + public static String ipv6toStr(byte[] src) { + assert src.length == 16; + StringBuilder sb = new StringBuilder(39); + ipv6toStr(sb, src, 0, 8); + return sb.toString(); + } + + private static void ipv6toStr(StringBuilder sb, byte[] src, int fromHextet, int toHextet) { + int i; + toHextet --; + for (i = fromHextet; i < toHextet; i++) { + appendHextet(sb, src, i); + sb.append(ipv6hextetSeparator); + } + + appendHextet(sb, src, i); + } + + private static void appendHextet(StringBuilder sb, byte[] src, int i) { + StringUtil.toHexString(sb, src, i << 1, 2); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4Request.java new file mode 100755 index 0000000000..cee0cc25bb --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4Request.java @@ -0,0 +1,34 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.SocksMessageType; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.SocksRequest; + +/** + * An abstract class that defines a SocksRequest, providing common properties for + * {@link SocksV4CmdRequest}. + * + * @see SocksV4CmdRequest + * @see UnknownSocksV4Request + */ +public abstract class SocksV4Request extends SocksRequest { + protected SocksV4Request() { + super(SocksProtocolVersion.SOCKS4a); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4Response.java new file mode 100755 index 0000000000..cf4ad582c9 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/SocksV4Response.java @@ -0,0 +1,36 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.SocksMessageType; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.SocksResponse; +import io.netty.handler.codec.socksx.v5.SocksV5ResponseType; + +/** + * An abstract class that defines a SocksResponse, providing common properties for + * {@link SocksV4CmdResponse}. + * + * @see SocksV4CmdResponse + * @see UnknownSocksV4Response + */ +public abstract class SocksV4Response extends SocksResponse { + + protected SocksV4Response() { + super(SocksProtocolVersion.SOCKS4a); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocksV4Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocksV4Request.java new file mode 100755 index 0000000000..f19ce391e8 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocksV4Request.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; + +/** + * An unknown socks request. + * + * @see SocksV4CmdRequestDecoder + */ +public final class UnknownSocksV4Request extends SocksV4Request { + + public UnknownSocksV4Request() { + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + // NOOP + } + + private static class UnknownSocksV4RequestHolder { + public static final UnknownSocksV4Request HOLDER_INSTANCE = new UnknownSocksV4Request(); + } + + public static UnknownSocksV4Request getInstance() { + return UnknownSocksV4RequestHolder.HOLDER_INSTANCE; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocksV4Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocksV4Response.java new file mode 100755 index 0000000000..a629a3b0b2 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocksV4Response.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; + +/** + * An unknown socks response. + * + * @see SocksV4CmdResponseDecoder + */ +public final class UnknownSocksV4Response extends SocksV4Response { + + public UnknownSocksV4Response() { + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + // NOOP + } + + private static class UnknownSocksV4ResponseHolder { + public static final UnknownSocksV4Response HOLDER_INSTANCE = new UnknownSocksV4Response(); + } + + public static UnknownSocksV4Response getInstance() { + return UnknownSocksV4ResponseHolder.HOLDER_INSTANCE; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/package-info.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/package-info.java new file mode 100755 index 0000000000..2e1dc4b888 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright 2014 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Encoder, decoder and their related message types for Socks. + */ +package io.netty.handler.codec.socksx.v4; diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AddressType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AddressType.java new file mode 100755 index 0000000000..3bb952c9b9 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AddressType.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +public enum SocksV5AddressType { + IPv4((byte) 0x01), + DOMAIN((byte) 0x03), + IPv6((byte) 0x04), + UNKNOWN((byte) 0xff); + + private final byte b; + + SocksV5AddressType(byte b) { + this.b = b; + } + + public static SocksV5AddressType valueOf(byte b) { + for (SocksV5AddressType code : values()) { + if (code.b == b) { + return code; + } + } + return UNKNOWN; + } + + public byte byteValue() { + return b; + } +} + diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequest.java new file mode 100755 index 0000000000..8bb3b07ac8 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; + +import java.nio.charset.CharsetEncoder; + +/** + * An socks auth request. + * + * @see SocksV5AuthResponse + * @see SocksV5AuthRequestDecoder + */ +public final class SocksV5AuthRequest extends SocksV5Request { + private static final CharsetEncoder asciiEncoder = CharsetUtil.getEncoder(CharsetUtil.US_ASCII); + private static final SocksV5SubnegotiationVersion SUBNEGOTIATION_VERSION = + SocksV5SubnegotiationVersion.AUTH_PASSWORD; + private final String username; + private final String password; + + public SocksV5AuthRequest(String username, String password) { + super(SocksV5RequestType.AUTH); + if (username == null) { + throw new NullPointerException("username"); + } + if (password == null) { + throw new NullPointerException("username"); + } + if (!asciiEncoder.canEncode(username) || !asciiEncoder.canEncode(password)) { + throw new IllegalArgumentException(" username: " + username + " or password: " + password + + " values should be in pure ascii"); + } + if (username.length() > 255) { + throw new IllegalArgumentException(username + " exceeds 255 char limit"); + } + if (password.length() > 255) { + throw new IllegalArgumentException(password + " exceeds 255 char limit"); + } + this.username = username; + this.password = password; + } + + /** + * Returns username that needs to be authenticated + * + * @return username that needs to be authenticated + */ + public String username() { + return username; + } + + /** + * Returns password that needs to be validated + * + * @return password that needs to be validated + */ + public String password() { + return password; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(SUBNEGOTIATION_VERSION.byteValue()); + byteBuf.writeByte(username.length()); + byteBuf.writeBytes(username.getBytes(CharsetUtil.US_ASCII)); + byteBuf.writeByte(password.length()); + byteBuf.writeBytes(password.getBytes(CharsetUtil.US_ASCII)); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestDecoder.java new file mode 100755 index 0000000000..9f8034b39a --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestDecoder.java @@ -0,0 +1,71 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.v5.SocksV5AuthRequestDecoder.State; +import io.netty.util.CharsetUtil; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV5AuthRequest}. + * Before returning SocksRequest decoder removes itself from pipeline. + */ +public class SocksV5AuthRequestDecoder extends ReplayingDecoder { + private SocksV5SubnegotiationVersion version; + private int fieldLength; + private String username; + private String password; + private SocksV5Request msg = UnknownSocksV5Request.getInstance(); + + public SocksV5AuthRequestDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksV5SubnegotiationVersion.valueOf(byteBuf.readByte()); + if (version != SocksV5SubnegotiationVersion.AUTH_PASSWORD) { + break; + } + checkpoint(State.READ_USERNAME); + } + case READ_USERNAME: { + fieldLength = byteBuf.readByte(); + username = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII); + checkpoint(State.READ_PASSWORD); + } + case READ_PASSWORD: { + fieldLength = byteBuf.readByte(); + password = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII); + msg = new SocksV5AuthRequest(username, password); + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_USERNAME, + READ_PASSWORD + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponse.java new file mode 100755 index 0000000000..4ceffdb0fc --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponse.java @@ -0,0 +1,53 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; + +/** + * An socks auth response. + * + * @see SocksV5AuthRequest + * @see SocksV5AuthResponseDecoder + */ +public final class SocksV5AuthResponse extends SocksV5Response { + private static final SocksV5SubnegotiationVersion SUBNEGOTIATION_VERSION = + SocksV5SubnegotiationVersion.AUTH_PASSWORD; + private final SocksV5AuthStatus authStatus; + + public SocksV5AuthResponse(SocksV5AuthStatus authStatus) { + super(SocksV5ResponseType.AUTH); + if (authStatus == null) { + throw new NullPointerException("authStatus"); + } + this.authStatus = authStatus; + } + + /** + * Returns the {@link SocksV5AuthStatus} of this {@link SocksV5AuthResponse} + * + * @return The {@link SocksV5AuthStatus} of this {@link SocksV5AuthResponse} + */ + public SocksV5AuthStatus authStatus() { + return authStatus; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(SUBNEGOTIATION_VERSION.byteValue()); + byteBuf.writeByte(authStatus.byteValue()); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseDecoder.java new file mode 100755 index 0000000000..c5e02d18a8 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseDecoder.java @@ -0,0 +1,62 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.v5.SocksV5AuthResponseDecoder.State; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV5AuthResponse}. + * Before returning SocksResponse decoder removes itself from pipeline. + */ +public class SocksV5AuthResponseDecoder extends ReplayingDecoder { + private SocksV5SubnegotiationVersion version; + private SocksV5AuthStatus authStatus; + private SocksV5Response msg = UnknownSocksV5Response.getInstance(); + + public SocksV5AuthResponseDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List out) + throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksV5SubnegotiationVersion.valueOf(byteBuf.readByte()); + if (version != SocksV5SubnegotiationVersion.AUTH_PASSWORD) { + break; + } + checkpoint(State.READ_AUTH_RESPONSE); + } + case READ_AUTH_RESPONSE: { + authStatus = SocksV5AuthStatus.valueOf(byteBuf.readByte()); + msg = new SocksV5AuthResponse(authStatus); + } + } + channelHandlerContext.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_AUTH_RESPONSE + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthScheme.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthScheme.java new file mode 100755 index 0000000000..5613fdab7a --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthScheme.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +public enum SocksV5AuthScheme { + NO_AUTH((byte) 0x00), + AUTH_GSSAPI((byte) 0x01), + AUTH_PASSWORD((byte) 0x02), + UNKNOWN((byte) 0xff); + + private final byte b; + + SocksV5AuthScheme(byte b) { + this.b = b; + } + + public static SocksV5AuthScheme valueOf(byte b) { + for (SocksV5AuthScheme code : values()) { + if (code.b == b) { + return code; + } + } + return UNKNOWN; + } + + public byte byteValue() { + return b; + } +} + diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthStatus.java new file mode 100755 index 0000000000..4742f7b190 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5AuthStatus.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +public enum SocksV5AuthStatus { + SUCCESS((byte) 0x00), + FAILURE((byte) 0xff); + + private final byte b; + + SocksV5AuthStatus(byte b) { + this.b = b; + } + + public static SocksV5AuthStatus valueOf(byte b) { + for (SocksV5AuthStatus code : values()) { + if (code.b == b) { + return code; + } + } + return FAILURE; + } + + public byte byteValue() { + return b; + } +} + diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequest.java new file mode 100755 index 0000000000..b2a84b96e6 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequest.java @@ -0,0 +1,138 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; + +import java.net.IDN; + +/** + * An socks cmd request. + * + * @see SocksV5CmdResponse + * @see SocksV5CmdRequestDecoder + */ +public final class SocksV5CmdRequest extends SocksV5Request { + private final SocksV5CmdType cmdType; + private final SocksV5AddressType addressType; + private final String host; + private final int port; + + public SocksV5CmdRequest(SocksV5CmdType cmdType, SocksV5AddressType addressType, String host, int port) { + super(SocksV5RequestType.CMD); + if (cmdType == null) { + throw new NullPointerException("cmdType"); + } + if (addressType == null) { + throw new NullPointerException("addressType"); + } + if (host == null) { + throw new NullPointerException("host"); + } + switch (addressType) { + case IPv4: + if (!NetUtil.isValidIpV4Address(host)) { + throw new IllegalArgumentException(host + " is not a valid IPv4 address"); + } + break; + case DOMAIN: + if (IDN.toASCII(host).length() > 255) { + throw new IllegalArgumentException(host + " IDN: " + IDN.toASCII(host) + " exceeds 255 char limit"); + } + break; + case IPv6: + if (!NetUtil.isValidIpV6Address(host)) { + throw new IllegalArgumentException(host + " is not a valid IPv6 address"); + } + break; + case UNKNOWN: + break; + } + if (port <= 0 || port >= 65536) { + throw new IllegalArgumentException(port + " is not in bounds 0 < x < 65536"); + } + this.cmdType = cmdType; + this.addressType = addressType; + this.host = IDN.toASCII(host); + this.port = port; + } + + /** + * Returns the {@link SocksV5CmdType} of this {@link SocksV5CmdRequest} + * + * @return The {@link SocksV5CmdType} of this {@link SocksV5CmdRequest} + */ + public SocksV5CmdType cmdType() { + return cmdType; + } + + /** + * Returns the {@link SocksV5AddressType} of this {@link SocksV5CmdRequest} + * + * @return The {@link SocksV5AddressType} of this {@link SocksV5CmdRequest} + */ + public SocksV5AddressType addressType() { + return addressType; + } + + /** + * Returns host that is used as a parameter in {@link SocksV5CmdType} + * + * @return host that is used as a parameter in {@link SocksV5CmdType} + */ + public String host() { + return IDN.toUnicode(host); + } + + /** + * Returns port that is used as a parameter in {@link SocksV5CmdType} + * + * @return port that is used as a parameter in {@link SocksV5CmdType} + */ + public int port() { + return port; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(protocolVersion().byteValue()); + byteBuf.writeByte(cmdType.byteValue()); + byteBuf.writeByte(0x00); + byteBuf.writeByte(addressType.byteValue()); + switch (addressType) { + case IPv4: { + byteBuf.writeBytes(NetUtil.createByteArrayFromIpAddressString(host)); + byteBuf.writeShort(port); + break; + } + + case DOMAIN: { + byteBuf.writeByte(host.length()); + byteBuf.writeBytes(host.getBytes(CharsetUtil.US_ASCII)); + byteBuf.writeShort(port); + break; + } + + case IPv6: { + byteBuf.writeBytes(NetUtil.createByteArrayFromIpAddressString(host)); + byteBuf.writeShort(port); + break; + } + } + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestDecoder.java new file mode 100755 index 0000000000..d2a4245900 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestDecoder.java @@ -0,0 +1,97 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v5.SocksV5CmdRequestDecoder.State; +import io.netty.util.CharsetUtil; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV5CmdRequest}. + * Before returning SocksRequest decoder removes itself from pipeline. + */ +public class SocksV5CmdRequestDecoder extends ReplayingDecoder { + private SocksProtocolVersion version; + private int fieldLength; + private SocksV5CmdType cmdType; + private SocksV5AddressType addressType; + @SuppressWarnings("UnusedDeclaration") + private byte reserved; + private String host; + private int port; + private SocksV5Request msg = UnknownSocksV5Request.getInstance(); + + public SocksV5CmdRequestDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksProtocolVersion.valueOf(byteBuf.readByte()); + if (version != SocksProtocolVersion.SOCKS5) { + break; + } + checkpoint(State.READ_CMD_HEADER); + } + case READ_CMD_HEADER: { + cmdType = SocksV5CmdType.valueOf(byteBuf.readByte()); + reserved = byteBuf.readByte(); + addressType = SocksV5AddressType.valueOf(byteBuf.readByte()); + checkpoint(State.READ_CMD_ADDRESS); + } + case READ_CMD_ADDRESS: { + switch (addressType) { + case IPv4: { + host = SocksV5CommonUtils.intToIp(byteBuf.readInt()); + port = byteBuf.readUnsignedShort(); + msg = new SocksV5CmdRequest(cmdType, addressType, host, port); + break; + } + case DOMAIN: { + fieldLength = byteBuf.readByte(); + host = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII); + port = byteBuf.readUnsignedShort(); + msg = new SocksV5CmdRequest(cmdType, addressType, host, port); + break; + } + case IPv6: { + host = SocksV5CommonUtils.ipv6toStr(byteBuf.readBytes(16).array()); + port = byteBuf.readUnsignedShort(); + msg = new SocksV5CmdRequest(cmdType, addressType, host, port); + break; + } + case UNKNOWN: + break; + } + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_CMD_HEADER, + READ_CMD_ADDRESS + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponse.java new file mode 100755 index 0000000000..c57da6bacf --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponse.java @@ -0,0 +1,177 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; + +import java.net.IDN; + +/** + * A socks cmd response. + * + * @see SocksV5CmdRequest + * @see SocksV5CmdResponseDecoder + */ +public final class SocksV5CmdResponse extends SocksV5Response { + private final SocksV5CmdStatus cmdStatus; + + private final SocksV5AddressType addressType; + private final String host; + private final int port; + + // All arrays are initialized on construction time to 0/false/null remove array Initialization + private static final byte[] DOMAIN_ZEROED = {0x00}; + private static final byte[] IPv4_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00}; + private static final byte[] IPv6_HOSTNAME_ZEROED = {0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00}; + + public SocksV5CmdResponse(SocksV5CmdStatus cmdStatus, SocksV5AddressType addressType) { + this(cmdStatus, addressType, null, 0); + } + + /** + * Constructs new response and includes provided host and port as part of it. + * + * @param cmdStatus status of the response + * @param addressType type of host parameter + * @param host host (BND.ADDR field) is address that server used when connecting to the target host. + * When null a value of 4/8 0x00 octets will be used for IPv4/IPv6 and a single 0x00 byte will be + * used for domain addressType. Value is converted to ASCII using {@link IDN#toASCII(String)}. + * @param port port (BND.PORT field) that the server assigned to connect to the target host + * @throws NullPointerException in case cmdStatus or addressType are missing + * @throws IllegalArgumentException in case host or port cannot be validated + * @see IDN#toASCII(String) + */ + public SocksV5CmdResponse(SocksV5CmdStatus cmdStatus, SocksV5AddressType addressType, String host, int port) { + super(SocksV5ResponseType.CMD); + if (cmdStatus == null) { + throw new NullPointerException("cmdStatus"); + } + if (addressType == null) { + throw new NullPointerException("addressType"); + } + if (host != null) { + switch (addressType) { + case IPv4: + if (!NetUtil.isValidIpV4Address(host)) { + throw new IllegalArgumentException(host + " is not a valid IPv4 address"); + } + break; + case DOMAIN: + if (IDN.toASCII(host).length() > 255) { + throw new IllegalArgumentException(host + " IDN: " + + IDN.toASCII(host) + " exceeds 255 char limit"); + } + break; + case IPv6: + if (!NetUtil.isValidIpV6Address(host)) { + throw new IllegalArgumentException(host + " is not a valid IPv6 address"); + } + break; + case UNKNOWN: + break; + } + host = IDN.toASCII(host); + } + if (port < 0 || port > 65535) { + throw new IllegalArgumentException(port + " is not in bounds 0 <= x <= 65535"); + } + this.cmdStatus = cmdStatus; + this.addressType = addressType; + this.host = host; + this.port = port; + } + + /** + * Returns the {@link SocksV5CmdStatus} of this {@link SocksV5CmdResponse} + * + * @return The {@link SocksV5CmdStatus} of this {@link SocksV5CmdResponse} + */ + public SocksV5CmdStatus cmdStatus() { + return cmdStatus; + } + + /** + * Returns the {@link SocksV5AddressType} of this {@link SocksV5CmdResponse} + * + * @return The {@link SocksV5AddressType} of this {@link SocksV5CmdResponse} + */ + public SocksV5AddressType addressType() { + return addressType; + } + + /** + * Returns host that is used as a parameter in {@link SocksV5CmdType}. + * Host (BND.ADDR field in response) is address that server used when connecting to the target host. + * This is typically different from address which client uses to connect to the SOCKS server. + * + * @return host that is used as a parameter in {@link SocksV5CmdType} + * or null when there was no host specified during response construction + */ + public String host() { + if (host != null) { + return IDN.toUnicode(host); + } else { + return null; + } + } + + /** + * Returns port that is used as a parameter in {@link SocksV5CmdType}. + * Port (BND.PORT field in response) is port that the server assigned to connect to the target host. + * + * @return port that is used as a parameter in {@link SocksV5CmdType} + */ + public int port() { + return port; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(protocolVersion().byteValue()); + byteBuf.writeByte(cmdStatus.byteValue()); + byteBuf.writeByte(0x00); + byteBuf.writeByte(addressType.byteValue()); + switch (addressType) { + case IPv4: { + byte[] hostContent = host == null ? + IPv4_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host); + byteBuf.writeBytes(hostContent); + byteBuf.writeShort(port); + break; + } + case DOMAIN: { + byte[] hostContent = host == null ? + DOMAIN_ZEROED : host.getBytes(CharsetUtil.US_ASCII); + byteBuf.writeByte(hostContent.length); // domain length + byteBuf.writeBytes(hostContent); // domain value + byteBuf.writeShort(port); // port value + break; + } + case IPv6: { + byte[] hostContent = host == null + ? IPv6_HOSTNAME_ZEROED : NetUtil.createByteArrayFromIpAddressString(host); + byteBuf.writeBytes(hostContent); + byteBuf.writeShort(port); + break; + } + } + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseDecoder.java new file mode 100755 index 0000000000..91c3ef64a7 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseDecoder.java @@ -0,0 +1,96 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v5.SocksV5CmdResponseDecoder.State; +import io.netty.util.CharsetUtil; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV5CmdResponse}. + * Before returning SocksResponse decoder removes itself from pipeline. + */ +public class SocksV5CmdResponseDecoder extends ReplayingDecoder { + private SocksProtocolVersion version; + private int fieldLength; + private SocksV5CmdStatus cmdStatus; + private SocksV5AddressType addressType; + private byte reserved; + private String host; + private int port; + private SocksV5Response msg = UnknownSocksV5Response.getInstance(); + + public SocksV5CmdResponseDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksProtocolVersion.valueOf(byteBuf.readByte()); + if (version != SocksProtocolVersion.SOCKS5) { + break; + } + checkpoint(State.READ_CMD_HEADER); + } + case READ_CMD_HEADER: { + cmdStatus = SocksV5CmdStatus.valueOf(byteBuf.readByte()); + reserved = byteBuf.readByte(); + addressType = SocksV5AddressType.valueOf(byteBuf.readByte()); + checkpoint(State.READ_CMD_ADDRESS); + } + case READ_CMD_ADDRESS: { + switch (addressType) { + case IPv4: { + host = SocksV5CommonUtils.intToIp(byteBuf.readInt()); + port = byteBuf.readUnsignedShort(); + msg = new SocksV5CmdResponse(cmdStatus, addressType, host, port); + break; + } + case DOMAIN: { + fieldLength = byteBuf.readByte(); + host = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII); + port = byteBuf.readUnsignedShort(); + msg = new SocksV5CmdResponse(cmdStatus, addressType, host, port); + break; + } + case IPv6: { + host = SocksV5CommonUtils.ipv6toStr(byteBuf.readBytes(16).array()); + port = byteBuf.readUnsignedShort(); + msg = new SocksV5CmdResponse(cmdStatus, addressType, host, port); + break; + } + case UNKNOWN: + break; + } + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_CMD_HEADER, + READ_CMD_ADDRESS + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdStatus.java new file mode 100755 index 0000000000..756986f9ca --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdStatus.java @@ -0,0 +1,49 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +public enum SocksV5CmdStatus { + SUCCESS((byte) 0x00), + FAILURE((byte) 0x01), + FORBIDDEN((byte) 0x02), + NETWORK_UNREACHABLE((byte) 0x03), + HOST_UNREACHABLE((byte) 0x04), + REFUSED((byte) 0x05), + TTL_EXPIRED((byte) 0x06), + COMMAND_NOT_SUPPORTED((byte) 0x07), + ADDRESS_NOT_SUPPORTED((byte) 0x08), + UNASSIGNED((byte) 0xff); + + private final byte b; + + SocksV5CmdStatus(byte b) { + this.b = b; + } + + public static SocksV5CmdStatus valueOf(byte b) { + for (SocksV5CmdStatus code : values()) { + if (code.b == b) { + return code; + } + } + return UNASSIGNED; + } + + public byte byteValue() { + return b; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdType.java new file mode 100755 index 0000000000..f1b1ec6c75 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CmdType.java @@ -0,0 +1,44 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +public enum SocksV5CmdType { + CONNECT((byte) 0x01), + BIND((byte) 0x02), + UDP((byte) 0x03), + UNKNOWN((byte) 0xff); + + private final byte b; + + SocksV5CmdType(byte b) { + this.b = b; + } + + public static SocksV5CmdType valueOf(byte b) { + for (SocksV5CmdType code : values()) { + if (code.b == b) { + return code; + } + } + return UNKNOWN; + } + + public byte byteValue() { + return b; + } +} + diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CommonUtils.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CommonUtils.java new file mode 100755 index 0000000000..25ebd013dc --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5CommonUtils.java @@ -0,0 +1,102 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; +import io.netty.util.internal.StringUtil; + +public final class SocksV5CommonUtils { + private static final int SECOND_ADDRESS_OCTET_SHIFT = 16; + private static final int FIRST_ADDRESS_OCTET_SHIFT = 24; + private static final int THIRD_ADDRESS_OCTET_SHIFT = 8; + private static final int XOR_DEFAULT_VALUE = 0xff; + + /** + * A constructor to stop this class being constructed. + */ + private SocksV5CommonUtils() { + // NOOP + } + + public static String intToIp(int i) { + return String.valueOf(i >> FIRST_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + + (i >> SECOND_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + + (i >> THIRD_ADDRESS_OCTET_SHIFT & XOR_DEFAULT_VALUE) + '.' + + (i & XOR_DEFAULT_VALUE); + } + + private static final char[] ipv6conseqZeroFiller = {':', ':'}; + private static final char ipv6hextetSeparator = ':'; + + /** + * Convert numeric IPv6 to compressed format, where + * the longest sequence of 0's (with 2 or more 0's) is replaced with "::" + */ + public static String ipv6toCompressedForm(byte[] src) { + assert src.length == 16; + //Find the longest sequence of 0's + //start of compressed region (hextet index) + int cmprHextet = -1; + //length of compressed region + int cmprSize = 0; + for (int hextet = 0; hextet < 8;) { + int curByte = hextet * 2; + int size = 0; + while (curByte < src.length && src[curByte] == 0 + && src[curByte + 1] == 0) { + curByte += 2; + size++; + } + if (size > cmprSize) { + cmprHextet = hextet; + cmprSize = size; + } + hextet = curByte / 2 + 1; + } + if (cmprHextet == -1 || cmprSize < 2) { + //No compression can be applied + return ipv6toStr(src); + } + StringBuilder sb = new StringBuilder(39); + ipv6toStr(sb, src, 0, cmprHextet); + sb.append(ipv6conseqZeroFiller); + ipv6toStr(sb, src, cmprHextet + cmprSize, 8); + return sb.toString(); + } + + /** + * Converts numeric IPv6 to standard (non-compressed) format. + */ + public static String ipv6toStr(byte[] src) { + assert src.length == 16; + StringBuilder sb = new StringBuilder(39); + ipv6toStr(sb, src, 0, 8); + return sb.toString(); + } + + private static void ipv6toStr(StringBuilder sb, byte[] src, int fromHextet, int toHextet) { + int i; + toHextet --; + for (i = fromHextet; i < toHextet; i++) { + appendHextet(sb, src, i); + sb.append(ipv6hextetSeparator); + } + + appendHextet(sb, src, i); + } + + private static void appendHextet(StringBuilder sb, byte[] src, int i) { + StringUtil.toHexString(sb, src, i << 1, 2); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequest.java new file mode 100755 index 0000000000..fa73204ab7 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequest.java @@ -0,0 +1,57 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; + +import java.util.Collections; +import java.util.List; + +/** + * An socks init request. + * + * @see SocksV5InitResponse + * @see SocksV5InitRequestDecoder + */ +public final class SocksV5InitRequest extends SocksV5Request { + private final List authSchemes; + + public SocksV5InitRequest(List authSchemes) { + super(SocksV5RequestType.INIT); + if (authSchemes == null) { + throw new NullPointerException("authSchemes"); + } + this.authSchemes = authSchemes; + } + + /** + * Returns the List<{@link SocksV5AuthScheme}> of this {@link SocksV5InitRequest} + * + * @return The List<{@link SocksV5AuthScheme}> of this {@link SocksV5InitRequest} + */ + public List authSchemes() { + return Collections.unmodifiableList(authSchemes); + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(protocolVersion().byteValue()); + byteBuf.writeByte(authSchemes.size()); + for (SocksV5AuthScheme authScheme : authSchemes) { + byteBuf.writeByte(authScheme.byteValue()); + } + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequestDecoder.java new file mode 100755 index 0000000000..511848cc1c --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequestDecoder.java @@ -0,0 +1,69 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v5.SocksV5InitRequestDecoder.State; + +import java.util.ArrayList; +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV5InitRequest}. + * Before returning SocksRequest decoder removes itself from pipeline. + */ +public class SocksV5InitRequestDecoder extends ReplayingDecoder { + private final List authSchemes = new ArrayList(); + private SocksProtocolVersion version; + private byte authSchemeNum; + private SocksV5Request msg = UnknownSocksV5Request.getInstance(); + + public SocksV5InitRequestDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksProtocolVersion.valueOf(byteBuf.readByte()); + if (version != SocksProtocolVersion.SOCKS5) { + break; + } + checkpoint(State.READ_AUTH_SCHEMES); + } + case READ_AUTH_SCHEMES: { + authSchemes.clear(); + authSchemeNum = byteBuf.readByte(); + for (int i = 0; i < authSchemeNum; i++) { + authSchemes.add(SocksV5AuthScheme.valueOf(byteBuf.readByte())); + } + msg = new SocksV5InitRequest(authSchemes); + break; + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_AUTH_SCHEMES + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponse.java new file mode 100755 index 0000000000..7ceec9ba0f --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponse.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; + +/** + * An socks init response. + * + * @see SocksV5InitRequest + * @see SocksV5InitResponseDecoder + */ +public final class SocksV5InitResponse extends SocksV5Response { + private final SocksV5AuthScheme authScheme; + + public SocksV5InitResponse(SocksV5AuthScheme authScheme) { + super(SocksV5ResponseType.INIT); + if (authScheme == null) { + throw new NullPointerException("authScheme"); + } + this.authScheme = authScheme; + } + + /** + * Returns the {@link SocksV5AuthScheme} of this {@link SocksV5InitResponse} + * + * @return The {@link SocksV5AuthScheme} of this {@link SocksV5InitResponse} + */ + public SocksV5AuthScheme authScheme() { + return authScheme; + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + byteBuf.writeByte(protocolVersion().byteValue()); + byteBuf.writeByte(authScheme.byteValue()); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponseDecoder.java new file mode 100755 index 0000000000..d2b23fc03b --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponseDecoder.java @@ -0,0 +1,64 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v5.SocksV5InitResponseDecoder.State; + +import java.util.List; + +/** + * Decodes {@link ByteBuf}s into {@link SocksV5InitResponse}. + * Before returning SocksResponse decoder removes itself from pipeline. + */ +public class SocksV5InitResponseDecoder extends ReplayingDecoder { + private SocksProtocolVersion version; + private SocksV5AuthScheme authScheme; + + private SocksV5Response msg = UnknownSocksV5Response.getInstance(); + + public SocksV5InitResponseDecoder() { + super(State.CHECK_PROTOCOL_VERSION); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { + switch (state()) { + case CHECK_PROTOCOL_VERSION: { + version = SocksProtocolVersion.valueOf(byteBuf.readByte()); + if (version != SocksProtocolVersion.SOCKS5) { + break; + } + checkpoint(State.READ_PREFFERED_AUTH_TYPE); + } + case READ_PREFFERED_AUTH_TYPE: { + authScheme = SocksV5AuthScheme.valueOf(byteBuf.readByte()); + msg = new SocksV5InitResponse(authScheme); + break; + } + } + ctx.pipeline().remove(this); + out.add(msg); + } + + enum State { + CHECK_PROTOCOL_VERSION, + READ_PREFFERED_AUTH_TYPE + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5Request.java new file mode 100755 index 0000000000..9a8897c5ae --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5Request.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.SocksMessageType; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.SocksRequest; + +/** + * An abstract class that defines a SocksRequest, providing common properties for + * {@link SocksV5InitRequest}, + * {@link SocksV5AuthRequest}, + * {@link SocksV5CmdRequest} and + * {@link UnknownSocksV5Request}. + * + * @see SocksV5InitRequest + * @see SocksV5AuthRequest + * @see SocksV5CmdRequest + * @see UnknownSocksV5Request + */ +public abstract class SocksV5Request extends SocksRequest { + private final SocksV5RequestType requestType; + + protected SocksV5Request(SocksV5RequestType requestType) { + super(SocksProtocolVersion.SOCKS5); + if (requestType == null) { + throw new NullPointerException("requestType"); + } + this.requestType = requestType; + } + + /** + * Returns socks request type + * + * @return socks request type + */ + public SocksV5RequestType requestType() { + return requestType; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5RequestType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5RequestType.java new file mode 100755 index 0000000000..ba1c1541df --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5RequestType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +/** + * Type of socks request + */ +public enum SocksV5RequestType { + INIT, + AUTH, + CMD, + UNKNOWN +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5Response.java new file mode 100755 index 0000000000..37cbb1fe14 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5Response.java @@ -0,0 +1,54 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.SocksMessageType; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.SocksResponse; + +/** + * An abstract class that defines a SocksResponse, providing common properties for + * {@link SocksV5InitResponse}, + * {@link SocksV5AuthResponse}, + * {@link SocksV5CmdResponse} + * and {@link UnknownSocksV5Response}. + * + * @see SocksV5InitResponse + * @see SocksV5AuthResponse + * @see SocksV5CmdResponse + * @see UnknownSocksV5Response + */ +public abstract class SocksV5Response extends SocksResponse { + private final SocksV5ResponseType responseType; + + protected SocksV5Response(SocksV5ResponseType responseType) { + super(SocksProtocolVersion.SOCKS5); + if (responseType == null) { + throw new NullPointerException("responseType"); + } + this.responseType = responseType; + } + + /** + * Returns socks response type + * + * @return socks response type + */ + public SocksV5ResponseType responseType() { + return responseType; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5ResponseType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5ResponseType.java new file mode 100755 index 0000000000..c181c9e31c --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5ResponseType.java @@ -0,0 +1,27 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +/** + * Type of socks response + */ +public enum SocksV5ResponseType { + INIT, + AUTH, + CMD, + UNKNOWN +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5SubnegotiationVersion.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5SubnegotiationVersion.java new file mode 100755 index 0000000000..d3d1609d93 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/SocksV5SubnegotiationVersion.java @@ -0,0 +1,42 @@ +/* + * Copyright 2013 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +package io.netty.handler.codec.socksx.v5; + +public enum SocksV5SubnegotiationVersion { + AUTH_PASSWORD((byte) 0x01), + UNKNOWN((byte) 0xff); + + private final byte b; + + SocksV5SubnegotiationVersion(byte b) { + this.b = b; + } + + public static SocksV5SubnegotiationVersion valueOf(byte b) { + for (SocksV5SubnegotiationVersion code : values()) { + if (code.b == b) { + return code; + } + } + return UNKNOWN; + } + + public byte byteValue() { + return b; + } +} + diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocksV5Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocksV5Request.java new file mode 100755 index 0000000000..28ab600cc0 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocksV5Request.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; + +/** + * An unknown socks request. + * + * @see SocksV5InitRequestDecoder + * @see SocksV5AuthRequestDecoder + * @see SocksV5CmdRequestDecoder + */ +public final class UnknownSocksV5Request extends SocksV5Request { + + public UnknownSocksV5Request() { + super(SocksV5RequestType.UNKNOWN); + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + // NOOP + } + + private static class UnknownSocksV5RequestHolder { + public static final UnknownSocksV5Request HOLDER_INSTANCE = new UnknownSocksV5Request(); + } + + public static UnknownSocksV5Request getInstance() { + return UnknownSocksV5RequestHolder.HOLDER_INSTANCE; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocksV5Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocksV5Response.java new file mode 100755 index 0000000000..4ce2d560ce --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocksV5Response.java @@ -0,0 +1,45 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; + +/** + * An unknown socks response. + * + * @see SocksV5InitResponseDecoder + * @see SocksV5AuthResponseDecoder + * @see SocksV5CmdResponseDecoder + */ +public final class UnknownSocksV5Response extends SocksV5Response { + + public UnknownSocksV5Response() { + super(SocksV5ResponseType.UNKNOWN); + } + + @Override + public void encodeAsByteBuf(ByteBuf byteBuf) { + // NOOP + } + + private static class UnknownSocksV5ResponseHolder { + public static final UnknownSocksV5Response HOLDER_INSTANCE = new UnknownSocksV5Response(); + } + + public static UnknownSocksV5Response getInstance() { + return UnknownSocksV5ResponseHolder.HOLDER_INSTANCE; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/package-info.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/package-info.java new file mode 100755 index 0000000000..592c44cb39 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ + +/** + * Encoder, decoder and their related message types for Socks. + */ +package io.netty.handler.codec.socksx.v5; +// TODO: Combine decoders into one. diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestDecoderTest.java new file mode 100755 index 0000000000..710a34d322 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestDecoderTest.java @@ -0,0 +1,70 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.socksx.v5.SocksV5AddressType; +import io.netty.handler.codec.socksx.v5.SocksV5CmdRequest; +import io.netty.handler.codec.socksx.v5.SocksV5CmdType; +import io.netty.handler.codec.socksx.v5.UnknownSocksV5Request; +import io.netty.util.internal.SystemPropertyUtil; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; + +public class SocksV4CmdRequestDecoderTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocksV4CmdRequestDecoderTest.class); + + private static void testSocksV4CmdRequestDecoderWithDifferentParams(String userId, + SocksV4CmdType cmdType, + String host, + int port) { + logger.debug("Testing cmdType: " + cmdType + " userId: " + userId + " host: " + host + + " port: " + port); + SocksV4CmdRequest msg = new SocksV4CmdRequest(userId, cmdType, host, port); + SocksV4CmdRequestDecoder decoder = new SocksV4CmdRequestDecoder(); + EmbeddedChannel embedder = new EmbeddedChannel(decoder); + SocksV4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + Object obj = embedder.readInbound(); + msg = (SocksV4CmdRequest) obj; + assertSame(msg.cmdType(), cmdType); + assertEquals(msg.userId(), userId); + assertEquals(msg.host(), host); + assertEquals(msg.port(), port); + assertNull(embedder.readInbound()); + } + + @Test + public void testCmdRequestDecoder() { + String[] hosts = {"127.0.0.1", }; + String[] userIds = {"test", }; + int[] ports = {1, 32769, 65535}; + for (SocksV4CmdType cmdType : SocksV4CmdType.values()) { + for (String userId : userIds) { + for (String host : hosts) { + for (int port : ports) { + testSocksV4CmdRequestDecoderWithDifferentParams(userId, cmdType, host, port); + } + } + } + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestTest.java new file mode 100755 index 0000000000..79a55804a8 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdRequestTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SocksV4CmdRequestTest { + private static final Logger logger = LoggerFactory.getLogger(SocksV4CmdRequestTest.class); +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseDecoderTest.java new file mode 100755 index 0000000000..54f40ba936 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseDecoderTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.socksx.v5.SocksV5AddressType; +import io.netty.handler.codec.socksx.v5.SocksV5CmdStatus; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class SocksV4CmdResponseDecoderTest { + private static final Logger logger = LoggerFactory.getLogger(SocksV4CmdResponseDecoderTest.class); + + private static void testSocksCmdResponseDecoderWithDifferentParams( + SocksV4CmdStatus cmdStatus, String host, int port) { + logger.debug("Testing cmdStatus: " + cmdStatus); + SocksV4Response msg = new SocksV4CmdResponse(cmdStatus, host, port); + SocksV4CmdResponseDecoder decoder = new SocksV4CmdResponseDecoder(); + EmbeddedChannel embedder = new EmbeddedChannel(decoder); + SocksV4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + + msg = (SocksV4Response) embedder.readInbound(); + assertEquals(((SocksV4CmdResponse) msg).cmdStatus(), cmdStatus); + if (host != null) { + assertEquals(((SocksV4CmdResponse) msg).host(), host); + } + assertEquals(((SocksV4CmdResponse) msg).port(), port); + assertNull(embedder.readInbound()); + } + + /** + * Verifies that sent socks messages are decoded correctly. + */ + @Test + public void testSocksCmdResponseDecoder() { + for (SocksV4CmdStatus cmdStatus : SocksV4CmdStatus.values()) { + testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, null, 0); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseTest.java new file mode 100755 index 0000000000..be07247619 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CmdResponseTest.java @@ -0,0 +1,23 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SocksV4CmdResponseTest { + private static final Logger logger = LoggerFactory.getLogger(SocksV4CmdResponseTest.class); +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CommonTestUtils.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CommonTestUtils.java new file mode 100755 index 0000000000..bdb8947af0 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/SocksV4CommonTestUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.socksx.SocksMessage; + +final class SocksV4CommonTestUtils { + /** + * A constructor to stop this class being constructed. + */ + private SocksV4CommonTestUtils() { + //NOOP + } + + @SuppressWarnings("deprecation") + public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, SocksMessage msg) { + ByteBuf buf = Unpooled.buffer(); + msg.encodeAsByteBuf(buf); + embedder.writeInbound(buf); + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestDecoderTest.java new file mode 100755 index 0000000000..d0f32769a9 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestDecoderTest.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.channel.embedded.EmbeddedChannel; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SocksV5AuthRequestDecoderTest { + + @Test + public void testAuthRequestDecoder() { + String username = "test"; + String password = "test"; + SocksV5AuthRequest msg = new SocksV5AuthRequest(username, password); + SocksV5AuthRequestDecoder decoder = new SocksV5AuthRequestDecoder(); + EmbeddedChannel embedder = new EmbeddedChannel(decoder); + SocksV5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + msg = (SocksV5AuthRequest) embedder.readInbound(); + assertEquals(msg.username(), username); + assertEquals(msg.username(), password); + assertNull(embedder.readInbound()); + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestTest.java new file mode 100755 index 0000000000..d140805cf7 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthRequestTest.java @@ -0,0 +1,81 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; + +public class SocksV5AuthRequestTest { + @Test + public void testConstructorParamsAreNotNull() { + try { + new SocksV5AuthRequest(null, ""); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + try { + new SocksV5AuthRequest("", null); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + } + + @Test + public void testUsernameOrPasswordIsNotAscii() { + try { + new SocksV5AuthRequest("παράδειγμα.δοκιμή", "password"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + try { + new SocksV5AuthRequest("username", "παράδειγμα.δοκιμή"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testUsernameOrPasswordLengthIsLessThan255Chars() { + try { + new SocksV5AuthRequest( + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword", + "password"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + try { + new SocksV5AuthRequest("password", + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + + "passwordpasswordpasswordpasswordpasswordpasswordpassword"); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseDecoderTest.java new file mode 100755 index 0000000000..3ea8f37075 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseDecoderTest.java @@ -0,0 +1,46 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SocksV5AuthResponseDecoderTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance( + SocksV5AuthResponseDecoderTest.class); + + private static void testSocksAuthResponseDecoderWithDifferentParams(SocksV5AuthStatus authStatus) { + logger.debug("Testing SocksAuthResponseDecoder with authStatus: " + authStatus); + SocksV5AuthResponse msg = new SocksV5AuthResponse(authStatus); + SocksV5AuthResponseDecoder decoder = new SocksV5AuthResponseDecoder(); + EmbeddedChannel embedder = new EmbeddedChannel(decoder); + SocksV5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + msg = (SocksV5AuthResponse) embedder.readInbound(); + assertSame(msg.authStatus(), authStatus); + assertNull(embedder.readInbound()); + } + + @Test + public void testSocksCmdResponseDecoder() { + for (SocksV5AuthStatus authStatus: SocksV5AuthStatus.values()) { + testSocksAuthResponseDecoderWithDifferentParams(authStatus); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseTest.java new file mode 100755 index 0000000000..fb6ee19fac --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5AuthResponseTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; + +public class SocksV5AuthResponseTest { + @Test + public void testConstructorParamsAreNotNull() { + try { + new SocksV5AuthResponse(null); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + } + +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestDecoderTest.java new file mode 100755 index 0000000000..d8494a459a --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestDecoderTest.java @@ -0,0 +1,109 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.Test; +import sun.net.util.IPAddressUtil; + +import static org.junit.Assert.*; + +public class SocksV5CmdRequestDecoderTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocksV5CmdRequestDecoderTest.class); + + private static void testSocksCmdRequestDecoderWithDifferentParams(SocksV5CmdType cmdType, + SocksV5AddressType addressType, + String host, + int port) { + logger.debug("Testing cmdType: " + cmdType + " addressType: " + addressType + " host: " + host + + " port: " + port); + SocksV5CmdRequest msg = new SocksV5CmdRequest(cmdType, addressType, host, port); + SocksV5CmdRequestDecoder decoder = new SocksV5CmdRequestDecoder(); + EmbeddedChannel embedder = new EmbeddedChannel(decoder); + SocksV5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + if (msg.addressType() == SocksV5AddressType.UNKNOWN) { + assertTrue(embedder.readInbound() instanceof UnknownSocksV5Request); + } else { + msg = (SocksV5CmdRequest) embedder.readInbound(); + assertSame(msg.cmdType(), cmdType); + assertSame(msg.addressType(), addressType); + assertEquals(msg.host(), host); + assertEquals(msg.port(), port); + } + assertNull(embedder.readInbound()); + } + + @Test + public void testCmdRequestDecoderIPv4() { + String[] hosts = {"127.0.0.1", }; + int[] ports = {1, 32769, 65535 }; + for (SocksV5CmdType cmdType : SocksV5CmdType.values()) { + for (String host : hosts) { + for (int port : ports) { + testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksV5AddressType.IPv4, host, port); + } + } + } + } + + @Test + public void testCmdRequestDecoderIPv6() { + String[] hosts = {SocksV5CommonUtils.ipv6toStr(IPAddressUtil.textToNumericFormatV6("::1"))}; + int[] ports = {1, 32769, 65535}; + for (SocksV5CmdType cmdType : SocksV5CmdType.values()) { + for (String host : hosts) { + for (int port : ports) { + testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksV5AddressType.IPv6, host, port); + } + } + } + } + + @Test + public void testCmdRequestDecoderDomain() { + String[] hosts = {"google.com" , + "مثال.إختبار", + "παράδειγμα.δοκιμή", + "مثال.آزمایشی", + "пример.испытание", + "בײַשפּיל.טעסט", + "例子.测试", + "例子.測試", + "उदाहरण.परीक्षा", + "例え.テスト", + "실례.테스트", + "உதாரணம்.பரிட்சை"}; + int[] ports = {1, 32769, 65535}; + for (SocksV5CmdType cmdType : SocksV5CmdType.values()) { + for (String host : hosts) { + for (int port : ports) { + testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksV5AddressType.DOMAIN, host, port); + } + } + } + } + + @Test + public void testCmdRequestDecoderUnknown() { + String host = "google.com"; + int port = 80; + for (SocksV5CmdType cmdType : SocksV5CmdType.values()) { + testSocksCmdRequestDecoderWithDifferentParams(cmdType, SocksV5AddressType.UNKNOWN, host, port); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestTest.java new file mode 100755 index 0000000000..ccd9eee32e --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdRequestTest.java @@ -0,0 +1,91 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SocksV5CmdRequestTest { + @Test + public void testConstructorParamsAreNotNull() { + try { + new SocksV5CmdRequest(null, SocksV5AddressType.UNKNOWN, "", 1); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + + try { + new SocksV5CmdRequest(SocksV5CmdType.UNKNOWN, null, "", 1); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + + try { + new SocksV5CmdRequest(SocksV5CmdType.UNKNOWN, SocksV5AddressType.UNKNOWN, null, 1); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + } + + @Test + public void testIPv4CorrectAddress() { + try { + new SocksV5CmdRequest(SocksV5CmdType.BIND, SocksV5AddressType.IPv4, "54.54.1111.253", 1); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testIPv6CorrectAddress() { + try { + new SocksV5CmdRequest(SocksV5CmdType.BIND, SocksV5AddressType.IPv6, "xxx:xxx:xxx", 1); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testIDNNotExceeds255CharsLimit() { + try { + new SocksV5CmdRequest(SocksV5CmdType.BIND, SocksV5AddressType.DOMAIN, + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή", 1); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } + + @Test + public void testValidPortRange() { + try { + new SocksV5CmdRequest(SocksV5CmdType.BIND, SocksV5AddressType.DOMAIN, + "παράδειγμα.δοκιμήπαράδει", 0); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + + try { + new SocksV5CmdRequest(SocksV5CmdType.BIND, SocksV5AddressType.DOMAIN, + "παράδειγμα.δοκιμήπαράδει", 65536); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseDecoderTest.java new file mode 100755 index 0000000000..d991e1cedc --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseDecoderTest.java @@ -0,0 +1,84 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SocksV5CmdResponseDecoderTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(SocksV5CmdResponseDecoderTest.class); + + private static void testSocksCmdResponseDecoderWithDifferentParams( + SocksV5CmdStatus cmdStatus, SocksV5AddressType addressType, String host, int port) { + logger.debug("Testing cmdStatus: " + cmdStatus + " addressType: " + addressType); + SocksV5Response msg = new SocksV5CmdResponse(cmdStatus, addressType, host, port); + SocksV5CmdResponseDecoder decoder = new SocksV5CmdResponseDecoder(); + EmbeddedChannel embedder = new EmbeddedChannel(decoder); + SocksV5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + if (addressType == SocksV5AddressType.UNKNOWN) { + assertTrue(embedder.readInbound() instanceof UnknownSocksV5Response); + } else { + msg = (SocksV5Response) embedder.readInbound(); + assertEquals(((SocksV5CmdResponse) msg).cmdStatus(), cmdStatus); + if (host != null) { + assertEquals(((SocksV5CmdResponse) msg).host(), host); + } + assertEquals(((SocksV5CmdResponse) msg).port(), port); + } + assertNull(embedder.readInbound()); + } + + /** + * Verifies that sent socks messages are decoded correctly. + */ + @Test + public void testSocksCmdResponseDecoder() { + for (SocksV5CmdStatus cmdStatus : SocksV5CmdStatus.values()) { + for (SocksV5AddressType addressType : SocksV5AddressType.values()) { + testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, addressType, null, 0); + } + } + } + + /** + * Verifies that invalid bound host will fail with IllegalArgumentException during encoding. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidAddress() { + testSocksCmdResponseDecoderWithDifferentParams(SocksV5CmdStatus.SUCCESS, SocksV5AddressType.IPv4, "1", 80); + } + + /** + * Verifies that send socks messages are decoded correctly when bound host and port are set. + */ + @Test + public void testSocksCmdResponseDecoderIncludingHost() { + for (SocksV5CmdStatus cmdStatus : SocksV5CmdStatus.values()) { + testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, SocksV5AddressType.IPv4, + "127.0.0.1", 80); + testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, SocksV5AddressType.DOMAIN, + "testDomain.com", 80); + testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, SocksV5AddressType.IPv6, + "2001:db8:85a3:42:1000:8a2e:370:7334", 80); + testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, SocksV5AddressType.IPv6, + "1111:111:11:1:0:0:0:1", 80); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseTest.java new file mode 100755 index 0000000000..40699f2ab3 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CmdResponseTest.java @@ -0,0 +1,141 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class SocksV5CmdResponseTest { + @Test + public void testConstructorParamsAreNotNull() { + try { + new SocksV5CmdResponse(null, SocksV5AddressType.UNKNOWN); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + try { + new SocksV5CmdResponse(SocksV5CmdStatus.UNASSIGNED, null); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + } + + /** + * Verifies content of the response when domain is not specified. + */ + @Test + public void testEmptyDomain() { + SocksV5CmdResponse socksV5CmdResponse = new SocksV5CmdResponse( + SocksV5CmdStatus.SUCCESS, SocksV5AddressType.DOMAIN); + assertNull(socksV5CmdResponse.host()); + assertEquals(0, socksV5CmdResponse.port()); + ByteBuf buffer = Unpooled.buffer(20); + socksV5CmdResponse.encodeAsByteBuf(buffer); + byte[] expected = { + 0x05, // version + 0x00, // success reply + 0x00, // reserved + 0x03, // address type domain + 0x01, // length of domain + 0x00, // domain value + 0x00, // port value + 0x00 + }; + assertByteBufEquals(expected, buffer); + } + + /** + * Verifies content of the response when IPv4 address is specified. + */ + @Test + public void testIPv4Host() { + SocksV5CmdResponse socksV5CmdResponse = new SocksV5CmdResponse( + SocksV5CmdStatus.SUCCESS, SocksV5AddressType.IPv4, "127.0.0.1", 80); + assertEquals("127.0.0.1", socksV5CmdResponse.host()); + assertEquals(80, socksV5CmdResponse.port()); + ByteBuf buffer = Unpooled.buffer(20); + socksV5CmdResponse.encodeAsByteBuf(buffer); + byte[] expected = { + 0x05, // version + 0x00, // success reply + 0x00, // reserved + 0x01, // address type IPv4 + 0x7F, // address 127.0.0.1 + 0x00, + 0x00, + 0x01, + 0x00, // port + 0x50 + }; + assertByteBufEquals(expected, buffer); + } + + /** + * Verifies that empty domain is allowed Response. + */ + @Test + public void testEmptyBoundAddress() { + SocksV5CmdResponse socksV5CmdResponse = new SocksV5CmdResponse( + SocksV5CmdStatus.SUCCESS, SocksV5AddressType.DOMAIN, "", 80); + assertEquals("", socksV5CmdResponse.host()); + assertEquals(80, socksV5CmdResponse.port()); + ByteBuf buffer = Unpooled.buffer(20); + socksV5CmdResponse.encodeAsByteBuf(buffer); + byte[] expected = { + 0x05, // version + 0x00, // success reply + 0x00, // reserved + 0x03, // address type domain + 0x00, // domain length + 0x00, // port + 0x50 + }; + assertByteBufEquals(expected, buffer); + } + + /** + * Verifies that Response cannot be constructed with invalid IP. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidBoundAddress() { + new SocksV5CmdResponse(SocksV5CmdStatus.SUCCESS, SocksV5AddressType.IPv4, "127.0.0", 1000); + } + + private static void assertByteBufEquals(byte[] expected, ByteBuf actual) { + byte[] actualBytes = new byte[actual.readableBytes()]; + actual.readBytes(actualBytes); + assertEquals("Generated response has incorrect length", expected.length, actualBytes.length); + assertArrayEquals("Generated response differs from expected", expected, actualBytes); + } + + @Test + public void testValidPortRange() { + try { + new SocksV5CmdResponse(SocksV5CmdStatus.SUCCESS, SocksV5AddressType.IPv4, "127.0.0", 0); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + + try { + new SocksV5CmdResponse(SocksV5CmdStatus.SUCCESS, SocksV5AddressType.IPv4, "127.0.0", 65536); + } catch (Exception e) { + assertTrue(e instanceof IllegalArgumentException); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CommonTestUtils.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CommonTestUtils.java new file mode 100755 index 0000000000..c155a09644 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5CommonTestUtils.java @@ -0,0 +1,37 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.handler.codec.socksx.SocksMessage; + +final class SocksV5CommonTestUtils { + /** + * A constructor to stop this class being constructed. + */ + private SocksV5CommonTestUtils() { + //NOOP + } + + @SuppressWarnings("deprecation") + public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, SocksMessage msg) { + ByteBuf buf = Unpooled.buffer(); + msg.encodeAsByteBuf(buf); + embedder.writeInbound(buf); + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequestTest.java new file mode 100755 index 0000000000..3cd989566b --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5InitRequestTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; + +public class SocksV5InitRequestTest { + @Test + public void testConstructorParamsAreNotNull() { + try { + new SocksV5InitRequest(null); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponseTest.java new file mode 100755 index 0000000000..10840a7395 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/SocksV5InitResponseTest.java @@ -0,0 +1,30 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.handler.codec.socksx.v5; + +import org.junit.Test; +import static org.junit.Assert.assertTrue; + +public class SocksV5InitResponseTest { + @Test + public void testConstructorParamsAreNotNull() { + try { + new SocksV5InitResponse(null); + } catch (Exception e) { + assertTrue(e instanceof NullPointerException); + } + } +} diff --git a/example/pom.xml b/example/pom.xml index 5e80e00ec1..fdd7c0bcce 100644 --- a/example/pom.xml +++ b/example/pom.xml @@ -72,7 +72,7 @@ ${project.groupId} netty-tcnative - ${os.detected.classifier} + ${os.detected.classifier} org.eclipse.jetty.npn diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksPortUnificationServerHandler.java b/example/src/main/java/io/netty/example/socksproxy/SocksPortUnificationServerHandler.java new file mode 100644 index 0000000000..cbb5dadd53 --- /dev/null +++ b/example/src/main/java/io/netty/example/socksproxy/SocksPortUnificationServerHandler.java @@ -0,0 +1,52 @@ +/* + * Copyright 2012 The Netty Project + * + * The Netty Project licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package io.netty.example.socksproxy; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.socksx.SocksMessageEncoder; +import io.netty.handler.codec.socksx.SocksProtocolVersion; +import io.netty.handler.codec.socksx.v4.SocksV4CmdRequestDecoder; +import io.netty.handler.codec.socksx.v5.SocksV5InitRequestDecoder; + +import java.util.List; + +public class SocksPortUnificationServerHandler extends ByteToMessageDecoder { + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + ChannelPipeline p = ctx.pipeline(); + SocksProtocolVersion version = SocksProtocolVersion.valueOf(in.readByte()); + System.out.println(version); + in.resetReaderIndex(); + switch (version) { + case SOCKS4a: + p.addLast(new SocksV4CmdRequestDecoder()); + break; + case SOCKS5: + p.addLast(new SocksV5InitRequestDecoder()); + break; + case UNKNOWN: + in.clear(); + ctx.close(); + return; + } + p.addLast(SocksMessageEncoder.getInstance()); + p.addLast(SocksServerHandler.getInstance()); + p.remove(this); + } +} diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java b/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java index 46428e461c..4d71d6d56c 100644 --- a/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java +++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java @@ -27,61 +27,124 @@ import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.handler.codec.socks.SocksCmdRequest; import io.netty.handler.codec.socks.SocksCmdResponse; import io.netty.handler.codec.socks.SocksCmdStatus; +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.SocksRequest; +import io.netty.handler.codec.socksx.v4.SocksV4CmdRequest; +import io.netty.handler.codec.socksx.v4.SocksV4CmdResponse; +import io.netty.handler.codec.socksx.v4.SocksV4CmdStatus; +import io.netty.handler.codec.socksx.v5.SocksV5CmdRequest; +import io.netty.handler.codec.socksx.v5.SocksV5CmdResponse; +import io.netty.handler.codec.socksx.v5.SocksV5CmdStatus; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.Promise; @ChannelHandler.Sharable -public final class SocksServerConnectHandler extends SimpleChannelInboundHandler { +public final class SocksServerConnectHandler extends SimpleChannelInboundHandler { private final Bootstrap b = new Bootstrap(); @Override - public void channelRead0(final ChannelHandlerContext ctx, final SocksCmdRequest request) throws Exception { - Promise promise = ctx.executor().newPromise(); - promise.addListener( - new GenericFutureListener>() { - @Override - public void operationComplete(final Future future) throws Exception { - final Channel outboundChannel = future.getNow(); - if (future.isSuccess()) { - ctx.channel().writeAndFlush(new SocksCmdResponse(SocksCmdStatus.SUCCESS, request.addressType())) - .addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture channelFuture) { - ctx.pipeline().remove(SocksServerConnectHandler.this); - outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel())); - ctx.pipeline().addLast(new RelayHandler(outboundChannel)); - } - }); - } else { - ctx.channel().writeAndFlush(new SocksCmdResponse(SocksCmdStatus.FAILURE, request.addressType())); - SocksServerUtils.closeOnFlush(ctx.channel()); + public void channelRead0(final ChannelHandlerContext ctx, final SocksRequest message) throws Exception { + if (message instanceof SocksV4CmdRequest) { + final SocksV4CmdRequest request = (SocksV4CmdRequest) message; + Promise promise = ctx.executor().newPromise(); + promise.addListener( + new GenericFutureListener>() { + @Override + public void operationComplete(final Future future) throws Exception { + final Channel outboundChannel = future.getNow(); + if (future.isSuccess()) { + ctx.channel().writeAndFlush(new SocksV4CmdResponse(SocksV4CmdStatus.SUCCESS)) + .addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture channelFuture) { + ctx.pipeline().remove(SocksServerConnectHandler.this); + outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel())); + ctx.pipeline().addLast(new RelayHandler(outboundChannel)); + } + }); + } else { + ctx.channel().writeAndFlush( + new SocksV4CmdResponse(SocksV4CmdStatus.REJECTED_OR_FAILED) + ); + SocksServerUtils.closeOnFlush(ctx.channel()); + } + } + }); + + final Channel inboundChannel = ctx.channel(); + b.group(inboundChannel.eventLoop()) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(new DirectClientHandler(promise)); + + b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + // Connection established use handler provided results + } else { + // Close the connection if the connection attempt has failed. + ctx.channel().writeAndFlush( + new SocksV4CmdResponse(SocksV4CmdStatus.REJECTED_OR_FAILED) + ); + SocksServerUtils.closeOnFlush(ctx.channel()); + } } - } - }); + }); + } else if (message instanceof SocksV5CmdRequest) { + final SocksV5CmdRequest request = (SocksV5CmdRequest) message; + Promise promise = ctx.executor().newPromise(); + promise.addListener( + new GenericFutureListener>() { + @Override + public void operationComplete(final Future future) throws Exception { + final Channel outboundChannel = future.getNow(); + if (future.isSuccess()) { + ctx.channel().writeAndFlush( + new SocksV5CmdResponse(SocksV5CmdStatus.SUCCESS, request.addressType()) + ).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture channelFuture) { + ctx.pipeline().remove(SocksServerConnectHandler.this); + outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel())); + ctx.pipeline().addLast(new RelayHandler(outboundChannel)); + } + } + ); + } else { + ctx.channel().writeAndFlush( + new SocksV5CmdResponse(SocksV5CmdStatus.FAILURE, request.addressType())); + SocksServerUtils.closeOnFlush(ctx.channel()); + } + } + }); - final Channel inboundChannel = ctx.channel(); + final Channel inboundChannel = ctx.channel(); + b.group(inboundChannel.eventLoop()) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) + .option(ChannelOption.SO_KEEPALIVE, true) + .handler(new DirectClientHandler(promise)); - b.group(inboundChannel.eventLoop()) - .channel(NioSocketChannel.class) - .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) - .option(ChannelOption.SO_KEEPALIVE, true) - .handler(new DirectClientHandler(promise)); - - b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { - @Override - public void operationComplete(ChannelFuture future) throws Exception { - if (future.isSuccess()) { - // Connection established use handler provided results - } else { - // Close the connection if the connection attempt has failed. - ctx.channel().writeAndFlush( - new SocksCmdResponse(SocksCmdStatus.FAILURE, request.addressType())); - SocksServerUtils.closeOnFlush(ctx.channel()); + b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { + @Override + public void operationComplete(ChannelFuture future) throws Exception { + if (future.isSuccess()) { + // Connection established use handler provided results + } else { + // Close the connection if the connection attempt has failed. + ctx.channel().writeAndFlush( + new SocksV5CmdResponse(SocksV5CmdStatus.FAILURE, request.addressType())); + SocksServerUtils.closeOnFlush(ctx.channel()); + } } - } - }); + }); + } else { + ctx.close(); + } } @Override diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java b/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java index df71ce3ca8..fb27abf7d7 100644 --- a/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java +++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java @@ -18,37 +18,27 @@ package io.netty.example.socksproxy; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; -import io.netty.handler.codec.socks.SocksAuthResponse; -import io.netty.handler.codec.socks.SocksAuthScheme; -import io.netty.handler.codec.socks.SocksAuthStatus; -import io.netty.handler.codec.socks.SocksCmdRequest; -import io.netty.handler.codec.socks.SocksCmdRequestDecoder; -import io.netty.handler.codec.socks.SocksCmdType; -import io.netty.handler.codec.socks.SocksInitResponse; -import io.netty.handler.codec.socks.SocksRequest; - +import io.netty.handler.codec.socksx.SocksRequest; +import io.netty.handler.codec.socksx.v4.SocksV4CmdRequest; +import io.netty.handler.codec.socksx.v4.SocksV4CmdType; +import io.netty.handler.codec.socksx.v5.SocksV5AuthScheme; +import io.netty.handler.codec.socksx.v5.SocksV5CmdRequestDecoder; +import io.netty.handler.codec.socksx.v5.SocksV5InitResponse; +import io.netty.handler.codec.socksx.v5.SocksV5Request; +import io.netty.handler.codec.socksx.v5.SocksV5AuthResponse; +import io.netty.handler.codec.socksx.v5.SocksV5AuthStatus; +import io.netty.handler.codec.socksx.v5.SocksV5CmdRequest; +import io.netty.handler.codec.socksx.v5.SocksV5CmdType; @ChannelHandler.Sharable public final class SocksServerHandler extends SimpleChannelInboundHandler { @Override public void channelRead0(ChannelHandlerContext ctx, SocksRequest socksRequest) throws Exception { - switch (socksRequest.requestType()) { - case INIT: { - // auth support example - //ctx.pipeline().addFirst(new SocksAuthRequestDecoder()); - //ctx.write(new SocksInitResponse(SocksAuthScheme.AUTH_PASSWORD)); - ctx.pipeline().addFirst(new SocksCmdRequestDecoder()); - ctx.write(new SocksInitResponse(SocksAuthScheme.NO_AUTH)); - break; - } - case AUTH: - ctx.pipeline().addFirst(new SocksCmdRequestDecoder()); - ctx.write(new SocksAuthResponse(SocksAuthStatus.SUCCESS)); - break; - case CMD: - SocksCmdRequest req = (SocksCmdRequest) socksRequest; - if (req.cmdType() == SocksCmdType.CONNECT) { + switch (socksRequest.protocolVersion()) { + case SOCKS4a: + SocksV4CmdRequest socksV4CmdRequest = (SocksV4CmdRequest) socksRequest; + if (socksV4CmdRequest.cmdType() == SocksV4CmdType.CONNECT) { ctx.pipeline().addLast(new SocksServerConnectHandler()); ctx.pipeline().remove(this); ctx.fireChannelRead(socksRequest); @@ -56,6 +46,35 @@ public final class SocksServerHandler extends SimpleChannelInboundHandler { - - private final SocksMessageEncoder socksMessageEncoder = new SocksMessageEncoder(); - private final SocksServerHandler socksServerHandler = new SocksServerHandler(); - @Override public void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline p = socketChannel.pipeline(); - p.addLast(new SocksInitRequestDecoder()); - p.addLast(socksMessageEncoder); - p.addLast(socksServerHandler); + p.addFirst(new LoggingHandler(LogLevel.DEBUG)); + p.addLast(new SocksPortUnificationServerHandler()); } }