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/AbstractSocksMessage.java old mode 100755 new mode 100644 similarity index 54% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksProtocolVersion.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/AbstractSocksMessage.java index c0650df526..fbb847a68c --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksProtocolVersion.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/AbstractSocksMessage.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -16,27 +16,25 @@ package io.netty.handler.codec.socksx; -public enum SocksProtocolVersion { - SOCKS4a((byte) 0x04), - SOCKS5((byte) 0x05), - UNKNOWN((byte) 0xff); +import io.netty.handler.codec.DecoderResult; - private final byte b; +/** + * An abstract {@link SocksMessage}. + */ +public abstract class AbstractSocksMessage implements SocksMessage { - SocksProtocolVersion(byte b) { - this.b = b; + private DecoderResult decoderResult = DecoderResult.SUCCESS; + + @Override + public DecoderResult decoderResult() { + return decoderResult; } - public static SocksProtocolVersion valueOf(byte b) { - for (SocksProtocolVersion code : values()) { - if (code.b == b) { - return code; - } + @Override + public void setDecoderResult(DecoderResult decoderResult) { + if (decoderResult == null) { + throw new NullPointerException("decoderResult"); } - return UNKNOWN; - } - - public byte byteValue() { - return b; + this.decoderResult = decoderResult; } } 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 index a31c8034db..795ae9ee38 100755 --- 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 @@ -15,40 +15,15 @@ */ package io.netty.handler.codec.socksx; +import io.netty.handler.codec.DecoderResultProvider; + /** - * An abstract class that defines a SocksMessage, providing common properties for - * {@link SocksRequest} and {@link SocksResponse}. + * An interface that all SOCKS protocol messages implement. */ -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; - } +public interface SocksMessage extends DecoderResultProvider { /** - * Returns the {@link SocksMessageType} of this {@link SocksMessage} - * - * @return The {@link SocksMessageType} of this {@link SocksMessage} + * Returns the protocol version of this message. */ - 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; - } + SocksVersion version(); } 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 deleted file mode 100755 index ef349f7955..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksMessageType.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * 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/SocksPortUnificationServerHandler.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksPortUnificationServerHandler.java new file mode 100644 index 0000000000..c4dfe3f642 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksPortUnificationServerHandler.java @@ -0,0 +1,104 @@ +/* + * Copyright 2015 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.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.ByteToMessageDecoder; +import io.netty.handler.codec.socksx.v4.Socks4ServerDecoder; +import io.netty.handler.codec.socksx.v4.Socks4ServerEncoder; +import io.netty.handler.codec.socksx.v5.Socks5AddressEncoder; +import io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder; +import io.netty.handler.codec.socksx.v5.Socks5ServerEncoder; +import io.netty.util.internal.logging.InternalLogger; +import io.netty.util.internal.logging.InternalLoggerFactory; + +import java.util.List; + +/** + * Detects the version of the current SOCKS connection and initializes the pipeline with + * {@link Socks4ServerDecoder} or {@link Socks5InitialRequestDecoder}. + */ +public class SocksPortUnificationServerHandler extends ByteToMessageDecoder { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(SocksPortUnificationServerHandler.class); + + private final Socks5ServerEncoder socks5encoder; + + /** + * Creates a new instance with the default configuration. + */ + public SocksPortUnificationServerHandler() { + this(Socks5ServerEncoder.DEFAULT); + } + + /** + * Creates a new instance with the specified {@link Socks5ServerEncoder}. + * This constructor is useful when a user wants to use an alternative {@link Socks5AddressEncoder}. + */ + public SocksPortUnificationServerHandler(Socks5ServerEncoder socks5encoder) { + if (socks5encoder == null) { + throw new NullPointerException("socks5encoder"); + } + + this.socks5encoder = socks5encoder; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + final int readerIndex = in.readerIndex(); + if (in.writerIndex() == readerIndex) { + return; + } + + ChannelPipeline p = ctx.pipeline(); + final byte versionVal = in.getByte(readerIndex); + SocksVersion version = SocksVersion.valueOf(versionVal); + + switch (version) { + case SOCKS4a: + logKnownVersion(ctx, version); + p.addAfter(ctx.name(), null, Socks4ServerEncoder.INSTANCE); + p.addAfter(ctx.name(), null, new Socks4ServerDecoder()); + break; + case SOCKS5: + logKnownVersion(ctx, version); + p.addAfter(ctx.name(), null, socks5encoder); + p.addAfter(ctx.name(), null, new Socks5InitialRequestDecoder()); + break; + default: + logUnknownVersion(ctx, versionVal); + in.skipBytes(in.readableBytes()); + ctx.close(); + return; + } + + p.remove(this); + } + + private static void logKnownVersion(ChannelHandlerContext ctx, SocksVersion version) { + logger.debug("{} Protocol version: {}({})", ctx.channel(), version); + } + + private static void logUnknownVersion(ChannelHandlerContext ctx, byte versionVal) { + if (logger.isDebugEnabled()) { + logger.debug("{} Unknown protocol version: {}", ctx.channel(), versionVal & 0xFF); + } + } +} 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 deleted file mode 100644 index 449cdbc891..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksRequest.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * 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.handler.codec.socksx.v4.Socks4Request; -import io.netty.handler.codec.socksx.v5.Socks5Request; - -/** - * An abstract class that defines a SOCKS request, providing common properties for - * {@link Socks4Request} and {@link Socks5Request}. - */ -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 deleted file mode 100644 index 975123b6d6..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksResponse.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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.handler.codec.socksx.v4.Socks4Response; -import io.netty.handler.codec.socksx.v5.Socks5Response; - -/** - * An abstract class that defines a SOCKS response, providing common properties for - * {@link Socks4Response} and {@link Socks5Response}. - */ -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/SocksVersion.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksVersion.java new file mode 100755 index 0000000000..83f74c855f --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/SocksVersion.java @@ -0,0 +1,64 @@ +/* + * 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; + +/** + * The version of SOCKS protocol. + */ +public enum SocksVersion { + /** + * SOCKS protocol version 4a (or 4) + */ + SOCKS4a((byte) 0x04), + /** + * SOCKS protocol version 5 + */ + SOCKS5((byte) 0x05), + /** + * Unknown protocol version + */ + UNKNOWN((byte) 0xff); + + /** + * Returns the {@link SocksVersion} that corresponds to the specified version field value, + * as defined in the protocol specification. + * + * @return {@link #UNKNOWN} if the specified value does not represent a known SOCKS protocol version + */ + public static SocksVersion valueOf(byte b) { + if (b == SOCKS4a.byteValue()) { + return SOCKS4a; + } + if (b == SOCKS5.byteValue()) { + return SOCKS5; + } + return UNKNOWN; + } + + private final byte b; + + SocksVersion(byte b) { + this.b = b; + } + + /** + * Returns the value of the version field, as defined in the protocol specification. + */ + public byte byteValue() { + return b; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocks4Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/AbstractSocks4Message.java old mode 100755 new mode 100644 similarity index 63% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocks4Response.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/AbstractSocks4Message.java index 7be1de8f12..55a23a81ca --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocks4Response.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/AbstractSocks4Message.java @@ -1,5 +1,5 @@ /* - * Copyright 2012 The Netty Project + * 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 @@ -13,23 +13,18 @@ * 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.handler.codec.socksx.AbstractSocksMessage; +import io.netty.handler.codec.socksx.SocksVersion; /** - * An unknown socks response. - * - * @see Socks4CmdResponseDecoder + * An abstract {@link Socks4Message}. */ -public final class UnknownSocks4Response extends Socks4Response { - - public static final UnknownSocks4Response INSTANCE = new UnknownSocks4Response(); - - private UnknownSocks4Response() { } - +public abstract class AbstractSocks4Message extends AbstractSocksMessage implements Socks4Message { @Override - void encodeAsByteBuf(ByteBuf byteBuf) { - // NOOP + public final SocksVersion version() { + return SocksVersion.SOCKS4a; } } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/DefaultSocks4CommandRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/DefaultSocks4CommandRequest.java new file mode 100755 index 0000000000..169768e183 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/DefaultSocks4CommandRequest.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.handler.codec.DecoderResult; +import io.netty.util.internal.StringUtil; + +import java.net.IDN; + +/** + * The default {@link Socks4CommandRequest}. + */ +public class DefaultSocks4CommandRequest extends AbstractSocks4Message implements Socks4CommandRequest { + + private final Socks4CommandType type; + private final String dstAddr; + private final int dstPort; + private final String userId; + + /** + * Creates a new instance. + * + * @param type the type of the request + * @param dstAddr the {@code DSTIP} field of the request + * @param dstPort the {@code DSTPORT} field of the request + */ + public DefaultSocks4CommandRequest(Socks4CommandType type, String dstAddr, int dstPort) { + this(type, dstAddr, dstPort, ""); + } + + /** + * Creates a new instance. + * + * @param type the type of the request + * @param dstAddr the {@code DSTIP} field of the request + * @param dstPort the {@code DSTPORT} field of the request + * @param userId the {@code USERID} field of the request + */ + public DefaultSocks4CommandRequest(Socks4CommandType type, String dstAddr, int dstPort, String userId) { + if (type == null) { + throw new NullPointerException("type"); + } + if (dstAddr == null) { + throw new NullPointerException("dstAddr"); + } + if (dstPort <= 0 || dstPort >= 65536) { + throw new IllegalArgumentException("dstPort: " + dstPort + " (expected: 1~65535)"); + } + if (userId == null) { + throw new NullPointerException("userId"); + } + + this.userId = userId; + this.type = type; + this.dstAddr = IDN.toASCII(dstAddr); + this.dstPort = dstPort; + } + + @Override + public Socks4CommandType type() { + return type; + } + + @Override + public String dstAddr() { + return dstAddr; + } + + @Override + public int dstPort() { + return dstPort; + } + + @Override + public String userId() { + return userId; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", type: "); + } else { + buf.append("(type: "); + } + buf.append(type()); + buf.append(", dstAddr: "); + buf.append(dstAddr()); + buf.append(", dstPort: "); + buf.append(dstPort()); + buf.append(", userId: "); + buf.append(userId()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/DefaultSocks4CommandResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/DefaultSocks4CommandResponse.java new file mode 100755 index 0000000000..bd76265e44 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/DefaultSocks4CommandResponse.java @@ -0,0 +1,101 @@ +/* + * 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.DecoderResult; +import io.netty.util.NetUtil; +import io.netty.util.internal.StringUtil; + +/** + * The default {@link Socks4CommandResponse}. + */ +public class DefaultSocks4CommandResponse extends AbstractSocks4Message implements Socks4CommandResponse { + + private final Socks4CommandStatus status; + private final String dstAddr; + private final int dstPort; + + /** + * Creates a new instance. + * + * @param status the status of the response + */ + public DefaultSocks4CommandResponse(Socks4CommandStatus status) { + this(status, null, 0); + } + + /** + * Creates a new instance. + * + * @param status the status of the response + * @param dstAddr the {@code DSTIP} field of the response + * @param dstPort the {@code DSTPORT} field of the response + */ + public DefaultSocks4CommandResponse(Socks4CommandStatus status, String dstAddr, int dstPort) { + if (status == null) { + throw new NullPointerException("cmdStatus"); + } + if (dstAddr != null) { + if (!NetUtil.isValidIpV4Address(dstAddr)) { + throw new IllegalArgumentException( + "dstAddr: " + dstAddr + " (expected: a valid IPv4 address)"); + } + } + if (dstPort < 0 || dstPort > 65535) { + throw new IllegalArgumentException("dstPort: " + dstPort + " (expected: 0~65535)"); + } + + this.status = status; + this.dstAddr = dstAddr; + this.dstPort = dstPort; + } + + @Override + public Socks4CommandStatus status() { + return status; + } + + @Override + public String dstAddr() { + return dstAddr; + } + + @Override + public int dstPort() { + return dstPort; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(96); + buf.append(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", dstAddr: "); + } else { + buf.append("(dstAddr: "); + } + buf.append(dstAddr()); + buf.append(", dstPort: "); + buf.append(dstPort()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ClientDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ClientDecoder.java new file mode 100755 index 0000000000..231a78c251 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ClientDecoder.java @@ -0,0 +1,92 @@ +/* + * 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.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.v4.Socks4ClientDecoder.State; +import io.netty.util.NetUtil; + +import java.util.List; + +/** + * Decodes a single {@link Socks4CommandResponse} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove this decoder later. On failed decode, this decoder will discard the + * received data, so that other handler closes the connection later. + */ +public class Socks4ClientDecoder extends ReplayingDecoder { + + enum State { + START, + SUCCESS, + FAILURE + } + + public Socks4ClientDecoder() { + super(State.START); + setSingleDecode(true); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case START: { + final int version = in.readUnsignedByte(); + if (version != 0) { + throw new DecoderException("unsupported reply version: " + version + " (expected: 0)"); + } + + final Socks4CommandStatus status = Socks4CommandStatus.valueOf(in.readByte()); + final int dstPort = in.readUnsignedShort(); + final String dstAddr = NetUtil.intToIpAddress(in.readInt()); + + out.add(new DefaultSocks4CommandResponse(status, dstAddr, dstPort)); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + Socks4CommandResponse m = new DefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + + checkpoint(State.FAILURE); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ClientEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ClientEncoder.java new file mode 100644 index 0000000000..98f7ec6360 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ClientEncoder.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.util.NetUtil; + +/** + * Encodes a {@link Socks4CommandRequest} into a {@link ByteBuf}. + */ +@Sharable +public final class Socks4ClientEncoder extends MessageToByteEncoder { + + /** + * The singleton instance of {@link Socks4ClientEncoder} + */ + public static final Socks4ClientEncoder INSTANCE = new Socks4ClientEncoder(); + + private static final byte[] IPv4_DOMAIN_MARKER = {0x00, 0x00, 0x00, 0x01}; + + private Socks4ClientEncoder() { } + + @Override + protected void encode(ChannelHandlerContext ctx, Socks4CommandRequest msg, ByteBuf out) throws Exception { + out.writeByte(msg.version().byteValue()); + out.writeByte(msg.type().byteValue()); + out.writeShort(msg.dstPort()); + if (NetUtil.isValidIpV4Address(msg.dstAddr())) { + out.writeBytes(NetUtil.createByteArrayFromIpAddressString(msg.dstAddr())); + ByteBufUtil.writeAscii(out, msg.userId()); + out.writeByte(0); + } else { + out.writeBytes(IPv4_DOMAIN_MARKER); + ByteBufUtil.writeAscii(out, msg.userId()); + out.writeByte(0); + ByteBufUtil.writeAscii(out, msg.dstAddr()); + out.writeByte(0); + } + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequest.java deleted file mode 100755 index 42e3866b7f..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 java.net.IDN; - -/** - * An socksv4a cmd request. - * - * @see Socks4Response - * @see Socks4CmdRequestDecoder - */ -public final class Socks4CmdRequest extends Socks4Request { - private final String userId; - private final Socks4CmdType cmdType; - private final String host; - private final int port; - - private static final byte[] IPv4_DOMAIN_MARKER = {0x00, 0x00, 0x00, 0x01}; - - public Socks4CmdRequest(String userId, Socks4CmdType 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 Socks4CmdRequest(Socks4CmdType cmdType, String host, int port) { - this("", cmdType, host, port); - } - - /** - * Returns the {@link Socks4CmdType} of this {@link Socks4Request} - * - * @return The {@link Socks4CmdType} of this {@link Socks4Request} - */ - public Socks4CmdType cmdType() { - return cmdType; - } - - /** - * Returns host that is used as a parameter in {@link Socks4CmdType} - * - * @return host that is used as a parameter in {@link Socks4CmdType} - */ - public String host() { - return IDN.toUnicode(host); - } - - /** - * Returns userId that is used as a parameter in {@link Socks4CmdType} - * - * @return userId that is used as a parameter in {@link Socks4CmdType} - */ - public String userId() { - return userId; - } - - /** - * Returns port that is used as a parameter in {@link Socks4CmdType} - * - * @return port that is used as a parameter in {@link Socks4CmdType} - */ - public int port() { - return port; - } - - @Override - 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/Socks4CmdRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequestDecoder.java deleted file mode 100755 index aa44677c7d..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequestDecoder.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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.Socks4CmdRequestDecoder.State; -import io.netty.util.CharsetUtil; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks4CmdRequest}. - * Before returning SocksRequest decoder removes itself from pipeline. - */ -public class Socks4CmdRequestDecoder extends ReplayingDecoder { - - private SocksProtocolVersion version; - private Socks4CmdType cmdType; - @SuppressWarnings("UnusedDeclaration") - private byte reserved; - private String host; - private int port; - private String userId; - private Socks4Request msg = UnknownSocks4Request.INSTANCE; - - public Socks4CmdRequestDecoder() { - 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 = Socks4CmdType.valueOf(byteBuf.readByte()); - port = byteBuf.readUnsignedShort(); - host = Socks4CommonUtils.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 (!"0.0.0.0".equals(host) && host.startsWith("0.0.0.")) { - host = readNullTerminatedString(byteBuf); - } - msg = new Socks4CmdRequest(userId, cmdType, host, port); - } - } - ctx.pipeline().remove(this); - out.add(msg); - } - private static String readNullTerminatedString(ByteBuf byteBuf) throws Exception { - byte NULL_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/Socks4CmdResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponse.java deleted file mode 100755 index 78142c969a..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponse.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * 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 Socks4CmdRequest - * @see Socks4CmdResponseDecoder - */ -public final class Socks4CmdResponse extends Socks4Response { - private final Socks4CmdStatus 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 Socks4CmdResponse(Socks4CmdStatus 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 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 Socks4CmdResponse(Socks4CmdStatus 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 Socks4CmdStatus} of this {@link Socks4Response} - * - * @return The {@link Socks4CmdStatus} of this {@link Socks4Response} - */ - public Socks4CmdStatus cmdStatus() { - return cmdStatus; - } - - /** - * Returns host that is used as a parameter in {@link Socks4CmdType}. - * 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 Socks4CmdType} - * 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 Socks4CmdType}. - * 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 Socks4CmdType} - */ - 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/Socks4CmdResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponseDecoder.java deleted file mode 100755 index c8f11e795d..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponseDecoder.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.v4.Socks4CmdResponseDecoder.State; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks4CmdResponse}. - * Before returning SocksResponse decoder removes itself from pipeline. - */ -public class Socks4CmdResponseDecoder extends ReplayingDecoder { - - private Socks4CmdStatus cmdStatus; - - private String host; - private int port; - private Socks4Response msg = UnknownSocks4Response.INSTANCE; - - public Socks4CmdResponseDecoder() { - 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 = Socks4CmdStatus.valueOf(byteBuf.readByte()); - checkpoint(State.READ_CMD_ADDRESS); - } - case READ_CMD_ADDRESS: { - port = byteBuf.readUnsignedShort(); - host = Socks4CommonUtils.intToIp(byteBuf.readInt()); - msg = new Socks4CmdResponse(cmdStatus, host, port); - } - } - 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/Socks4CmdStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdStatus.java deleted file mode 100755 index 65b38a7c93..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdStatus.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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 Socks4CmdStatus { - SUCCESS((byte) 0x5a), - REJECTED_OR_FAILED((byte) 0x5b), - IDENTD_UNREACHABLE((byte) 0x5c), - IDENTD_AUTH_FAILURE((byte) 0x5d), - UNASSIGNED((byte) 0xff); - - private final byte b; - - Socks4CmdStatus(byte b) { - this.b = b; - } - - public static Socks4CmdStatus valueOf(byte b) { - for (Socks4CmdStatus 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/Socks4CmdType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandRequest.java similarity index 58% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdType.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandRequest.java index 40d9c612ba..176ce0873f 100755 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CmdType.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandRequest.java @@ -15,27 +15,28 @@ */ package io.netty.handler.codec.socksx.v4; -public enum Socks4CmdType { - CONNECT((byte) 0x01), - BIND((byte) 0x02), - UNKNOWN((byte) 0xff); +/** + * A SOCKS4a {@code CONNECT} or {@code BIND} request. + */ +public interface Socks4CommandRequest extends Socks4Message { - private final byte b; + /** + * Returns the type of this request. + */ + Socks4CommandType type(); - Socks4CmdType(byte b) { - this.b = b; - } + /** + * Returns the {@code USERID} field of this request. + */ + String userId(); - public static Socks4CmdType valueOf(byte b) { - for (Socks4CmdType code : values()) { - if (code.b == b) { - return code; - } - } - return UNKNOWN; - } + /** + * Returns the {@code DSTIP} field of this request. + */ + String dstAddr(); - public byte byteValue() { - return b; - } + /** + * Returns the {@code DSTPORT} field of this request. + */ + int dstPort(); } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocks4Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandResponse.java similarity index 65% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocks4Request.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandResponse.java index 5d0ee6c9cb..90dc9a92a1 100755 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/UnknownSocks4Request.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandResponse.java @@ -15,21 +15,23 @@ */ package io.netty.handler.codec.socksx.v4; -import io.netty.buffer.ByteBuf; - /** - * An unknown socks request. - * - * @see Socks4CmdRequestDecoder + * A SOCKS4a response. */ -public final class UnknownSocks4Request extends Socks4Request { +public interface Socks4CommandResponse extends Socks4Message { - public static final UnknownSocks4Request INSTANCE = new UnknownSocks4Request(); + /** + * Returns the status of this response. + */ + Socks4CommandStatus status(); - private UnknownSocks4Request() { } + /** + * Returns the {@code DSTIP} field of this response. + */ + String dstAddr(); - @Override - void encodeAsByteBuf(ByteBuf byteBuf) { - // NOOP - } + /** + * Returns the {@code DSTPORT} field of this response. + */ + int dstPort(); } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandStatus.java new file mode 100755 index 0000000000..fdcfe0f18f --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandStatus.java @@ -0,0 +1,95 @@ +/* + * 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; + +/** + * The status of {@link Socks4CommandResponse}. + */ +public class Socks4CommandStatus implements Comparable { + + public static final Socks4CommandStatus SUCCESS = new Socks4CommandStatus(0x5a, "SUCCESS"); + public static final Socks4CommandStatus REJECTED_OR_FAILED = new Socks4CommandStatus(0x5b, "REJECTED_OR_FAILED"); + public static final Socks4CommandStatus IDENTD_UNREACHABLE = new Socks4CommandStatus(0x5c, "IDENTD_UNREACHABLE"); + public static final Socks4CommandStatus IDENTD_AUTH_FAILURE = new Socks4CommandStatus(0x5d, "IDENTD_AUTH_FAILURE"); + + public static Socks4CommandStatus valueOf(byte b) { + switch (b) { + case 0x5a: + return SUCCESS; + case 0x5b: + return REJECTED_OR_FAILED; + case 0x5c: + return IDENTD_UNREACHABLE; + case 0x5d: + return IDENTD_AUTH_FAILURE; + } + + return new Socks4CommandStatus(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks4CommandStatus(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks4CommandStatus(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + this.byteValue = (byte) byteValue; + this.name = name; + } + + public byte byteValue() { + return byteValue; + } + + public boolean isSuccess() { + return byteValue == 0x5a; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks4CommandStatus)) { + return false; + } + + return byteValue == ((Socks4CommandStatus) obj).byteValue; + } + + @Override + public int compareTo(Socks4CommandStatus o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandType.java new file mode 100755 index 0000000000..0cd6fe48f8 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommandType.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.v4; + +/** + * The type of {@link Socks4CommandRequest}. + */ +public class Socks4CommandType implements Comparable { + + public static final Socks4CommandType CONNECT = new Socks4CommandType(0x01, "CONNECT"); + public static final Socks4CommandType BIND = new Socks4CommandType(0x02, "BIND"); + + public static Socks4CommandType valueOf(byte b) { + switch (b) { + case 0x01: + return CONNECT; + case 0x02: + return BIND; + } + + return new Socks4CommandType(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks4CommandType(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks4CommandType(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + this.byteValue = (byte) byteValue; + this.name = name; + } + + public byte byteValue() { + return byteValue; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks4CommandType)) { + return false; + } + + return byteValue == ((Socks4CommandType) obj).byteValue; + } + + @Override + public int compareTo(Socks4CommandType o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommonUtils.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommonUtils.java deleted file mode 100755 index 56f20737a8..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4CommonUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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; - -final class Socks4CommonUtils { - - 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 Socks4CommonUtils() { - // 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/Socks5ResponseType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Message.java old mode 100755 new mode 100644 similarity index 69% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ResponseType.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Message.java index 17bd2ffb37..2322ea6764 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ResponseType.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Message.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -14,14 +14,13 @@ * under the License. */ -package io.netty.handler.codec.socksx.v5; +package io.netty.handler.codec.socksx.v4; + +import io.netty.handler.codec.socksx.SocksMessage; /** - * Type of socks response + * A tag interface that all SOCKS4a protocol messages implement. */ -public enum Socks5ResponseType { - INIT, - AUTH, - CMD, - UNKNOWN +public interface Socks4Message extends SocksMessage { + // Tag interface } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4MessageEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4MessageEncoder.java deleted file mode 100755 index 4f68cfff56..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4MessageEncoder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import io.netty.handler.codec.socksx.SocksMessage; -import io.netty.handler.codec.socksx.SocksProtocolVersion; - -/** - * Encodes a {@link Socks4Request} and {@link Socks4Response} into a {@link ByteBuf}. - */ -@ChannelHandler.Sharable -public final class Socks4MessageEncoder extends MessageToByteEncoder { - - public static final Socks4MessageEncoder INSTANCE = new Socks4MessageEncoder(); - - private Socks4MessageEncoder() { } - - @Override - public boolean acceptOutboundMessage(Object msg) throws Exception { - return super.acceptOutboundMessage(msg) && - ((SocksMessage) msg).protocolVersion() == SocksProtocolVersion.SOCKS4a; - } - - @Override - protected void encode(ChannelHandlerContext ctx, SocksMessage msg, ByteBuf out) throws Exception { - if (msg instanceof Socks4Response) { - ((Socks4Response) msg).encodeAsByteBuf(out); - } else if (msg instanceof Socks4Request) { - ((Socks4Request) msg).encodeAsByteBuf(out); - } else { - // Should not reach here. - throw new Error(); - } - } -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Request.java deleted file mode 100755 index c57a89abf0..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Request.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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.handler.codec.socks.SocksMessage; -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 Socks4CmdRequest}. - * - * @see Socks4CmdRequest - * @see UnknownSocks4Request - */ -public abstract class Socks4Request extends SocksRequest { - protected Socks4Request() { - super(SocksProtocolVersion.SOCKS4a); - } - - /** - * We could have defined this method in {@link SocksMessage} as a protected method, but we did not, - * because we do not want to expose this method to users. - */ - abstract void encodeAsByteBuf(ByteBuf byteBuf); -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Response.java deleted file mode 100755 index 50924dd59c..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4Response.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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.handler.codec.socks.SocksMessage; -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 Socks4CmdResponse}. - * - * @see Socks4CmdResponse - * @see UnknownSocks4Response - */ -public abstract class Socks4Response extends SocksResponse { - - protected Socks4Response() { - super(SocksProtocolVersion.SOCKS4a); - } - - /** - * We could have defined this method in {@link SocksMessage} as a protected method, but we did not, - * because we do not want to expose this method to users. - */ - abstract void encodeAsByteBuf(ByteBuf byteBuf); -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ServerDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ServerDecoder.java new file mode 100755 index 0000000000..cdb0e0e7c7 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ServerDecoder.java @@ -0,0 +1,133 @@ +/* + * 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.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksVersion; +import io.netty.handler.codec.socksx.v4.Socks4ServerDecoder.State; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; + +import java.util.List; + +/** + * Decodes a single {@link Socks4CommandRequest} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove this decoder later. On failed decode, this decoder will discard the + * received data, so that other handler closes the connection later. + */ +public class Socks4ServerDecoder extends ReplayingDecoder { + + private static final int MAX_FIELD_LENGTH = 255; + + enum State { + START, + READ_USERID, + READ_DOMAIN, + SUCCESS, + FAILURE + } + + private Socks4CommandType type; + private String dstAddr; + private int dstPort; + private String userId; + + public Socks4ServerDecoder() { + super(State.START); + setSingleDecode(true); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case START: { + final int version = in.readUnsignedByte(); + if (version != SocksVersion.SOCKS4a.byteValue()) { + throw new DecoderException("unsupported protocol version: " + version); + } + + type = Socks4CommandType.valueOf(in.readByte()); + dstPort = in.readUnsignedShort(); + dstAddr = NetUtil.intToIpAddress(in.readInt()); + checkpoint(State.READ_USERID); + } + case READ_USERID: { + userId = readString("userid", in); + checkpoint(State.READ_DOMAIN); + } + case READ_DOMAIN: { + // Check for Socks4a protocol marker 0.0.0.x + if (!"0.0.0.0".equals(dstAddr) && dstAddr.startsWith("0.0.0.")) { + dstAddr = readString("dstAddr", in); + } + out.add(new DefaultSocks4CommandRequest(type, dstAddr, dstPort, userId)); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + Socks4CommandRequest m = new DefaultSocks4CommandRequest( + type != null? type : Socks4CommandType.CONNECT, + dstAddr != null? dstAddr : "", + dstPort != 0? dstPort : 65535, + userId != null? userId : ""); + + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + + checkpoint(State.FAILURE); + } + + /** + * Reads a variable-length NUL-terminated string as defined in SOCKS4. + */ + private static String readString(String fieldName, ByteBuf in) { + int length = in.bytesBefore(MAX_FIELD_LENGTH + 1, (byte) 0); + if (length < 0) { + throw new DecoderException("field '" + fieldName + "' longer than " + MAX_FIELD_LENGTH + " chars"); + } + + String value = in.readSlice(length).toString(CharsetUtil.US_ASCII); + in.skipBytes(1); // Skip the NUL. + + return value; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ServerEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ServerEncoder.java new file mode 100644 index 0000000000..0b24ce428b --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v4/Socks4ServerEncoder.java @@ -0,0 +1,45 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v4; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.util.NetUtil; + +/** + * Encodes a {@link Socks4CommandResponse} into a {@link ByteBuf}. + */ +@Sharable +public final class Socks4ServerEncoder extends MessageToByteEncoder { + + public static final Socks4ServerEncoder INSTANCE = new Socks4ServerEncoder(); + + private static final byte[] IPv4_HOSTNAME_ZEROED = { 0x00, 0x00, 0x00, 0x00 }; + + private Socks4ServerEncoder() { } + + @Override + protected void encode(ChannelHandlerContext ctx, Socks4CommandResponse msg, ByteBuf out) throws Exception { + out.writeByte(0); + out.writeByte(msg.status().byteValue()); + out.writeShort(msg.dstPort()); + out.writeBytes(msg.dstAddr() == null? IPv4_HOSTNAME_ZEROED + : NetUtil.createByteArrayFromIpAddressString(msg.dstAddr())); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/AbstractSocks5Message.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/AbstractSocks5Message.java new file mode 100644 index 0000000000..00e918d441 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/AbstractSocks5Message.java @@ -0,0 +1,30 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.handler.codec.socksx.AbstractSocksMessage; +import io.netty.handler.codec.socksx.SocksVersion; + +/** + * An abstract {@link Socks5Message}. + */ +public abstract class AbstractSocks5Message extends AbstractSocksMessage implements Socks5Message { + @Override + public final SocksVersion version() { + return SocksVersion.SOCKS5; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandRequest.java new file mode 100755 index 0000000000..d90150ebc2 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandRequest.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.v5; + +import io.netty.handler.codec.DecoderResult; +import io.netty.util.NetUtil; +import io.netty.util.internal.StringUtil; + +import java.net.IDN; + +/** + * The default {@link Socks5CommandRequest}. + */ +public final class DefaultSocks5CommandRequest extends AbstractSocks5Message implements Socks5CommandRequest { + + private final Socks5CommandType type; + private final Socks5AddressType dstAddrType; + private final String dstAddr; + private final int dstPort; + + public DefaultSocks5CommandRequest( + Socks5CommandType type, Socks5AddressType dstAddrType, String dstAddr, int dstPort) { + + if (type == null) { + throw new NullPointerException("type"); + } + if (dstAddrType == null) { + throw new NullPointerException("dstAddrType"); + } + if (dstAddr == null) { + throw new NullPointerException("dstAddr"); + } + + if (dstAddrType == Socks5AddressType.IPv4) { + if (!NetUtil.isValidIpV4Address(dstAddr)) { + throw new IllegalArgumentException("dstAddr: " + dstAddr + " (expected: a valid IPv4 address)"); + } + } else if (dstAddrType == Socks5AddressType.DOMAIN) { + dstAddr = IDN.toASCII(dstAddr); + if (dstAddr.length() > 255) { + throw new IllegalArgumentException("dstAddr: " + dstAddr + " (expected: less than 256 chars)"); + } + } else if (dstAddrType == Socks5AddressType.IPv6) { + if (!NetUtil.isValidIpV6Address(dstAddr)) { + throw new IllegalArgumentException("dstAddr: " + dstAddr + " (expected: a valid IPv6 address"); + } + } + + if (dstPort <= 0 || dstPort >= 65536) { + throw new IllegalArgumentException("dstPort: " + dstPort + " (expected: 1~65535)"); + } + + this.type = type; + this.dstAddrType = dstAddrType; + this.dstAddr = dstAddr; + this.dstPort = dstPort; + } + + @Override + public Socks5CommandType type() { + return type; + } + + @Override + public Socks5AddressType dstAddrType() { + return dstAddrType; + } + + @Override + public String dstAddr() { + return dstAddr; + } + + @Override + public int dstPort() { + return dstPort; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", type: "); + } else { + buf.append("(type: "); + } + buf.append(type()); + buf.append(", dstAddrType: "); + buf.append(dstAddrType()); + buf.append(", dstAddr: "); + buf.append(dstAddr()); + buf.append(", dstPort: "); + buf.append(dstPort()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandResponse.java new file mode 100755 index 0000000000..8d9719e6f8 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandResponse.java @@ -0,0 +1,117 @@ +/* + * 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.DecoderResult; +import io.netty.util.NetUtil; +import io.netty.util.internal.StringUtil; + +import java.net.IDN; + +/** + * The default {@link Socks5CommandResponse}. + */ +public final class DefaultSocks5CommandResponse extends AbstractSocks5Message implements Socks5CommandResponse { + + private final Socks5CommandStatus status; + private final Socks5AddressType bndAddrType; + private final String bndAddr; + private final int bndPort; + + public DefaultSocks5CommandResponse(Socks5CommandStatus status, Socks5AddressType bndAddrType) { + this(status, bndAddrType, null, 0); + } + + public DefaultSocks5CommandResponse( + Socks5CommandStatus status, Socks5AddressType bndAddrType, String bndAddr, int bndPort) { + + if (status == null) { + throw new NullPointerException("status"); + } + if (bndAddrType == null) { + throw new NullPointerException("bndAddrType"); + } + if (bndAddr != null) { + if (bndAddrType == Socks5AddressType.IPv4) { + if (!NetUtil.isValidIpV4Address(bndAddr)) { + throw new IllegalArgumentException("bndAddr: " + bndAddr + " (expected: a valid IPv4 address)"); + } + } else if (bndAddrType == Socks5AddressType.DOMAIN) { + bndAddr = IDN.toASCII(bndAddr); + if (bndAddr.length() > 255) { + throw new IllegalArgumentException("bndAddr: " + bndAddr + " (expected: less than 256 chars)"); + } + } else if (bndAddrType == Socks5AddressType.IPv6) { + if (!NetUtil.isValidIpV6Address(bndAddr)) { + throw new IllegalArgumentException("bndAddr: " + bndAddr + " (expected: a valid IPv6 address)"); + } + } + } + + if (bndPort < 0 || bndPort > 65535) { + throw new IllegalArgumentException("bndPort: " + bndPort + " (expected: 0~65535)"); + } + this.status = status; + this.bndAddrType = bndAddrType; + this.bndAddr = bndAddr; + this.bndPort = bndPort; + } + + @Override + public Socks5CommandStatus status() { + return status; + } + + @Override + public Socks5AddressType bndAddrType() { + return bndAddrType; + } + + @Override + public String bndAddr() { + return bndAddr; + } + + @Override + public int bndPort() { + return bndPort; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(128); + buf.append(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", status: "); + } else { + buf.append("(status: "); + } + buf.append(status()); + buf.append(", bndAddrType: "); + buf.append(bndAddrType()); + buf.append(", bndAddr: "); + buf.append(bndAddr()); + buf.append(", bndPort: "); + buf.append(bndPort()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialRequest.java new file mode 100755 index 0000000000..0610e04993 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialRequest.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.v5; + +import io.netty.handler.codec.DecoderResult; +import io.netty.util.internal.StringUtil; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * The default {@link Socks5InitialRequest}. + */ +public class DefaultSocks5InitialRequest extends AbstractSocks5Message implements Socks5InitialRequest { + + private final List authMethods; + + public DefaultSocks5InitialRequest(Socks5AuthMethod... authMethods) { + if (authMethods == null) { + throw new NullPointerException("authMethods"); + } + + List list = new ArrayList(authMethods.length); + for (Socks5AuthMethod m: authMethods) { + if (m == null) { + break; + } + list.add(m); + } + + if (list.isEmpty()) { + throw new IllegalArgumentException("authMethods is empty"); + } + + this.authMethods = Collections.unmodifiableList(list); + } + + public DefaultSocks5InitialRequest(Iterable authMethods) { + if (authMethods == null) { + throw new NullPointerException("authSchemes"); + } + + List list = new ArrayList(); + for (Socks5AuthMethod m: authMethods) { + if (m == null) { + break; + } + list.add(m); + } + + if (list.isEmpty()) { + throw new IllegalArgumentException("authMethods is empty"); + } + + this.authMethods = Collections.unmodifiableList(list); + } + + @Override + public List authMethods() { + return authMethods; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", authMethods: "); + } else { + buf.append("(authMethods: "); + } + buf.append(authMethods()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialResponse.java new file mode 100755 index 0000000000..23d1c12ed8 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialResponse.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.handler.codec.DecoderResult; +import io.netty.util.internal.StringUtil; + +/** + * The default {@link Socks5InitialResponse}. + */ +public class DefaultSocks5InitialResponse extends AbstractSocks5Message implements Socks5InitialResponse { + + private final Socks5AuthMethod authMethod; + + public DefaultSocks5InitialResponse(Socks5AuthMethod authMethod) { + if (authMethod == null) { + throw new NullPointerException("authMethod"); + } + this.authMethod = authMethod; + } + + @Override + public Socks5AuthMethod authMethod() { + return authMethod; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", authMethod: "); + } else { + buf.append("(authMethod: "); + } + buf.append(authMethod()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthRequest.java new file mode 100755 index 0000000000..69177895c4 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthRequest.java @@ -0,0 +1,75 @@ +/* + * 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.DecoderResult; +import io.netty.util.internal.StringUtil; + +/** + * The default {@link Socks5PasswordAuthRequest}. + */ +public class DefaultSocks5PasswordAuthRequest extends AbstractSocks5Message implements Socks5PasswordAuthRequest { + + private final String username; + private final String password; + + public DefaultSocks5PasswordAuthRequest(String username, String password) { + if (username == null) { + throw new NullPointerException("username"); + } + if (password == null) { + throw new NullPointerException("password"); + } + + if (username.length() > 255) { + throw new IllegalArgumentException("username: **** (expected: less than 256 chars)"); + } + if (password.length() > 255) { + throw new IllegalArgumentException("password: **** (expected: less than 256 chars)"); + } + + this.username = username; + this.password = password; + } + + @Override + public String username() { + return username; + } + + @Override + public String password() { + return password; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", username: "); + } else { + buf.append("(username: "); + } + buf.append(username()); + buf.append(", password: ****)"); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthResponse.java new file mode 100755 index 0000000000..6cd9a30620 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthResponse.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.v5; + +import io.netty.handler.codec.DecoderResult; +import io.netty.util.internal.StringUtil; + +/** + * The default {@link Socks5PasswordAuthResponse}. + */ +public class DefaultSocks5PasswordAuthResponse extends AbstractSocks5Message implements Socks5PasswordAuthResponse { + + private final Socks5PasswordAuthStatus status; + + public DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus status) { + if (status == null) { + throw new NullPointerException("status"); + } + + this.status = status; + } + + @Override + public Socks5PasswordAuthStatus status() { + return status; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(StringUtil.simpleClassName(this)); + + DecoderResult decoderResult = decoderResult(); + if (!decoderResult.isSuccess()) { + buf.append("(decoderResult: "); + buf.append(decoderResult); + buf.append(", status: "); + } else { + buf.append("(status: "); + } + buf.append(status()); + buf.append(')'); + + return buf.toString(); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressDecoder.java new file mode 100644 index 0000000000..83b15c42f1 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressDecoder.java @@ -0,0 +1,70 @@ +/* + * Copyright 2015 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.handler.codec.DecoderException; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; + +/** + * Decodes a SOCKS5 address field into its string representation. + * + * @see Socks5CommandRequestDecoder + * @see Socks5CommandResponseDecoder + */ +public interface Socks5AddressDecoder { + + Socks5AddressDecoder DEFAULT = new Socks5AddressDecoder() { + + private static final int IPv6_LEN = 16; + + @Override + public String decodeAddress(Socks5AddressType addrType, ByteBuf in) throws Exception { + if (addrType == Socks5AddressType.IPv4) { + return NetUtil.intToIpAddress(in.readInt()); + } + if (addrType == Socks5AddressType.DOMAIN) { + final int length = in.readUnsignedByte(); + final String domain = in.toString(in.readerIndex(), length, CharsetUtil.US_ASCII); + in.skipBytes(length); + return domain; + } + if (addrType == Socks5AddressType.IPv6) { + if (in.hasArray()) { + final int readerIdx = in.readerIndex(); + in.readerIndex(readerIdx + IPv6_LEN); + return NetUtil.bytesToIpAddress(in.array(), in.arrayOffset() + readerIdx, IPv6_LEN); + } else { + byte[] tmp = new byte[IPv6_LEN]; + in.readBytes(tmp); + return NetUtil.bytesToIpAddress(tmp, 0, IPv6_LEN); + } + } else { + throw new DecoderException("unsupported address type: " + (addrType.byteValue() & 0xFF)); + } + } + }; + + /** + * Decodes a SOCKS5 address field into its string representation. + * + * @param addrType the type of the address + * @param in the input buffer which contains the SOCKS5 address field at its reader index + */ + String decodeAddress(Socks5AddressType addrType, ByteBuf in) throws Exception; +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressEncoder.java new file mode 100644 index 0000000000..16cd0a579a --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressEncoder.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 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.handler.codec.EncoderException; +import io.netty.util.CharsetUtil; +import io.netty.util.NetUtil; + +/** + * Encodes a SOCKS5 address into binary representation. + * + * @see Socks5ClientEncoder + * @see Socks5ServerEncoder + */ +public interface Socks5AddressEncoder { + + Socks5AddressEncoder DEFAULT = new Socks5AddressEncoder() { + @Override + public void encodeAddress(Socks5AddressType addrType, String addrValue, ByteBuf out) throws Exception { + final byte typeVal = addrType.byteValue(); + if (typeVal == Socks5AddressType.IPv4.byteValue()) { + if (addrValue != null) { + out.writeBytes(NetUtil.createByteArrayFromIpAddressString(addrValue)); + } else { + out.writeInt(0); + } + } else if (typeVal == Socks5AddressType.DOMAIN.byteValue()) { + if (addrValue != null) { + byte[] bndAddr = addrValue.getBytes(CharsetUtil.US_ASCII); + out.writeByte(bndAddr.length); + out.writeBytes(bndAddr); + } else { + out.writeByte(1); + out.writeByte(0); + } + } else if (typeVal == Socks5AddressType.IPv6.byteValue()) { + if (addrValue != null) { + out.writeBytes(NetUtil.createByteArrayFromIpAddressString(addrValue)); + } else { + out.writeLong(0); + out.writeLong(0); + } + } else { + throw new EncoderException("unsupported addrType: " + (addrType.byteValue() & 0xFF)); + } + } + }; + + /** + * Encodes a SOCKS5 address. + * + * @param addrType the type of the address + * @param addrValue the string representation of the address + * @param out the output buffer where the encoded SOCKS5 address field will be written to + */ + void encodeAddress(Socks5AddressType addrType, String addrValue, ByteBuf out) throws Exception; +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressType.java index f0415f4cea..554721a8bc 100755 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressType.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AddressType.java @@ -16,29 +16,74 @@ package io.netty.handler.codec.socksx.v5; -public enum Socks5AddressType { - IPv4((byte) 0x01), - DOMAIN((byte) 0x03), - IPv6((byte) 0x04), - UNKNOWN((byte) 0xff); +/** + * The type of address in {@link Socks5CommandRequest} and {@link Socks5CommandResponse}. + */ +public class Socks5AddressType implements Comparable { - private final byte b; - - Socks5AddressType(byte b) { - this.b = b; - } + public static final Socks5AddressType IPv4 = new Socks5AddressType(0x01, "IPv4"); + public static final Socks5AddressType DOMAIN = new Socks5AddressType(0x03, "DOMAIN"); + public static final Socks5AddressType IPv6 = new Socks5AddressType(0x04, "IPv6"); public static Socks5AddressType valueOf(byte b) { - for (Socks5AddressType code : values()) { - if (code.b == b) { - return code; - } + switch (b) { + case 0x01: + return IPv4; + case 0x03: + return DOMAIN; + case 0x04: + return IPv6; } - return UNKNOWN; + + return new Socks5AddressType(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks5AddressType(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks5AddressType(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + this.byteValue = (byte) byteValue; + this.name = name; } public byte byteValue() { - return b; + return byteValue; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks5AddressType)) { + return false; + } + + return byteValue == ((Socks5AddressType) obj).byteValue; + } + + @Override + public int compareTo(Socks5AddressType o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; } } - diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthMethod.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthMethod.java new file mode 100755 index 0000000000..8efceeea7a --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthMethod.java @@ -0,0 +1,96 @@ +/* + * 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; + +/** + * The authentication method of SOCKS5. + */ +public class Socks5AuthMethod implements Comparable { + + public static final Socks5AuthMethod NO_AUTH = new Socks5AuthMethod(0x00, "NO_AUTH"); + public static final Socks5AuthMethod GSSAPI = new Socks5AuthMethod(0x01, "GSSAPI"); + public static final Socks5AuthMethod PASSWORD = new Socks5AuthMethod(0x02, "PASSWORD"); + + /** + * Indicates that the server does not accept any authentication methods the client proposed. + */ + public static final Socks5AuthMethod UNACCEPTED = new Socks5AuthMethod(0xff, "UNACCEPTED"); + + public static Socks5AuthMethod valueOf(byte b) { + switch (b) { + case 0x00: + return NO_AUTH; + case 0x01: + return GSSAPI; + case 0x02: + return PASSWORD; + case (byte) 0xFF: + return UNACCEPTED; + } + + return new Socks5AuthMethod(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks5AuthMethod(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks5AuthMethod(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + this.byteValue = (byte) byteValue; + this.name = name; + } + + public byte byteValue() { + return byteValue; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks5AuthMethod)) { + return false; + } + + return byteValue == ((Socks5AuthMethod) obj).byteValue; + } + + @Override + public int compareTo(Socks5AuthMethod o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequest.java deleted file mode 100755 index 19b0260a6a..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 Socks5AuthResponse - * @see Socks5AuthRequestDecoder - */ -public final class Socks5AuthRequest extends Socks5Request { - private static final CharsetEncoder asciiEncoder = CharsetUtil.getEncoder(CharsetUtil.US_ASCII); - private static final Socks5SubnegotiationVersion SUBNEGOTIATION_VERSION = - Socks5SubnegotiationVersion.AUTH_PASSWORD; - private final String username; - private final String password; - - public Socks5AuthRequest(String username, String password) { - super(Socks5RequestType.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 - 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/Socks5AuthRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestDecoder.java deleted file mode 100755 index 3126337353..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestDecoder.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.Socks5AuthRequestDecoder.State; -import io.netty.util.CharsetUtil; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks5AuthRequest}. - * Before returning SocksRequest decoder removes itself from pipeline. - */ -public class Socks5AuthRequestDecoder extends ReplayingDecoder { - private Socks5SubnegotiationVersion version; - private int fieldLength; - private String username; - private String password; - private Socks5Request msg = UnknownSocks5Request.INSTANCE; - - public Socks5AuthRequestDecoder() { - super(State.CHECK_PROTOCOL_VERSION); - } - - @Override - protected void decode(ChannelHandlerContext ctx, ByteBuf byteBuf, List out) throws Exception { - switch (state()) { - case CHECK_PROTOCOL_VERSION: { - version = Socks5SubnegotiationVersion.valueOf(byteBuf.readByte()); - if (version != Socks5SubnegotiationVersion.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 Socks5AuthRequest(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/Socks5AuthResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponse.java deleted file mode 100755 index 7fe87755e1..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * 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 Socks5AuthRequest - * @see Socks5AuthResponseDecoder - */ -public final class Socks5AuthResponse extends Socks5Response { - private static final Socks5SubnegotiationVersion SUBNEGOTIATION_VERSION = - Socks5SubnegotiationVersion.AUTH_PASSWORD; - private final Socks5AuthStatus authStatus; - - public Socks5AuthResponse(Socks5AuthStatus authStatus) { - super(Socks5ResponseType.AUTH); - if (authStatus == null) { - throw new NullPointerException("authStatus"); - } - this.authStatus = authStatus; - } - - /** - * Returns the {@link Socks5AuthStatus} of this {@link Socks5AuthResponse} - * - * @return The {@link Socks5AuthStatus} of this {@link Socks5AuthResponse} - */ - public Socks5AuthStatus authStatus() { - return authStatus; - } - - @Override - 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/Socks5AuthResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseDecoder.java deleted file mode 100755 index 73447b9429..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseDecoder.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * 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.Socks5AuthResponseDecoder.State; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks5AuthResponse}. - * Before returning SocksResponse decoder removes itself from pipeline. - */ -public class Socks5AuthResponseDecoder extends ReplayingDecoder { - private Socks5SubnegotiationVersion version; - private Socks5AuthStatus authStatus; - private Socks5Response msg = UnknownSocks5Response.INSTANCE; - - public Socks5AuthResponseDecoder() { - super(State.CHECK_PROTOCOL_VERSION); - } - - @Override - protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List out) - throws Exception { - switch (state()) { - case CHECK_PROTOCOL_VERSION: { - version = Socks5SubnegotiationVersion.valueOf(byteBuf.readByte()); - if (version != Socks5SubnegotiationVersion.AUTH_PASSWORD) { - break; - } - checkpoint(State.READ_AUTH_RESPONSE); - } - case READ_AUTH_RESPONSE: { - authStatus = Socks5AuthStatus.valueOf(byteBuf.readByte()); - msg = new Socks5AuthResponse(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/Socks5AuthScheme.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthScheme.java deleted file mode 100755 index 1a7bd581e9..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthScheme.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 Socks5AuthScheme { - NO_AUTH((byte) 0x00), - AUTH_GSSAPI((byte) 0x01), - AUTH_PASSWORD((byte) 0x02), - UNKNOWN((byte) 0xff); - - private final byte b; - - Socks5AuthScheme(byte b) { - this.b = b; - } - - public static Socks5AuthScheme valueOf(byte b) { - for (Socks5AuthScheme 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/Socks5AuthStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthStatus.java deleted file mode 100755 index ec04b279d5..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5AuthStatus.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 Socks5AuthStatus { - SUCCESS((byte) 0x00), - FAILURE((byte) 0xff); - - private final byte b; - - Socks5AuthStatus(byte b) { - this.b = b; - } - - public static Socks5AuthStatus valueOf(byte b) { - for (Socks5AuthStatus 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/Socks5ClientEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ClientEncoder.java new file mode 100644 index 0000000000..ea6092a9b1 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ClientEncoder.java @@ -0,0 +1,118 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.EncoderException; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.util.internal.StringUtil; + +import java.util.List; +import java.util.RandomAccess; + +/** + * Encodes a client-side {@link Socks5Message} into a {@link ByteBuf}. + */ +@Sharable +public class Socks5ClientEncoder extends MessageToByteEncoder { + + public static final Socks5ClientEncoder DEFAULT = new Socks5ClientEncoder(); + + private final Socks5AddressEncoder addressEncoder; + + /** + * Creates a new instance with the default {@link Socks5AddressEncoder}. + */ + protected Socks5ClientEncoder() { + this(Socks5AddressEncoder.DEFAULT); + } + + /** + * Creates a new instance with the specified {@link Socks5AddressEncoder}. + */ + public Socks5ClientEncoder(Socks5AddressEncoder addressEncoder) { + if (addressEncoder == null) { + throw new NullPointerException("addressEncoder"); + } + + this.addressEncoder = addressEncoder; + } + + /** + * Returns the {@link Socks5AddressEncoder} of this encoder. + */ + protected final Socks5AddressEncoder addressEncoder() { + return addressEncoder; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Socks5Message msg, ByteBuf out) throws Exception { + if (msg instanceof Socks5InitialRequest) { + encodeAuthMethodRequest((Socks5InitialRequest) msg, out); + } else if (msg instanceof Socks5PasswordAuthRequest) { + encodePasswordAuthRequest((Socks5PasswordAuthRequest) msg, out); + } else if (msg instanceof Socks5CommandRequest) { + encodeCommandRequest((Socks5CommandRequest) msg, out); + } else { + throw new EncoderException("unsupported message type: " + StringUtil.simpleClassName(msg)); + } + } + + private static void encodeAuthMethodRequest(Socks5InitialRequest msg, ByteBuf out) { + out.writeByte(msg.version().byteValue()); + + final List authMethods = msg.authMethods(); + final int numAuthMethods = authMethods.size(); + out.writeByte(numAuthMethods); + + if (authMethods instanceof RandomAccess) { + for (int i = 0; i < numAuthMethods; i ++) { + out.writeByte(authMethods.get(i).byteValue()); + } + } else { + for (Socks5AuthMethod a: authMethods) { + out.writeByte(a.byteValue()); + } + } + } + + private static void encodePasswordAuthRequest(Socks5PasswordAuthRequest msg, ByteBuf out) { + out.writeByte(0x01); + + final String username = msg.username(); + out.writeByte(username.length()); + ByteBufUtil.writeAscii(out, username); + + final String password = msg.password(); + out.writeByte(password.length()); + ByteBufUtil.writeAscii(out, password); + } + + private void encodeCommandRequest(Socks5CommandRequest msg, ByteBuf out) throws Exception { + out.writeByte(msg.version().byteValue()); + out.writeByte(msg.type().byteValue()); + out.writeByte(0x00); + + final Socks5AddressType dstAddrType = msg.dstAddrType(); + out.writeByte(dstAddrType.byteValue()); + addressEncoder.encodeAddress(dstAddrType, msg.dstAddr(), out); + out.writeShort(msg.dstPort()); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequest.java deleted file mode 100755 index 5c3f8cec49..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequest.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * 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 Socks5CmdResponse - * @see Socks5CmdRequestDecoder - */ -public final class Socks5CmdRequest extends Socks5Request { - private final Socks5CmdType cmdType; - private final Socks5AddressType addressType; - private final String host; - private final int port; - - public Socks5CmdRequest(Socks5CmdType cmdType, Socks5AddressType addressType, String host, int port) { - super(Socks5RequestType.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 Socks5CmdType} of this {@link Socks5CmdRequest} - * - * @return The {@link Socks5CmdType} of this {@link Socks5CmdRequest} - */ - public Socks5CmdType cmdType() { - return cmdType; - } - - /** - * Returns the {@link Socks5AddressType} of this {@link Socks5CmdRequest} - * - * @return The {@link Socks5AddressType} of this {@link Socks5CmdRequest} - */ - public Socks5AddressType addressType() { - return addressType; - } - - /** - * Returns host that is used as a parameter in {@link Socks5CmdType} - * - * @return host that is used as a parameter in {@link Socks5CmdType} - */ - public String host() { - return IDN.toUnicode(host); - } - - /** - * Returns port that is used as a parameter in {@link Socks5CmdType} - * - * @return port that is used as a parameter in {@link Socks5CmdType} - */ - public int port() { - return port; - } - - @Override - 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/Socks5CmdRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestDecoder.java deleted file mode 100755 index 33297d760c..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestDecoder.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * 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.Socks5CmdRequestDecoder.State; -import io.netty.util.CharsetUtil; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks5CmdRequest}. - * Before returning SocksRequest decoder removes itself from pipeline. - */ -public class Socks5CmdRequestDecoder extends ReplayingDecoder { - private SocksProtocolVersion version; - private int fieldLength; - private Socks5CmdType cmdType; - private Socks5AddressType addressType; - @SuppressWarnings("UnusedDeclaration") - private byte reserved; - private String host; - private int port; - private Socks5Request msg = UnknownSocks5Request.INSTANCE; - - public Socks5CmdRequestDecoder() { - 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 = Socks5CmdType.valueOf(byteBuf.readByte()); - reserved = byteBuf.readByte(); - addressType = Socks5AddressType.valueOf(byteBuf.readByte()); - checkpoint(State.READ_CMD_ADDRESS); - } - case READ_CMD_ADDRESS: { - switch (addressType) { - case IPv4: { - host = Socks5CommonUtils.intToIp(byteBuf.readInt()); - port = byteBuf.readUnsignedShort(); - msg = new Socks5CmdRequest(cmdType, addressType, host, port); - break; - } - case DOMAIN: { - fieldLength = byteBuf.readByte(); - host = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII); - port = byteBuf.readUnsignedShort(); - msg = new Socks5CmdRequest(cmdType, addressType, host, port); - break; - } - case IPv6: { - if (actualReadableBytes() < 16) { - // Let it replay. - byteBuf.readBytes(16); - - // Should never reach here. - throw new Error(); - } - - byte[] byteArray = new byte[16]; - byteBuf.readBytes(byteArray); - - host = Socks5CommonUtils.ipv6toStr(byteArray); - port = byteBuf.readUnsignedShort(); - msg = new Socks5CmdRequest(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/Socks5CmdResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponse.java deleted file mode 100755 index b464c59701..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponse.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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 Socks5CmdRequest - * @see Socks5CmdResponseDecoder - */ -public final class Socks5CmdResponse extends Socks5Response { - private final Socks5CmdStatus cmdStatus; - - private final Socks5AddressType 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 Socks5CmdResponse(Socks5CmdStatus cmdStatus, Socks5AddressType 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 Socks5CmdResponse(Socks5CmdStatus cmdStatus, Socks5AddressType addressType, String host, int port) { - super(Socks5ResponseType.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 Socks5CmdStatus} of this {@link Socks5CmdResponse} - * - * @return The {@link Socks5CmdStatus} of this {@link Socks5CmdResponse} - */ - public Socks5CmdStatus cmdStatus() { - return cmdStatus; - } - - /** - * Returns the {@link Socks5AddressType} of this {@link Socks5CmdResponse} - * - * @return The {@link Socks5AddressType} of this {@link Socks5CmdResponse} - */ - public Socks5AddressType addressType() { - return addressType; - } - - /** - * Returns host that is used as a parameter in {@link Socks5CmdType}. - * 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 Socks5CmdType} - * 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 Socks5CmdType}. - * 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 Socks5CmdType} - */ - public int port() { - return port; - } - - @Override - 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/Socks5CmdResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseDecoder.java deleted file mode 100755 index e612197afb..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseDecoder.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * 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.Socks5CmdResponseDecoder.State; -import io.netty.util.CharsetUtil; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks5CmdResponse}. - * Before returning SocksResponse decoder removes itself from pipeline. - */ -public class Socks5CmdResponseDecoder extends ReplayingDecoder { - private SocksProtocolVersion version; - private int fieldLength; - private Socks5CmdStatus cmdStatus; - private Socks5AddressType addressType; - private String host; - private int port; - private Socks5Response msg = UnknownSocks5Response.INSTANCE; - - public Socks5CmdResponseDecoder() { - 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 = Socks5CmdStatus.valueOf(byteBuf.readByte()); - byteBuf.skipBytes(1); // reserved - addressType = Socks5AddressType.valueOf(byteBuf.readByte()); - checkpoint(State.READ_CMD_ADDRESS); - } - case READ_CMD_ADDRESS: { - switch (addressType) { - case IPv4: { - host = Socks5CommonUtils.intToIp(byteBuf.readInt()); - port = byteBuf.readUnsignedShort(); - msg = new Socks5CmdResponse(cmdStatus, addressType, host, port); - break; - } - case DOMAIN: { - fieldLength = byteBuf.readByte(); - host = byteBuf.readBytes(fieldLength).toString(CharsetUtil.US_ASCII); - port = byteBuf.readUnsignedShort(); - msg = new Socks5CmdResponse(cmdStatus, addressType, host, port); - break; - } - case IPv6: { - host = Socks5CommonUtils.ipv6toStr(byteBuf.readBytes(16).array()); - port = byteBuf.readUnsignedShort(); - msg = new Socks5CmdResponse(cmdStatus, addressType, host, port); - break; - } - case UNKNOWN: - break; - } - } - } - 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/Socks5CmdStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdStatus.java deleted file mode 100755 index 201e7624bf..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdStatus.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * 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 Socks5CmdStatus { - 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; - - Socks5CmdStatus(byte b) { - this.b = b; - } - - public static Socks5CmdStatus valueOf(byte b) { - for (Socks5CmdStatus 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/Socks5CmdType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdType.java deleted file mode 100755 index e63850a55b..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CmdType.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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 Socks5CmdType { - CONNECT((byte) 0x01), - BIND((byte) 0x02), - UDP((byte) 0x03), - UNKNOWN((byte) 0xff); - - private final byte b; - - Socks5CmdType(byte b) { - this.b = b; - } - - public static Socks5CmdType valueOf(byte b) { - for (Socks5CmdType 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/UnknownSocks5Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequest.java similarity index 53% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocks5Request.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequest.java index b3e3bb8770..c2148682c9 100755 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocks5Request.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequest.java @@ -15,25 +15,29 @@ */ package io.netty.handler.codec.socksx.v5; -import io.netty.buffer.ByteBuf; - /** - * An unknown socks request. - * - * @see Socks5InitRequestDecoder - * @see Socks5AuthRequestDecoder - * @see Socks5CmdRequestDecoder + * A SOCKS5 request detail message, as defined in + * the section 4, RFC1928. */ -public final class UnknownSocks5Request extends Socks5Request { +public interface Socks5CommandRequest extends Socks5Message { - public static final UnknownSocks5Request INSTANCE = new UnknownSocks5Request(); + /** + * Returns the type of this request. + */ + Socks5CommandType type(); - private UnknownSocks5Request() { - super(Socks5RequestType.UNKNOWN); - } + /** + * Returns the type of the {@code DST.ADDR} field of this request. + */ + Socks5AddressType dstAddrType(); - @Override - void encodeAsByteBuf(ByteBuf byteBuf) { - // NOOP - } + /** + * Returns the {@code DST.ADDR} field of this request. + */ + String dstAddr(); + + /** + * Returns the {@code DST.PORT} field of this request. + */ + int dstPort(); } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequestDecoder.java new file mode 100644 index 0000000000..1b57288c37 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequestDecoder.java @@ -0,0 +1,107 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksVersion; +import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder.State; + +import java.util.List; + +/** + * Decodes a single {@link Socks5CommandRequest} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove or replace this decoder later. On failed decode, this decoder will + * discard the received data, so that other handler closes the connection later. + */ +public class Socks5CommandRequestDecoder extends ReplayingDecoder { + + enum State { + INIT, + SUCCESS, + FAILURE + } + + private final Socks5AddressDecoder addressDecoder; + + public Socks5CommandRequestDecoder() { + this(Socks5AddressDecoder.DEFAULT); + } + + public Socks5CommandRequestDecoder(Socks5AddressDecoder addressDecoder) { + super(State.INIT); + if (addressDecoder == null) { + throw new NullPointerException("addressDecoder"); + } + + this.addressDecoder = addressDecoder; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case INIT: { + final byte version = in.readByte(); + if (version != SocksVersion.SOCKS5.byteValue()) { + throw new DecoderException( + "unsupported version: " + version + " (expected: " + SocksVersion.SOCKS5.byteValue() + ')'); + } + + final Socks5CommandType type = Socks5CommandType.valueOf(in.readByte()); + in.skipBytes(1); // RSV + final Socks5AddressType dstAddrType = Socks5AddressType.valueOf(in.readByte()); + final String dstAddr = addressDecoder.decodeAddress(dstAddrType, in); + final int dstPort = in.readUnsignedShort(); + + out.add(new DefaultSocks5CommandRequest(type, dstAddrType, dstAddr, dstPort)); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + checkpoint(State.FAILURE); + + Socks5Message m = new DefaultSocks5CommandRequest( + Socks5CommandType.CONNECT, Socks5AddressType.IPv4, "0.0.0.0", 1); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocks5Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponse.java similarity index 52% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocks5Response.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponse.java index 2bdbdae188..823531b910 100755 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/UnknownSocks5Response.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponse.java @@ -15,25 +15,29 @@ */ package io.netty.handler.codec.socksx.v5; -import io.netty.buffer.ByteBuf; - /** - * An unknown socks response. - * - * @see Socks5InitResponseDecoder - * @see Socks5AuthResponseDecoder - * @see Socks5CmdResponseDecoder + * A response to a SOCKS5 request detail message, as defined in + * the section 6, RFC1928. */ -public final class UnknownSocks5Response extends Socks5Response { +public interface Socks5CommandResponse extends Socks5Message { - public static final UnknownSocks5Response INSTANCE = new UnknownSocks5Response(); + /** + * Returns the status of this response. + */ + Socks5CommandStatus status(); - private UnknownSocks5Response() { - super(Socks5ResponseType.UNKNOWN); - } + /** + * Returns the address type of the {@code BND.ADDR} field of this response. + */ + Socks5AddressType bndAddrType(); - @Override - void encodeAsByteBuf(ByteBuf byteBuf) { - // NOOP - } + /** + * Returns the {@code BND.ADDR} field of this response. + */ + String bndAddr(); + + /** + * Returns the {@code BND.PORT} field of this response. + */ + int bndPort(); } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponseDecoder.java new file mode 100644 index 0000000000..b9f36553fe --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponseDecoder.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksVersion; +import io.netty.handler.codec.socksx.v5.Socks5CommandResponseDecoder.State; + +import java.util.List; + +/** + * Decodes a single {@link Socks5CommandResponse} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove or replace this decoder later. On failed decode, this decoder will + * discard the received data, so that other handler closes the connection later. + */ +public class Socks5CommandResponseDecoder extends ReplayingDecoder { + + enum State { + INIT, + SUCCESS, + FAILURE + } + + private final Socks5AddressDecoder addressDecoder; + + public Socks5CommandResponseDecoder() { + this(Socks5AddressDecoder.DEFAULT); + } + + public Socks5CommandResponseDecoder(Socks5AddressDecoder addressDecoder) { + super(State.INIT); + if (addressDecoder == null) { + throw new NullPointerException("addressDecoder"); + } + + this.addressDecoder = addressDecoder; + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case INIT: { + final byte version = in.readByte(); + if (version != SocksVersion.SOCKS5.byteValue()) { + throw new DecoderException( + "unsupported version: " + version + " (expected: " + SocksVersion.SOCKS5.byteValue() + ')'); + } + final Socks5CommandStatus status = Socks5CommandStatus.valueOf(in.readByte()); + in.skipBytes(1); // Reserved + final Socks5AddressType addrType = Socks5AddressType.valueOf(in.readByte()); + final String addr = addressDecoder.decodeAddress(addrType, in); + final int port = in.readUnsignedShort(); + + out.add(new DefaultSocks5CommandResponse(status, addrType, addr, port)); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + checkpoint(State.FAILURE); + + Socks5Message m = new DefaultSocks5CommandResponse( + Socks5CommandStatus.FAILURE, Socks5AddressType.IPv4, null, 0); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandStatus.java new file mode 100755 index 0000000000..7973c043a0 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandStatus.java @@ -0,0 +1,111 @@ +/* + * 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; + +/** + * The status of {@link Socks5CommandResponse}. + */ +public class Socks5CommandStatus implements Comparable { + + public static final Socks5CommandStatus SUCCESS = new Socks5CommandStatus(0x00, "SUCCESS"); + public static final Socks5CommandStatus FAILURE = new Socks5CommandStatus(0x01, "FAILURE"); + public static final Socks5CommandStatus FORBIDDEN = new Socks5CommandStatus(0x02, "FORBIDDEN"); + public static final Socks5CommandStatus NETWORK_UNREACHABLE = new Socks5CommandStatus(0x03, "NETWORK_UNREACHABLE"); + public static final Socks5CommandStatus HOST_UNREACHABLE = new Socks5CommandStatus(0x04, "HOST_UNREACHABLE"); + public static final Socks5CommandStatus CONNECTION_REFUSED = new Socks5CommandStatus(0x05, "CONNECTION_REFUSED"); + public static final Socks5CommandStatus TTL_EXPIRED = new Socks5CommandStatus(0x06, "TTL_EXPIRED"); + public static final Socks5CommandStatus COMMAND_UNSUPPORTED = new Socks5CommandStatus(0x07, "COMMAND_UNSUPPORTED"); + public static final Socks5CommandStatus ADDRESS_UNSUPPORTED = new Socks5CommandStatus(0x08, "ADDRESS_UNSUPPORTED"); + + public static Socks5CommandStatus valueOf(byte b) { + switch (b) { + case 0x00: + return SUCCESS; + case 0x01: + return FAILURE; + case 0x02: + return FORBIDDEN; + case 0x03: + return NETWORK_UNREACHABLE; + case 0x04: + return HOST_UNREACHABLE; + case 0x05: + return CONNECTION_REFUSED; + case 0x06: + return TTL_EXPIRED; + case 0x07: + return COMMAND_UNSUPPORTED; + case 0x08: + return ADDRESS_UNSUPPORTED; + } + + return new Socks5CommandStatus(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks5CommandStatus(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks5CommandStatus(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + this.byteValue = (byte) byteValue; + this.name = name; + } + + public byte byteValue() { + return byteValue; + } + + public boolean isSuccess() { + return byteValue == 0; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks5CommandStatus)) { + return false; + } + + return byteValue == ((Socks5CommandStatus) obj).byteValue; + } + + @Override + public int compareTo(Socks5CommandStatus o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandType.java new file mode 100755 index 0000000000..ecc4ca3e43 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommandType.java @@ -0,0 +1,89 @@ +/* + * 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; + +/** + * The type of {@link Socks5CommandRequest}. + */ +public class Socks5CommandType implements Comparable { + + public static final Socks5CommandType CONNECT = new Socks5CommandType(0x01, "CONNECT"); + public static final Socks5CommandType BIND = new Socks5CommandType(0x02, "BIND"); + public static final Socks5CommandType UDP_ASSOCIATE = new Socks5CommandType(0x03, "UDP_ASSOCIATE"); + + public static Socks5CommandType valueOf(byte b) { + switch (b) { + case 0x01: + return CONNECT; + case 0x02: + return BIND; + case 0x03: + return UDP_ASSOCIATE; + } + + return new Socks5CommandType(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks5CommandType(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks5CommandType(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + this.byteValue = (byte) byteValue; + this.name = name; + } + + public byte byteValue() { + return byteValue; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks5CommandType)) { + return false; + } + + return byteValue == ((Socks5CommandType) obj).byteValue; + } + + @Override + public int compareTo(Socks5CommandType o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommonUtils.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommonUtils.java deleted file mode 100755 index 82e8baf5f7..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5CommonUtils.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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; - -final class Socks5CommonUtils { - - 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 Socks5CommonUtils() { - // 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/Socks5InitRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitRequest.java deleted file mode 100755 index 1e8adeafb1..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitRequest.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 Socks5InitResponse - * @see Socks5InitRequestDecoder - */ -public final class Socks5InitRequest extends Socks5Request { - private final List authSchemes; - - public Socks5InitRequest(List authSchemes) { - super(Socks5RequestType.INIT); - if (authSchemes == null) { - throw new NullPointerException("authSchemes"); - } - this.authSchemes = authSchemes; - } - - /** - * Returns the List<{@link Socks5AuthScheme}> of this {@link Socks5InitRequest} - * - * @return The List<{@link Socks5AuthScheme}> of this {@link Socks5InitRequest} - */ - public List authSchemes() { - return Collections.unmodifiableList(authSchemes); - } - - @Override - void encodeAsByteBuf(ByteBuf byteBuf) { - byteBuf.writeByte(protocolVersion().byteValue()); - byteBuf.writeByte(authSchemes.size()); - for (Socks5AuthScheme authScheme : authSchemes) { - byteBuf.writeByte(authScheme.byteValue()); - } - } -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitRequestDecoder.java deleted file mode 100755 index 8ed617c19f..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitRequestDecoder.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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.Socks5InitRequestDecoder.State; - -import java.util.ArrayList; -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks5InitRequest}. - * Before returning SocksRequest decoder removes itself from pipeline. - */ -public class Socks5InitRequestDecoder extends ReplayingDecoder { - private final List authSchemes = new ArrayList(); - private SocksProtocolVersion version; - private byte authSchemeNum; - private Socks5Request msg = UnknownSocks5Request.INSTANCE; - - public Socks5InitRequestDecoder() { - 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(Socks5AuthScheme.valueOf(byteBuf.readByte())); - } - msg = new Socks5InitRequest(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/Socks5InitResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitResponse.java deleted file mode 100755 index dd4be05c32..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitResponse.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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 Socks5InitRequest - * @see Socks5InitResponseDecoder - */ -public final class Socks5InitResponse extends Socks5Response { - private final Socks5AuthScheme authScheme; - - public Socks5InitResponse(Socks5AuthScheme authScheme) { - super(Socks5ResponseType.INIT); - if (authScheme == null) { - throw new NullPointerException("authScheme"); - } - this.authScheme = authScheme; - } - - /** - * Returns the {@link Socks5AuthScheme} of this {@link Socks5InitResponse} - * - * @return The {@link Socks5AuthScheme} of this {@link Socks5InitResponse} - */ - public Socks5AuthScheme authScheme() { - return authScheme; - } - - @Override - 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/Socks5InitResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitResponseDecoder.java deleted file mode 100755 index bf4cbeff4a..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitResponseDecoder.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * 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.Socks5InitResponseDecoder.State; - -import java.util.List; - -/** - * Decodes {@link ByteBuf}s into {@link Socks5InitResponse}. - * Before returning SocksResponse decoder removes itself from pipeline. - */ -public class Socks5InitResponseDecoder extends ReplayingDecoder { - private SocksProtocolVersion version; - private Socks5AuthScheme authScheme; - - private Socks5Response msg = UnknownSocks5Response.INSTANCE; - - public Socks5InitResponseDecoder() { - 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 = Socks5AuthScheme.valueOf(byteBuf.readByte()); - msg = new Socks5InitResponse(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/Socks5InitialRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialRequest.java new file mode 100755 index 0000000000..866402cd19 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialRequest.java @@ -0,0 +1,29 @@ +/* + * 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 java.util.List; + +/** + * An initial SOCKS5 authentication method selection request, as defined in + * the section 3, RFC1928. + */ +public interface Socks5InitialRequest extends Socks5Message { + /** + * Returns the list of desired authentication methods. + */ + List authMethods(); +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialRequestDecoder.java new file mode 100644 index 0000000000..17c043fae5 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialRequestDecoder.java @@ -0,0 +1,99 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksVersion; +import io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder.State; + +import java.util.List; + +/** + * Decodes a single {@link Socks5InitialRequest} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove or replace this decoder later. On failed decode, this decoder will + * discard the received data, so that other handler closes the connection later. + */ +public class Socks5InitialRequestDecoder extends ReplayingDecoder { + + enum State { + INIT, + SUCCESS, + FAILURE + } + + public Socks5InitialRequestDecoder() { + super(State.INIT); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case INIT: { + final byte version = in.readByte(); + if (version != SocksVersion.SOCKS5.byteValue()) { + throw new DecoderException( + "unsupported version: " + version + " (expected: " + SocksVersion.SOCKS5.byteValue() + ')'); + } + + final int authMethodCnt = in.readUnsignedByte(); + if (actualReadableBytes() < authMethodCnt) { + break; + } + + final Socks5AuthMethod[] authMethods = new Socks5AuthMethod[authMethodCnt]; + for (int i = 0; i < authMethodCnt; i++) { + authMethods[i] = Socks5AuthMethod.valueOf(in.readByte()); + } + + out.add(new DefaultSocks5InitialRequest(authMethods)); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + checkpoint(State.FAILURE); + + Socks5Message m = new DefaultSocks5InitialRequest(Socks5AuthMethod.NO_AUTH); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialResponse.java new file mode 100755 index 0000000000..df193f61ab --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialResponse.java @@ -0,0 +1,28 @@ +/* + * 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; + +/** + * An initial SOCKS5 authentication method selection request, as defined in + * the section 3, RFC1928. + */ +public interface Socks5InitialResponse extends Socks5Message { + + /** + * Returns the {@code METHOD} field of this response. + */ + Socks5AuthMethod authMethod(); +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialResponseDecoder.java new file mode 100644 index 0000000000..125ce2ebd2 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5InitialResponseDecoder.java @@ -0,0 +1,90 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.SocksVersion; +import io.netty.handler.codec.socksx.v5.Socks5InitialResponseDecoder.State; + +import java.util.List; + +/** + * Decodes a single {@link Socks5InitialResponse} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove or replace this decoder later. On failed decode, this decoder will + * discard the received data, so that other handler closes the connection later. + */ +public class Socks5InitialResponseDecoder extends ReplayingDecoder { + + enum State { + INIT, + SUCCESS, + FAILURE + } + + public Socks5InitialResponseDecoder() { + super(State.INIT); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case INIT: { + final byte version = in.readByte(); + if (version != SocksVersion.SOCKS5.byteValue()) { + throw new DecoderException( + "unsupported version: " + version + " (expected: " + SocksVersion.SOCKS5.byteValue() + ')'); + } + + final Socks5AuthMethod authMethod = Socks5AuthMethod.valueOf(in.readByte()); + out.add(new DefaultSocks5InitialResponse(authMethod)); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + checkpoint(State.FAILURE); + + Socks5Message m = new DefaultSocks5InitialResponse(Socks5AuthMethod.UNACCEPTED); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5RequestType.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Message.java old mode 100755 new mode 100644 similarity index 74% rename from codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5RequestType.java rename to codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Message.java index 28194ee682..77a0a21df7 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5RequestType.java +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Message.java @@ -1,5 +1,5 @@ /* - * Copyright 2013 The Netty Project + * 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 @@ -16,12 +16,11 @@ package io.netty.handler.codec.socksx.v5; +import io.netty.handler.codec.socksx.SocksMessage; + /** - * Type of socks request + * A tag interface that all SOCKS5 protocol messages implement. */ -public enum Socks5RequestType { - INIT, - AUTH, - CMD, - UNKNOWN +public interface Socks5Message extends SocksMessage { + // Tag interface } diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5MessageEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5MessageEncoder.java deleted file mode 100755 index 00d02c157f..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5MessageEncoder.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * 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.ChannelHandler; -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.MessageToByteEncoder; -import io.netty.handler.codec.socksx.SocksMessage; -import io.netty.handler.codec.socksx.SocksProtocolVersion; - -/** - * Encodes a {@link Socks5Request} and {@link Socks5Response} into a {@link ByteBuf}. - */ -@ChannelHandler.Sharable -public final class Socks5MessageEncoder extends MessageToByteEncoder { - - public static final Socks5MessageEncoder INSTANCE = new Socks5MessageEncoder(); - - private Socks5MessageEncoder() { } - - @Override - public boolean acceptOutboundMessage(Object msg) throws Exception { - return super.acceptOutboundMessage(msg) && - ((SocksMessage) msg).protocolVersion() == SocksProtocolVersion.SOCKS5; - } - - @Override - protected void encode(ChannelHandlerContext ctx, SocksMessage msg, ByteBuf out) throws Exception { - if (msg instanceof Socks5Response) { - ((Socks5Response) msg).encodeAsByteBuf(out); - } else if (msg instanceof Socks5Request) { - ((Socks5Request) msg).encodeAsByteBuf(out); - } else { - // Should not reach here. - throw new Error(); - } - } -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequest.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequest.java new file mode 100755 index 0000000000..fece40b57e --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequest.java @@ -0,0 +1,33 @@ +/* + * 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; + +/** + * A SOCKS5 subnegotiation request for username-password authentication, as defined in + * the section 2, RFC1929. + */ +public interface Socks5PasswordAuthRequest extends Socks5Message { + + /** + * Returns the username of this request. + */ + String username(); + + /** + * Returns the password of this request. + */ + String password(); +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequestDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequestDecoder.java new file mode 100644 index 0000000000..db0f684c96 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequestDecoder.java @@ -0,0 +1,97 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequestDecoder.State; +import io.netty.util.CharsetUtil; + +import java.util.List; + +/** + * Decodes a single {@link Socks5PasswordAuthRequest} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove or replace this decoder later. On failed decode, this decoder will + * discard the received data, so that other handler closes the connection later. + */ +public class Socks5PasswordAuthRequestDecoder extends ReplayingDecoder { + + enum State { + INIT, + SUCCESS, + FAILURE + } + + public Socks5PasswordAuthRequestDecoder() { + super(State.INIT); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case INIT: { + final int startOffset = in.readerIndex(); + final byte version = in.getByte(startOffset); + if (version != 1) { + throw new DecoderException("unsupported subnegotiation version: " + version + " (expected: 1)"); + } + + final int usernameLength = in.getUnsignedByte(startOffset + 1); + final int passwordLength = in.getUnsignedByte(startOffset + 2 + usernameLength); + final int totalLength = usernameLength + passwordLength + 3; + + in.skipBytes(totalLength); + out.add(new DefaultSocks5PasswordAuthRequest( + in.toString(startOffset + 2, usernameLength, CharsetUtil.US_ASCII), + in.toString(startOffset + 3 + usernameLength, passwordLength, CharsetUtil.US_ASCII))); + + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + checkpoint(State.FAILURE); + + Socks5Message m = new DefaultSocks5PasswordAuthRequest("", ""); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponse.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponse.java new file mode 100755 index 0000000000..e9e6a69c15 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponse.java @@ -0,0 +1,27 @@ +/* + * 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; + +/** + * A SOCKS5 subnegotiation response for username-password authentication, as defined in + * the section 2, RFC1929. + */ +public interface Socks5PasswordAuthResponse extends Socks5Message { + /** + * Returns the status of this response. + */ + Socks5PasswordAuthStatus status(); +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponseDecoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponseDecoder.java new file mode 100644 index 0000000000..a6386e59e9 --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponseDecoder.java @@ -0,0 +1,87 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.DecoderResult; +import io.netty.handler.codec.ReplayingDecoder; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponseDecoder.State; + +import java.util.List; + +/** + * Decodes a single {@link Socks5PasswordAuthResponse} from the inbound {@link ByteBuf}s. + * On successful decode, this decoder will forward the received data to the next handler, so that + * other handler can remove or replace this decoder later. On failed decode, this decoder will + * discard the received data, so that other handler closes the connection later. + */ +public class Socks5PasswordAuthResponseDecoder extends ReplayingDecoder { + + enum State { + INIT, + SUCCESS, + FAILURE + } + + public Socks5PasswordAuthResponseDecoder() { + super(State.INIT); + } + + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { + try { + switch (state()) { + case INIT: { + final byte version = in.readByte(); + if (version != 1) { + throw new DecoderException("unsupported subnegotiation version: " + version + " (expected: 1)"); + } + + out.add(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.valueOf(in.readByte()))); + checkpoint(State.SUCCESS); + } + case SUCCESS: { + int readableBytes = actualReadableBytes(); + if (readableBytes > 0) { + out.add(in.readSlice(readableBytes).retain()); + } + break; + } + case FAILURE: { + in.skipBytes(actualReadableBytes()); + break; + } + } + } catch (Exception e) { + fail(out, e); + } + } + + private void fail(List out, Throwable cause) { + if (!(cause instanceof DecoderException)) { + cause = new DecoderException(cause); + } + + checkpoint(State.FAILURE); + + Socks5Message m = new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.FAILURE); + m.setDecoderResult(DecoderResult.failure(cause)); + out.add(m); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthStatus.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthStatus.java new file mode 100755 index 0000000000..e7ea4a1eda --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthStatus.java @@ -0,0 +1,90 @@ +/* + * 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; + +/** + * The status of {@link Socks5PasswordAuthResponse}. + */ +public class Socks5PasswordAuthStatus implements Comparable { + + public static final Socks5PasswordAuthStatus SUCCESS = new Socks5PasswordAuthStatus(0x00, "SUCCESS"); + public static final Socks5PasswordAuthStatus FAILURE = new Socks5PasswordAuthStatus(0xFF, "FAILURE"); + + public static Socks5PasswordAuthStatus valueOf(byte b) { + switch (b) { + case 0x00: + return SUCCESS; + case (byte) 0xFF: + return FAILURE; + } + + return new Socks5PasswordAuthStatus(b); + } + + private final byte byteValue; + private final String name; + private String text; + + public Socks5PasswordAuthStatus(int byteValue) { + this(byteValue, "UNKNOWN"); + } + + public Socks5PasswordAuthStatus(int byteValue, String name) { + if (name == null) { + throw new NullPointerException("name"); + } + + this.byteValue = (byte) byteValue; + this.name = name; + } + + public byte byteValue() { + return byteValue; + } + + public boolean isSuccess() { + return byteValue == 0; + } + + @Override + public int hashCode() { + return byteValue; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Socks5PasswordAuthStatus)) { + return false; + } + + return byteValue == ((Socks5PasswordAuthStatus) obj).byteValue; + } + + @Override + public int compareTo(Socks5PasswordAuthStatus o) { + return byteValue - o.byteValue; + } + + @Override + public String toString() { + String text = this.text; + if (text == null) { + this.text = text = name + '(' + (byteValue & 0xFF) + ')'; + } + return text; + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Request.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Request.java deleted file mode 100755 index d416a05895..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Request.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.handler.codec.socks.SocksMessage; -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 Socks5InitRequest}, - * {@link Socks5AuthRequest}, - * {@link Socks5CmdRequest} and - * {@link UnknownSocks5Request}. - * - * @see Socks5InitRequest - * @see Socks5AuthRequest - * @see Socks5CmdRequest - * @see UnknownSocks5Request - */ -public abstract class Socks5Request extends SocksRequest { - private final Socks5RequestType requestType; - - protected Socks5Request(Socks5RequestType requestType) { - super(SocksProtocolVersion.SOCKS5); - if (requestType == null) { - throw new NullPointerException("requestType"); - } - this.requestType = requestType; - } - - /** - * Returns socks request type - * - * @return socks request type - */ - public Socks5RequestType requestType() { - return requestType; - } - - /** - * We could have defined this method in {@link SocksMessage} as a protected method, but we did not, - * because we do not want to expose this method to users. - */ - abstract void encodeAsByteBuf(ByteBuf byteBuf); -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Response.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Response.java deleted file mode 100755 index 4f570f2dfa..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5Response.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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.handler.codec.socks.SocksMessage; -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 Socks5InitResponse}, - * {@link Socks5AuthResponse}, - * {@link Socks5CmdResponse} - * and {@link UnknownSocks5Response}. - * - * @see Socks5InitResponse - * @see Socks5AuthResponse - * @see Socks5CmdResponse - * @see UnknownSocks5Response - */ -public abstract class Socks5Response extends SocksResponse { - private final Socks5ResponseType responseType; - - protected Socks5Response(Socks5ResponseType responseType) { - super(SocksProtocolVersion.SOCKS5); - if (responseType == null) { - throw new NullPointerException("responseType"); - } - this.responseType = responseType; - } - - /** - * Returns socks response type - * - * @return socks response type - */ - public Socks5ResponseType responseType() { - return responseType; - } - - /** - * We could have defined this method in {@link SocksMessage} as a protected method, but we did not, - * because we do not want to expose this method to users. - */ - abstract void encodeAsByteBuf(ByteBuf byteBuf); -} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ServerEncoder.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ServerEncoder.java new file mode 100644 index 0000000000..d24e559b6d --- /dev/null +++ b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5ServerEncoder.java @@ -0,0 +1,95 @@ +/* + * 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. + */ + +package io.netty.handler.codec.socksx.v5; + +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandler.Sharable; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.EncoderException; +import io.netty.handler.codec.MessageToByteEncoder; +import io.netty.util.internal.StringUtil; + +/** + * Encodes a server-side {@link Socks5Message} into a {@link ByteBuf}. + */ +@Sharable +public class Socks5ServerEncoder extends MessageToByteEncoder { + + public static final Socks5ServerEncoder DEFAULT = new Socks5ServerEncoder(Socks5AddressEncoder.DEFAULT); + + private final Socks5AddressEncoder addressEncoder; + + /** + * Creates a new instance with the default {@link Socks5AddressEncoder}. + */ + protected Socks5ServerEncoder() { + this(Socks5AddressEncoder.DEFAULT); + } + + /** + * Creates a new instance with the specified {@link Socks5AddressEncoder}. + */ + public Socks5ServerEncoder(Socks5AddressEncoder addressEncoder) { + if (addressEncoder == null) { + throw new NullPointerException("addressEncoder"); + } + + this.addressEncoder = addressEncoder; + } + + /** + * Returns the {@link Socks5AddressEncoder} of this encoder. + */ + protected final Socks5AddressEncoder addressEncoder() { + return addressEncoder; + } + + @Override + protected void encode(ChannelHandlerContext ctx, Socks5Message msg, ByteBuf out) throws Exception { + if (msg instanceof Socks5InitialResponse) { + encodeAuthMethodResponse((Socks5InitialResponse) msg, out); + } else if (msg instanceof Socks5PasswordAuthResponse) { + encodePasswordAuthResponse((Socks5PasswordAuthResponse) msg, out); + } else if (msg instanceof Socks5CommandResponse) { + encodeCommandResponse((Socks5CommandResponse) msg, out); + } else { + throw new EncoderException("unsupported message type: " + StringUtil.simpleClassName(msg)); + } + } + + private static void encodeAuthMethodResponse(Socks5InitialResponse msg, ByteBuf out) { + out.writeByte(msg.version().byteValue()); + out.writeByte(msg.authMethod().byteValue()); + } + + private static void encodePasswordAuthResponse(Socks5PasswordAuthResponse msg, ByteBuf out) { + out.writeByte(0x01); + out.writeByte(msg.status().byteValue()); + } + + private void encodeCommandResponse(Socks5CommandResponse msg, ByteBuf out) throws Exception { + out.writeByte(msg.version().byteValue()); + out.writeByte(msg.status().byteValue()); + out.writeByte(0x00); + + final Socks5AddressType bndAddrType = msg.bndAddrType(); + out.writeByte(bndAddrType.byteValue()); + addressEncoder.encodeAddress(bndAddrType, msg.bndAddr(), out); + + out.writeShort(msg.bndPort()); + } +} diff --git a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5SubnegotiationVersion.java b/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5SubnegotiationVersion.java deleted file mode 100755 index e37150e3bf..0000000000 --- a/codec-socks/src/main/java/io/netty/handler/codec/socksx/v5/Socks5SubnegotiationVersion.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 Socks5SubnegotiationVersion { - AUTH_PASSWORD((byte) 0x01), - UNKNOWN((byte) 0xff); - - private final byte b; - - Socks5SubnegotiationVersion(byte b) { - this.b = b; - } - - public static Socks5SubnegotiationVersion valueOf(byte b) { - for (Socks5SubnegotiationVersion code : values()) { - if (code.b == b) { - return code; - } - } - return UNKNOWN; - } - - public byte byteValue() { - return b; - } -} - diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4ClientDecoderTest.java similarity index 61% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponseDecoderTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4ClientDecoderTest.java index 84d4b42621..1b99fd342b 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CmdResponseDecoderTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4ClientDecoderTest.java @@ -22,23 +22,21 @@ import org.slf4j.LoggerFactory; import static org.junit.Assert.*; -public class Socks4CmdResponseDecoderTest { - private static final Logger logger = LoggerFactory.getLogger(Socks4CmdResponseDecoderTest.class); +public class Socks4ClientDecoderTest { + private static final Logger logger = LoggerFactory.getLogger(Socks4ClientDecoderTest.class); - private static void testSocksCmdResponseDecoderWithDifferentParams( - Socks4CmdStatus cmdStatus, String host, int port) { + private static void test(Socks4CommandStatus cmdStatus, String dstAddr, int dstPort) { logger.debug("Testing cmdStatus: " + cmdStatus); - Socks4Response msg = new Socks4CmdResponse(cmdStatus, host, port); - Socks4CmdResponseDecoder decoder = new Socks4CmdResponseDecoder(); - EmbeddedChannel embedder = new EmbeddedChannel(decoder); + Socks4CommandResponse msg = new DefaultSocks4CommandResponse(cmdStatus, dstAddr, dstPort); + EmbeddedChannel embedder = new EmbeddedChannel(new Socks4ClientDecoder()); Socks4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); msg = embedder.readInbound(); - assertEquals(((Socks4CmdResponse) msg).cmdStatus(), cmdStatus); - if (host != null) { - assertEquals(((Socks4CmdResponse) msg).host(), host); + assertEquals(msg.status(), cmdStatus); + if (dstAddr != null) { + assertEquals(msg.dstAddr(), dstAddr); } - assertEquals(((Socks4CmdResponse) msg).port(), port); + assertEquals(msg.dstPort(), dstPort); assertNull(embedder.readInbound()); } @@ -47,8 +45,9 @@ public class Socks4CmdResponseDecoderTest { */ @Test public void testSocksCmdResponseDecoder() { - for (Socks4CmdStatus cmdStatus : Socks4CmdStatus.values()) { - testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, null, 0); - } + test(Socks4CommandStatus.IDENTD_AUTH_FAILURE, null, 0); + test(Socks4CommandStatus.IDENTD_UNREACHABLE, null, 0); + test(Socks4CommandStatus.REJECTED_OR_FAILED, null, 0); + test(Socks4CommandStatus.SUCCESS, null, 0); } } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CommonTestUtils.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CommonTestUtils.java index 9d347be4d4..93a739ceb4 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CommonTestUtils.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CommonTestUtils.java @@ -15,8 +15,6 @@ */ package io.netty.handler.codec.socksx.v4; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; final class Socks4CommonTestUtils { @@ -27,15 +25,15 @@ final class Socks4CommonTestUtils { //NOOP } - public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks4Request msg) { - ByteBuf buf = Unpooled.buffer(); - msg.encodeAsByteBuf(buf); - embedder.writeInbound(buf); - } - - public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks4Response msg) { - ByteBuf buf = Unpooled.buffer(); - msg.encodeAsByteBuf(buf); - embedder.writeInbound(buf); + public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks4Message msg) { + EmbeddedChannel out; + if (msg instanceof Socks4CommandRequest) { + out = new EmbeddedChannel(Socks4ClientEncoder.INSTANCE); + } else { + out = new EmbeddedChannel(Socks4ServerEncoder.INSTANCE); + } + out.writeOutbound(msg); + embedder.writeInbound(out.readOutbound()); + out.finish(); } } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4ServerDecoderTest.java similarity index 53% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequestDecoderTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4ServerDecoderTest.java index cde2101f18..6e175b48ee 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4CmdRequestDecoderTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v4/Socks4ServerDecoderTest.java @@ -20,40 +20,41 @@ import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import org.junit.Test; +import java.util.Arrays; + import static org.junit.Assert.*; -public class Socks4CmdRequestDecoderTest { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks4CmdRequestDecoderTest.class); +public class Socks4ServerDecoderTest { + private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks4ServerDecoderTest.class); - private static void testSocksV4CmdRequestDecoderWithDifferentParams(String userId, - Socks4CmdType cmdType, - String host, - int port) { - logger.debug("Testing cmdType: " + cmdType + " userId: " + userId + " host: " + host + - " port: " + port); - Socks4CmdRequest msg = new Socks4CmdRequest(userId, cmdType, host, port); - Socks4CmdRequestDecoder decoder = new Socks4CmdRequestDecoder(); - EmbeddedChannel embedder = new EmbeddedChannel(decoder); + private static void test(String userId, Socks4CommandType type, String dstAddr, int dstPort) { + logger.debug( + "Testing type: " + type + " dstAddr: " + dstAddr + " dstPort: " + dstPort + + " userId: " + userId); + + Socks4CommandRequest msg = new DefaultSocks4CommandRequest(type, dstAddr, dstPort, userId); + EmbeddedChannel embedder = new EmbeddedChannel(new Socks4ServerDecoder()); Socks4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); - Object obj = embedder.readInbound(); - msg = (Socks4CmdRequest) obj; - assertSame(msg.cmdType(), cmdType); + msg = embedder.readInbound(); + assertSame(msg.type(), type); + assertEquals(msg.dstAddr(), dstAddr); + assertEquals(msg.dstPort(), dstPort); 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", }; + String[] hosts = { "127.0.0.1", }; + String[] userIds = { "test", }; int[] ports = {1, 32769, 65535}; - for (Socks4CmdType cmdType : Socks4CmdType.values()) { + + for (Socks4CommandType cmdType : Arrays.asList(Socks4CommandType.BIND, + Socks4CommandType.CONNECT)) { for (String userId : userIds) { for (String host : hosts) { for (int port : ports) { - testSocksV4CmdRequestDecoderWithDifferentParams(userId, cmdType, host, port); + test(userId, cmdType, host, port); } } } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandRequestTest.java similarity index 77% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandRequestTest.java index 2869458fa9..e4dc2dade7 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandRequestTest.java @@ -19,23 +19,23 @@ import org.junit.Test; import static org.junit.Assert.*; -public class Socks5CmdRequestTest { +public class DefaultSocks5CommandRequestTest { @Test public void testConstructorParamsAreNotNull() { try { - new Socks5CmdRequest(null, Socks5AddressType.UNKNOWN, "", 1); + new DefaultSocks5CommandRequest(null, Socks5AddressType.DOMAIN, "", 1); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } try { - new Socks5CmdRequest(Socks5CmdType.UNKNOWN, null, "", 1); + new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, null, "", 1); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } try { - new Socks5CmdRequest(Socks5CmdType.UNKNOWN, Socks5AddressType.UNKNOWN, null, 1); + new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, Socks5AddressType.DOMAIN, null, 1); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } @@ -44,7 +44,7 @@ public class Socks5CmdRequestTest { @Test public void testIPv4CorrectAddress() { try { - new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.IPv4, "54.54.1111.253", 1); + new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.IPv4, "54.54.1111.253", 1); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } @@ -53,7 +53,7 @@ public class Socks5CmdRequestTest { @Test public void testIPv6CorrectAddress() { try { - new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.IPv6, "xxx:xxx:xxx", 1); + new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.IPv6, "xxx:xxx:xxx", 1); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } @@ -62,7 +62,7 @@ public class Socks5CmdRequestTest { @Test public void testIDNNotExceeds255CharsLimit() { try { - new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.DOMAIN, + new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.DOMAIN, "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + @@ -75,14 +75,14 @@ public class Socks5CmdRequestTest { @Test public void testValidPortRange() { try { - new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.DOMAIN, + new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.DOMAIN, "παράδειγμα.δοκιμήπαράδει", 0); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { - new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.DOMAIN, + new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.DOMAIN, "παράδειγμα.δοκιμήπαράδει", 65536); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandResponseTest.java similarity index 67% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandResponseTest.java index 210f9c8008..79717b36c2 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5CommandResponseTest.java @@ -16,21 +16,20 @@ 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 Socks5CmdResponseTest { +public class DefaultSocks5CommandResponseTest { @Test public void testConstructorParamsAreNotNull() { try { - new Socks5CmdResponse(null, Socks5AddressType.UNKNOWN); + new DefaultSocks5CommandResponse(null, Socks5AddressType.DOMAIN); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } try { - new Socks5CmdResponse(Socks5CmdStatus.UNASSIGNED, null); + new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, null); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } @@ -41,12 +40,12 @@ public class Socks5CmdResponseTest { */ @Test public void testEmptyDomain() { - Socks5CmdResponse socks5CmdResponse = new Socks5CmdResponse( - Socks5CmdStatus.SUCCESS, Socks5AddressType.DOMAIN); - assertNull(socks5CmdResponse.host()); - assertEquals(0, socks5CmdResponse.port()); - ByteBuf buffer = Unpooled.buffer(20); - socks5CmdResponse.encodeAsByteBuf(buffer); + Socks5CommandResponse socks5CmdResponse = new DefaultSocks5CommandResponse( + Socks5CommandStatus.SUCCESS, Socks5AddressType.DOMAIN); + assertNull(socks5CmdResponse.bndAddr()); + assertEquals(0, socks5CmdResponse.bndPort()); + + ByteBuf buffer = Socks5CommonTestUtils.encodeServer(socks5CmdResponse); byte[] expected = { 0x05, // version 0x00, // success reply @@ -65,12 +64,12 @@ public class Socks5CmdResponseTest { */ @Test public void testIPv4Host() { - Socks5CmdResponse socks5CmdResponse = new Socks5CmdResponse( - Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0.1", 80); - assertEquals("127.0.0.1", socks5CmdResponse.host()); - assertEquals(80, socks5CmdResponse.port()); - ByteBuf buffer = Unpooled.buffer(20); - socks5CmdResponse.encodeAsByteBuf(buffer); + Socks5CommandResponse socks5CmdResponse = new DefaultSocks5CommandResponse( + Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0.1", 80); + assertEquals("127.0.0.1", socks5CmdResponse.bndAddr()); + assertEquals(80, socks5CmdResponse.bndPort()); + + ByteBuf buffer = Socks5CommonTestUtils.encodeServer(socks5CmdResponse); byte[] expected = { 0x05, // version 0x00, // success reply @@ -91,12 +90,12 @@ public class Socks5CmdResponseTest { */ @Test public void testEmptyBoundAddress() { - Socks5CmdResponse socks5CmdResponse = new Socks5CmdResponse( - Socks5CmdStatus.SUCCESS, Socks5AddressType.DOMAIN, "", 80); - assertEquals("", socks5CmdResponse.host()); - assertEquals(80, socks5CmdResponse.port()); - ByteBuf buffer = Unpooled.buffer(20); - socks5CmdResponse.encodeAsByteBuf(buffer); + Socks5CommandResponse socks5CmdResponse = new DefaultSocks5CommandResponse( + Socks5CommandStatus.SUCCESS, Socks5AddressType.DOMAIN, "", 80); + assertEquals("", socks5CmdResponse.bndAddr()); + assertEquals(80, socks5CmdResponse.bndPort()); + + ByteBuf buffer = Socks5CommonTestUtils.encodeServer(socks5CmdResponse); byte[] expected = { 0x05, // version 0x00, // success reply @@ -114,7 +113,8 @@ public class Socks5CmdResponseTest { */ @Test(expected = IllegalArgumentException.class) public void testInvalidBoundAddress() { - new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 1000); + new DefaultSocks5CommandResponse( + Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 1000); } private static void assertByteBufEquals(byte[] expected, ByteBuf actual) { @@ -127,13 +127,13 @@ public class Socks5CmdResponseTest { @Test public void testValidPortRange() { try { - new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 0); + new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 0); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { - new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 65536); + new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.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/Socks5InitRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialRequestTest.java similarity index 79% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5InitRequestTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialRequestTest.java index 4f4f7212c9..040d091fa3 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5InitRequestTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialRequestTest.java @@ -18,13 +18,13 @@ package io.netty.handler.codec.socksx.v5; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class Socks5InitRequestTest { +public class DefaultSocks5InitialRequestTest { @Test - public void testConstructorParamsAreNotNull() { + public void testConstructorParamsAreNotEmpty() { try { - new Socks5InitRequest(null); + new DefaultSocks5InitialRequest(); } catch (Exception e) { - assertTrue(e instanceof NullPointerException); + assertTrue(e instanceof IllegalArgumentException); } } } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5InitResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialResponseTest.java similarity index 90% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5InitResponseTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialResponseTest.java index 6846343f5e..c39f534c77 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5InitResponseTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5InitialResponseTest.java @@ -18,11 +18,11 @@ package io.netty.handler.codec.socksx.v5; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class Socks5InitResponseTest { +public class DefaultSocks5InitialResponseTest { @Test public void testConstructorParamsAreNotNull() { try { - new Socks5InitResponse(null); + new DefaultSocks5InitialResponse(null); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthRequestTest.java similarity index 86% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthRequestTest.java index 32e98ec8e2..87446d39bd 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthRequestTest.java @@ -18,16 +18,16 @@ package io.netty.handler.codec.socksx.v5; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class Socks5AuthRequestTest { +public class DefaultSocks5PasswordAuthRequestTest { @Test public void testConstructorParamsAreNotNull() { try { - new Socks5AuthRequest(null, ""); + new DefaultSocks5PasswordAuthRequest(null, ""); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } try { - new Socks5AuthRequest("", null); + new DefaultSocks5PasswordAuthRequest("", null); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } @@ -36,12 +36,12 @@ public class Socks5AuthRequestTest { @Test public void testUsernameOrPasswordIsNotAscii() { try { - new Socks5AuthRequest("παράδειγμα.δοκιμή", "password"); + new DefaultSocks5PasswordAuthRequest("παράδειγμα.δοκιμή", "password"); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } try { - new Socks5AuthRequest("username", "παράδειγμα.δοκιμή"); + new DefaultSocks5PasswordAuthRequest("username", "παράδειγμα.δοκιμή"); } catch (Exception e) { assertTrue(e instanceof IllegalArgumentException); } @@ -50,7 +50,7 @@ public class Socks5AuthRequestTest { @Test public void testUsernameOrPasswordLengthIsLessThan255Chars() { try { - new Socks5AuthRequest( + new DefaultSocks5PasswordAuthRequest( "passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + @@ -64,7 +64,7 @@ public class Socks5AuthRequestTest { assertTrue(e instanceof IllegalArgumentException); } try { - new Socks5AuthRequest("password", + new DefaultSocks5PasswordAuthRequest("password", "passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" + @@ -77,5 +77,4 @@ public class Socks5AuthRequestTest { assertTrue(e instanceof IllegalArgumentException); } } - } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthResponseTest.java similarity index 89% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthResponseTest.java index 3c7fe2e8b2..dc14e8ae78 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/DefaultSocks5PasswordAuthResponseTest.java @@ -18,14 +18,13 @@ package io.netty.handler.codec.socksx.v5; import org.junit.Test; import static org.junit.Assert.assertTrue; -public class Socks5AuthResponseTest { +public class DefaultSocks5PasswordAuthResponseTest { @Test public void testConstructorParamsAreNotNull() { try { - new Socks5AuthResponse(null); + new DefaultSocks5PasswordAuthResponse(null); } catch (Exception e) { assertTrue(e instanceof NullPointerException); } } - } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseDecoderTest.java deleted file mode 100755 index 7f449197af..0000000000 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdResponseDecoderTest.java +++ /dev/null @@ -1,84 +0,0 @@ -/* - * 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 Socks5CmdResponseDecoderTest { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks5CmdResponseDecoderTest.class); - - private static void testSocksCmdResponseDecoderWithDifferentParams( - Socks5CmdStatus cmdStatus, Socks5AddressType addressType, String host, int port) { - logger.debug("Testing cmdStatus: " + cmdStatus + " addressType: " + addressType); - Socks5Response msg = new Socks5CmdResponse(cmdStatus, addressType, host, port); - Socks5CmdResponseDecoder decoder = new Socks5CmdResponseDecoder(); - EmbeddedChannel embedder = new EmbeddedChannel(decoder); - Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); - if (addressType == Socks5AddressType.UNKNOWN) { - assertTrue(embedder.readInbound() instanceof UnknownSocks5Response); - } else { - msg = embedder.readInbound(); - assertEquals(((Socks5CmdResponse) msg).cmdStatus(), cmdStatus); - if (host != null) { - assertEquals(((Socks5CmdResponse) msg).host(), host); - } - assertEquals(((Socks5CmdResponse) msg).port(), port); - } - assertNull(embedder.readInbound()); - } - - /** - * Verifies that sent socks messages are decoded correctly. - */ - @Test - public void testSocksCmdResponseDecoder() { - for (Socks5CmdStatus cmdStatus : Socks5CmdStatus.values()) { - for (Socks5AddressType addressType : Socks5AddressType.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(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "1", 80); - } - - /** - * Verifies that send socks messages are decoded correctly when bound host and port are set. - */ - @Test - public void testSocksCmdResponseDecoderIncludingHost() { - for (Socks5CmdStatus cmdStatus : Socks5CmdStatus.values()) { - testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, Socks5AddressType.IPv4, - "127.0.0.1", 80); - testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, Socks5AddressType.DOMAIN, - "testDomain.com", 80); - testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, Socks5AddressType.IPv6, - "2001:db8:85a3:42:1000:8a2e:370:7334", 80); - testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, Socks5AddressType.IPv6, - "1111:111:11:1:0:0:0:1", 80); - } - } -} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequestDecoderTest.java similarity index 50% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestDecoderTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequestDecoderTest.java index 0e66a8970a..8b0478a4b1 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CmdRequestDecoderTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommandRequestDecoderTest.java @@ -16,35 +16,36 @@ package io.netty.handler.codec.socksx.v5; import io.netty.channel.embedded.EmbeddedChannel; +import io.netty.util.NetUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; import org.junit.Test; import sun.net.util.IPAddressUtil; +import java.net.IDN; +import java.util.Arrays; + import static org.junit.Assert.*; -public class Socks5CmdRequestDecoderTest { - private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks5CmdRequestDecoderTest.class); +public class Socks5CommandRequestDecoderTest { + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(Socks5CommandRequestDecoderTest.class); - private static void testSocksCmdRequestDecoderWithDifferentParams(Socks5CmdType cmdType, - Socks5AddressType addressType, - String host, - int port) { - logger.debug("Testing cmdType: " + cmdType + " addressType: " + addressType + " host: " + host + - " port: " + port); - Socks5CmdRequest msg = new Socks5CmdRequest(cmdType, addressType, host, port); - Socks5CmdRequestDecoder decoder = new Socks5CmdRequestDecoder(); - EmbeddedChannel embedder = new EmbeddedChannel(decoder); - Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); - if (msg.addressType() == Socks5AddressType.UNKNOWN) { - assertTrue(embedder.readInbound() instanceof UnknownSocks5Request); - } else { - msg = embedder.readInbound(); - assertSame(msg.cmdType(), cmdType); - assertSame(msg.addressType(), addressType); - assertEquals(msg.host(), host); - assertEquals(msg.port(), port); - } + private static void test( + Socks5CommandType type, Socks5AddressType dstAddrType, String dstAddr, int dstPort) { + logger.debug( + "Testing type: " + type + " dstAddrType: " + dstAddrType + + " dstAddr: " + dstAddr + " dstPort: " + dstPort); + + Socks5CommandRequest msg = + new DefaultSocks5CommandRequest(type, dstAddrType, dstAddr, dstPort); + EmbeddedChannel embedder = new EmbeddedChannel(new Socks5CommandRequestDecoder()); + Socks5CommonTestUtils.writeFromClientToServer(embedder, msg); + msg = embedder.readInbound(); + assertSame(msg.type(), type); + assertSame(msg.dstAddrType(), dstAddrType); + assertEquals(msg.dstAddr(), IDN.toASCII(dstAddr)); + assertEquals(msg.dstPort(), dstPort); assertNull(embedder.readInbound()); } @@ -52,10 +53,12 @@ public class Socks5CmdRequestDecoderTest { public void testCmdRequestDecoderIPv4() { String[] hosts = {"127.0.0.1", }; int[] ports = {1, 32769, 65535 }; - for (Socks5CmdType cmdType : Socks5CmdType.values()) { + for (Socks5CommandType cmdType: Arrays.asList(Socks5CommandType.BIND, + Socks5CommandType.CONNECT, + Socks5CommandType.UDP_ASSOCIATE)) { for (String host : hosts) { for (int port : ports) { - testSocksCmdRequestDecoderWithDifferentParams(cmdType, Socks5AddressType.IPv4, host, port); + test(cmdType, Socks5AddressType.IPv4, host, port); } } } @@ -63,12 +66,15 @@ public class Socks5CmdRequestDecoderTest { @Test public void testCmdRequestDecoderIPv6() { - String[] hosts = { Socks5CommonUtils.ipv6toStr(IPAddressUtil.textToNumericFormatV6("::1"))}; + String[] hosts = { + NetUtil.bytesToIpAddress(IPAddressUtil.textToNumericFormatV6("::1"), 0, 16) }; int[] ports = {1, 32769, 65535}; - for (Socks5CmdType cmdType : Socks5CmdType.values()) { + for (Socks5CommandType cmdType: Arrays.asList(Socks5CommandType.BIND, + Socks5CommandType.CONNECT, + Socks5CommandType.UDP_ASSOCIATE)) { for (String host : hosts) { for (int port : ports) { - testSocksCmdRequestDecoderWithDifferentParams(cmdType, Socks5AddressType.IPv6, host, port); + test(cmdType, Socks5AddressType.IPv6, host, port); } } } @@ -89,21 +95,14 @@ public class Socks5CmdRequestDecoderTest { "실례.테스트", "உதாரணம்.பரிட்சை"}; int[] ports = {1, 32769, 65535}; - for (Socks5CmdType cmdType : Socks5CmdType.values()) { + for (Socks5CommandType cmdType: Arrays.asList(Socks5CommandType.BIND, + Socks5CommandType.CONNECT, + Socks5CommandType.UDP_ASSOCIATE)) { for (String host : hosts) { for (int port : ports) { - testSocksCmdRequestDecoderWithDifferentParams(cmdType, Socks5AddressType.DOMAIN, host, port); + test(cmdType, Socks5AddressType.DOMAIN, host, port); } } } } - - @Test - public void testCmdRequestDecoderUnknown() { - String host = "google.com"; - int port = 80; - for (Socks5CmdType cmdType : Socks5CmdType.values()) { - testSocksCmdRequestDecoderWithDifferentParams(cmdType, Socks5AddressType.UNKNOWN, host, port); - } - } } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponseDecoderTest.java new file mode 100755 index 0000000000..150a4169a1 --- /dev/null +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommandResponseDecoderTest.java @@ -0,0 +1,98 @@ +/* + * 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 java.util.Arrays; + +import static org.junit.Assert.*; + +public class Socks5CommandResponseDecoderTest { + + private static final InternalLogger logger = + InternalLoggerFactory.getInstance(Socks5CommandResponseDecoderTest.class); + + private static final Socks5CommandStatus[] STATUSES = { + Socks5CommandStatus.ADDRESS_UNSUPPORTED, + Socks5CommandStatus.COMMAND_UNSUPPORTED, + Socks5CommandStatus.CONNECTION_REFUSED, + Socks5CommandStatus.FAILURE, + Socks5CommandStatus.FORBIDDEN, + Socks5CommandStatus.HOST_UNREACHABLE, + Socks5CommandStatus.NETWORK_UNREACHABLE, + Socks5CommandStatus.SUCCESS, + Socks5CommandStatus.TTL_EXPIRED + }; + + private static void test( + Socks5CommandStatus status, Socks5AddressType bndAddrType, String bndAddr, int bndPort) { + logger.debug("Testing status: " + status + " bndAddrType: " + bndAddrType); + Socks5CommandResponse msg = + new DefaultSocks5CommandResponse(status, bndAddrType, bndAddr, bndPort); + EmbeddedChannel embedder = new EmbeddedChannel(new Socks5CommandResponseDecoder()); + Socks5CommonTestUtils.writeFromServerToClient(embedder, msg); + msg = embedder.readInbound(); + assertEquals(msg.status(), status); + if (bndAddr != null) { + assertEquals(msg.bndAddr(), bndAddr); + } + assertEquals(msg.bndPort(), bndPort); + assertNull(embedder.readInbound()); + } + + /** + * Verifies that sent socks messages are decoded correctly. + */ + @Test + public void testSocksCmdResponseDecoder() { + for (Socks5CommandStatus cmdStatus: STATUSES) { + for (Socks5AddressType addressType : Arrays.asList(Socks5AddressType.DOMAIN, + Socks5AddressType.IPv4, + Socks5AddressType.IPv6)) { + test(cmdStatus, addressType, null, 0); + } + } + } + + /** + * Verifies that invalid bound host will fail with IllegalArgumentException during encoding. + */ + @Test(expected = IllegalArgumentException.class) + public void testInvalidAddress() { + test(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "1", 80); + } + + /** + * Verifies that send socks messages are decoded correctly when bound host and port are set. + */ + @Test + public void testSocksCmdResponseDecoderIncludingHost() { + for (Socks5CommandStatus cmdStatus : STATUSES) { + test(cmdStatus, Socks5AddressType.IPv4, + "127.0.0.1", 80); + test(cmdStatus, Socks5AddressType.DOMAIN, + "testDomain.com", 80); + test(cmdStatus, Socks5AddressType.IPv6, + "2001:db8:85a3:42:1000:8a2e:370:7334", 80); + test(cmdStatus, Socks5AddressType.IPv6, + "1111:111:11:1:0:0:0:1", 80); + } + } +} diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommonTestUtils.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommonTestUtils.java index be1212148b..f3ef3936e3 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommonTestUtils.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5CommonTestUtils.java @@ -16,7 +16,6 @@ package io.netty.handler.codec.socksx.v5; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; import io.netty.channel.embedded.EmbeddedChannel; final class Socks5CommonTestUtils { @@ -27,15 +26,31 @@ final class Socks5CommonTestUtils { //NOOP } - public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks5Request msg) { - ByteBuf buf = Unpooled.buffer(); - msg.encodeAsByteBuf(buf); - embedder.writeInbound(buf); + public static void writeFromClientToServer(EmbeddedChannel embedder, Socks5Message msg) { + embedder.writeInbound(encodeClient(msg)); } - public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks5Response msg) { - ByteBuf buf = Unpooled.buffer(); - msg.encodeAsByteBuf(buf); - embedder.writeInbound(buf); + public static void writeFromServerToClient(EmbeddedChannel embedder, Socks5Message msg) { + embedder.writeInbound(encodeServer(msg)); + } + + public static ByteBuf encodeClient(Socks5Message msg) { + EmbeddedChannel out = new EmbeddedChannel(Socks5ClientEncoder.DEFAULT); + out.writeOutbound(msg); + + ByteBuf encoded = out.readOutbound(); + out.finish(); + + return encoded; + } + + public static ByteBuf encodeServer(Socks5Message msg) { + EmbeddedChannel out = new EmbeddedChannel(Socks5ServerEncoder.DEFAULT); + out.writeOutbound(msg); + + ByteBuf encoded = out.readOutbound(); + out.finish(); + + return encoded; } } diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequestDecoderTest.java similarity index 76% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestDecoderTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequestDecoderTest.java index ae9235541c..8b8aa06bd2 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthRequestDecoderTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthRequestDecoderTest.java @@ -20,16 +20,15 @@ import org.junit.Test; import static org.junit.Assert.*; -public class Socks5AuthRequestDecoderTest { +public class Socks5PasswordAuthRequestDecoderTest { @Test public void testAuthRequestDecoder() { String username = "test"; String password = "test"; - Socks5AuthRequest msg = new Socks5AuthRequest(username, password); - Socks5AuthRequestDecoder decoder = new Socks5AuthRequestDecoder(); - EmbeddedChannel embedder = new EmbeddedChannel(decoder); - Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + Socks5PasswordAuthRequest msg = new DefaultSocks5PasswordAuthRequest(username, password); + EmbeddedChannel embedder = new EmbeddedChannel(new Socks5PasswordAuthRequestDecoder()); + Socks5CommonTestUtils.writeFromClientToServer(embedder, msg); msg = embedder.readInbound(); assertEquals(msg.username(), username); assertEquals(msg.username(), password); diff --git a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseDecoderTest.java b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponseDecoderTest.java similarity index 59% rename from codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseDecoderTest.java rename to codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponseDecoderTest.java index 4482635fc6..b823647fc9 100755 --- a/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5AuthResponseDecoderTest.java +++ b/codec-socks/src/test/java/io/netty/handler/codec/socksx/v5/Socks5PasswordAuthResponseDecoderTest.java @@ -22,25 +22,23 @@ import org.junit.Test; import static org.junit.Assert.*; -public class Socks5AuthResponseDecoderTest { +public class Socks5PasswordAuthResponseDecoderTest { private static final InternalLogger logger = InternalLoggerFactory.getInstance( - Socks5AuthResponseDecoderTest.class); + Socks5PasswordAuthResponseDecoderTest.class); - private static void testSocksAuthResponseDecoderWithDifferentParams(Socks5AuthStatus authStatus) { - logger.debug("Testing SocksAuthResponseDecoder with authStatus: " + authStatus); - Socks5AuthResponse msg = new Socks5AuthResponse(authStatus); - Socks5AuthResponseDecoder decoder = new Socks5AuthResponseDecoder(); - EmbeddedChannel embedder = new EmbeddedChannel(decoder); - Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); + private static void test(Socks5PasswordAuthStatus status) { + logger.debug("Testing Socks5PasswordAuthResponseDecoder with status: " + status); + Socks5PasswordAuthResponse msg = new DefaultSocks5PasswordAuthResponse(status); + EmbeddedChannel embedder = new EmbeddedChannel(new Socks5PasswordAuthResponseDecoder()); + Socks5CommonTestUtils.writeFromServerToClient(embedder, msg); msg = embedder.readInbound(); - assertSame(msg.authStatus(), authStatus); + assertSame(msg.status(), status); assertNull(embedder.readInbound()); } @Test public void testSocksCmdResponseDecoder() { - for (Socks5AuthStatus authStatus: Socks5AuthStatus.values()) { - testSocksAuthResponseDecoderWithDifferentParams(authStatus); - } + test(Socks5PasswordAuthStatus.SUCCESS); + test(Socks5PasswordAuthStatus.FAILURE); } } diff --git a/codec/src/main/java/io/netty/handler/codec/ReplayingDecoderBuffer.java b/codec/src/main/java/io/netty/handler/codec/ReplayingDecoderBuffer.java index a610a19a3e..abfc99283b 100644 --- a/codec/src/main/java/io/netty/handler/codec/ReplayingDecoderBuffer.java +++ b/codec/src/main/java/io/netty/handler/codec/ReplayingDecoderBuffer.java @@ -329,8 +329,7 @@ final class ReplayingDecoderBuffer extends ByteBuf { @Override public int bytesBefore(int length, byte value) { - final int readerIndex = buffer.readerIndex(); - return bytesBefore(readerIndex, buffer.writerIndex() - readerIndex, value); + return bytesBefore(buffer.readerIndex(), length, value); } @Override diff --git a/common/src/main/java/io/netty/util/NetUtil.java b/common/src/main/java/io/netty/util/NetUtil.java index d357c7e3f1..21e4d59534 100644 --- a/common/src/main/java/io/netty/util/NetUtil.java +++ b/common/src/main/java/io/netty/util/NetUtil.java @@ -16,6 +16,7 @@ package io.netty.util; import io.netty.util.internal.PlatformDependent; +import io.netty.util.internal.StringUtil; import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLoggerFactory; @@ -395,8 +396,7 @@ public final class NetUtil { ipByteArray[byteIndex + 1] |= charValue & 15; } - static int getIntValue(char c) { - + private static int getIntValue(char c) { switch (c) { case '0': return 0; @@ -438,6 +438,58 @@ public final class NetUtil { return 0; } + /** + * Converts a 32-bit integer into an IPv4 address. + */ + public static String intToIpAddress(int i) { + StringBuilder buf = new StringBuilder(15); + buf.append(i >> 24 & 0xff); + buf.append('.'); + buf.append(i >> 16 & 0xff); + buf.append('.'); + buf.append(i >> 8 & 0xff); + buf.append('.'); + buf.append(i & 0xff); + return buf.toString(); + } + + /** + * Converts 4-byte or 16-byte data into an IPv4 or IPv6 string respectively. + * + * @throws IllegalArgumentException + * if {@code length} is not {@code 4} nor {@code 16} + */ + public static String bytesToIpAddress(byte[] bytes, int offset, int length) { + if (length == 4) { + StringBuilder buf = new StringBuilder(15); + + buf.append(bytes[offset ++] >> 24 & 0xff); + buf.append('.'); + buf.append(bytes[offset ++] >> 16 & 0xff); + buf.append('.'); + buf.append(bytes[offset ++] >> 8 & 0xff); + buf.append('.'); + buf.append(bytes[offset] & 0xff); + + return buf.toString(); + } + + if (length == 16) { + final StringBuilder sb = new StringBuilder(39); + final int endOffset = offset + 14; + + for (; offset < endOffset; offset += 2) { + StringUtil.toHexString(sb, bytes, offset, 2); + sb.append(':'); + } + StringUtil.toHexString(sb, bytes, offset, 2); + + return sb.toString(); + } + + throw new IllegalArgumentException("length: " + length + " (expected: 4 or 16)"); + } + public static boolean isValidIpV6Address(String ipAddress) { int length = ipAddress.length(); boolean doubleColon = false; diff --git a/example/src/main/java/io/netty/example/socksproxy/SocksPortUnificationServerHandler.java b/example/src/main/java/io/netty/example/socksproxy/SocksPortUnificationServerHandler.java deleted file mode 100644 index b219ec18db..0000000000 --- a/example/src/main/java/io/netty/example/socksproxy/SocksPortUnificationServerHandler.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * 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.SocksProtocolVersion; -import io.netty.handler.codec.socksx.v4.Socks4CmdRequestDecoder; -import io.netty.handler.codec.socksx.v4.Socks4MessageEncoder; -import io.netty.handler.codec.socksx.v5.Socks5InitRequestDecoder; -import io.netty.handler.codec.socksx.v5.Socks5MessageEncoder; - -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 Socks4CmdRequestDecoder()); - p.addLast(Socks4MessageEncoder.INSTANCE); - - break; - case SOCKS5: - p.addLast(new Socks5InitRequestDecoder()); - p.addLast(Socks5MessageEncoder.INSTANCE); - - break; - case UNKNOWN: - in.clear(); - ctx.close(); - return; - } - p.addLast(SocksServerHandler.INSTANCE); - 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 07d73c1a2e..e6114d4233 100644 --- a/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java +++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerConnectHandler.java @@ -24,46 +24,47 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOption; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.socket.nio.NioSocketChannel; -import io.netty.handler.codec.socksx.SocksRequest; -import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; -import io.netty.handler.codec.socksx.v4.Socks4CmdResponse; -import io.netty.handler.codec.socksx.v4.Socks4CmdStatus; -import io.netty.handler.codec.socksx.v5.Socks5CmdRequest; -import io.netty.handler.codec.socksx.v5.Socks5CmdResponse; -import io.netty.handler.codec.socksx.v5.Socks5CmdStatus; +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandResponse; +import io.netty.handler.codec.socksx.v4.Socks4CommandRequest; +import io.netty.handler.codec.socksx.v4.Socks4CommandStatus; +import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse; +import io.netty.handler.codec.socksx.v5.Socks5CommandRequest; +import io.netty.handler.codec.socksx.v5.Socks5CommandStatus; import io.netty.util.concurrent.Future; -import io.netty.util.concurrent.GenericFutureListener; +import io.netty.util.concurrent.FutureListener; 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 messageReceived(final ChannelHandlerContext ctx, final SocksRequest message) throws Exception { - if (message instanceof Socks4CmdRequest) { - final Socks4CmdRequest request = (Socks4CmdRequest) message; + public void messageReceived(final ChannelHandlerContext ctx, final SocksMessage message) throws Exception { + if (message instanceof Socks4CommandRequest) { + final Socks4CommandRequest request = (Socks4CommandRequest) message; Promise promise = ctx.executor().newPromise(); promise.addListener( - new GenericFutureListener>() { + new FutureListener() { @Override public void operationComplete(final Future future) throws Exception { final Channel outboundChannel = future.getNow(); if (future.isSuccess()) { - ctx.channel().writeAndFlush(new Socks4CmdResponse(Socks4CmdStatus.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)); - } - }); + ChannelFuture responseFuture = ctx.channel().writeAndFlush( + new DefaultSocks4CommandResponse(Socks4CommandStatus.SUCCESS)); + + responseFuture.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 Socks4CmdResponse(Socks4CmdStatus.REJECTED_OR_FAILED) - ); + new DefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED)); SocksServerUtils.closeOnFlush(ctx.channel()); } } @@ -76,7 +77,7 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler .option(ChannelOption.SO_KEEPALIVE, true) .handler(new DirectClientHandler(promise)); - b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { + b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { @@ -84,35 +85,36 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler } else { // Close the connection if the connection attempt has failed. ctx.channel().writeAndFlush( - new Socks4CmdResponse(Socks4CmdStatus.REJECTED_OR_FAILED) + new DefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED) ); SocksServerUtils.closeOnFlush(ctx.channel()); } } }); - } else if (message instanceof Socks5CmdRequest) { - final Socks5CmdRequest request = (Socks5CmdRequest) message; + } else if (message instanceof Socks5CommandRequest) { + final Socks5CommandRequest request = (Socks5CommandRequest) message; Promise promise = ctx.executor().newPromise(); promise.addListener( - new GenericFutureListener>() { + new FutureListener() { @Override public void operationComplete(final Future future) throws Exception { final Channel outboundChannel = future.getNow(); if (future.isSuccess()) { - ctx.channel().writeAndFlush( - new Socks5CmdResponse(Socks5CmdStatus.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)); - } - } - ); + ChannelFuture responseFuture = + ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse( + Socks5CommandStatus.SUCCESS, request.dstAddrType())); + + responseFuture.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 Socks5CmdResponse(Socks5CmdStatus.FAILURE, request.addressType())); + ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse( + Socks5CommandStatus.FAILURE, request.dstAddrType())); SocksServerUtils.closeOnFlush(ctx.channel()); } } @@ -125,7 +127,7 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler .option(ChannelOption.SO_KEEPALIVE, true) .handler(new DirectClientHandler(promise)); - b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { + b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { @@ -133,7 +135,7 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler } else { // Close the connection if the connection attempt has failed. ctx.channel().writeAndFlush( - new Socks5CmdResponse(Socks5CmdStatus.FAILURE, request.addressType())); + new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, request.dstAddrType())); SocksServerUtils.closeOnFlush(ctx.channel()); } } 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 1e7087d521..853a645277 100644 --- a/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java +++ b/example/src/main/java/io/netty/example/socksproxy/SocksServerHandler.java @@ -18,31 +18,32 @@ 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.socksx.SocksRequest; -import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; -import io.netty.handler.codec.socksx.v4.Socks4CmdType; -import io.netty.handler.codec.socksx.v5.Socks5AuthScheme; -import io.netty.handler.codec.socksx.v5.Socks5CmdRequestDecoder; -import io.netty.handler.codec.socksx.v5.Socks5InitResponse; -import io.netty.handler.codec.socksx.v5.Socks5Request; -import io.netty.handler.codec.socksx.v5.Socks5AuthResponse; -import io.netty.handler.codec.socksx.v5.Socks5AuthStatus; -import io.netty.handler.codec.socksx.v5.Socks5CmdRequest; -import io.netty.handler.codec.socksx.v5.Socks5CmdType; +import io.netty.handler.codec.socksx.SocksMessage; +import io.netty.handler.codec.socksx.v4.Socks4CommandRequest; +import io.netty.handler.codec.socksx.v4.Socks4CommandType; +import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialResponse; +import io.netty.handler.codec.socksx.v5.DefaultSocks5PasswordAuthResponse; +import io.netty.handler.codec.socksx.v5.Socks5AuthMethod; +import io.netty.handler.codec.socksx.v5.Socks5CommandRequest; +import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder; +import io.netty.handler.codec.socksx.v5.Socks5CommandType; +import io.netty.handler.codec.socksx.v5.Socks5InitialRequest; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequest; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus; @ChannelHandler.Sharable -public final class SocksServerHandler extends SimpleChannelInboundHandler { +public final class SocksServerHandler extends SimpleChannelInboundHandler { public static final SocksServerHandler INSTANCE = new SocksServerHandler(); private SocksServerHandler() { } @Override - public void messageReceived(ChannelHandlerContext ctx, SocksRequest socksRequest) throws Exception { - switch (socksRequest.protocolVersion()) { + public void messageReceived(ChannelHandlerContext ctx, SocksMessage socksRequest) throws Exception { + switch (socksRequest.version()) { case SOCKS4a: - Socks4CmdRequest socksV4CmdRequest = (Socks4CmdRequest) socksRequest; - if (socksV4CmdRequest.cmdType() == Socks4CmdType.CONNECT) { + Socks4CommandRequest socksV4CmdRequest = (Socks4CommandRequest) socksRequest; + if (socksV4CmdRequest.type() == Socks4CommandType.CONNECT) { ctx.pipeline().addLast(new SocksServerConnectHandler()); ctx.pipeline().remove(this); ctx.fireChannelRead(socksRequest); @@ -51,32 +52,26 @@ public final class SocksServerHandler extends SimpleChannelInboundHandler { @Override - public void initChannel(SocketChannel socketChannel) throws Exception { - ChannelPipeline p = socketChannel.pipeline(); - p.addFirst(new LoggingHandler(LogLevel.DEBUG)); - p.addLast(new SocksPortUnificationServerHandler()); + public void initChannel(SocketChannel ch) throws Exception { + ch.pipeline().addLast( + new LoggingHandler(LogLevel.DEBUG), + new SocksPortUnificationServerHandler(), + SocksServerHandler.INSTANCE); } } diff --git a/handler-proxy/src/main/java/io/netty/handler/proxy/Socks4ProxyHandler.java b/handler-proxy/src/main/java/io/netty/handler/proxy/Socks4ProxyHandler.java index ce7dbb8431..bf52f29db7 100644 --- a/handler-proxy/src/main/java/io/netty/handler/proxy/Socks4ProxyHandler.java +++ b/handler-proxy/src/main/java/io/netty/handler/proxy/Socks4ProxyHandler.java @@ -18,12 +18,12 @@ package io.netty.handler.proxy; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; -import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; -import io.netty.handler.codec.socksx.v4.Socks4CmdResponse; -import io.netty.handler.codec.socksx.v4.Socks4CmdResponseDecoder; -import io.netty.handler.codec.socksx.v4.Socks4CmdStatus; -import io.netty.handler.codec.socksx.v4.Socks4CmdType; -import io.netty.handler.codec.socksx.v4.Socks4MessageEncoder; +import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandRequest; +import io.netty.handler.codec.socksx.v4.Socks4ClientDecoder; +import io.netty.handler.codec.socksx.v4.Socks4ClientEncoder; +import io.netty.handler.codec.socksx.v4.Socks4CommandResponse; +import io.netty.handler.codec.socksx.v4.Socks4CommandStatus; +import io.netty.handler.codec.socksx.v4.Socks4CommandType; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -69,13 +69,13 @@ public final class Socks4ProxyHandler extends ProxyHandler { ChannelPipeline p = ctx.pipeline(); String name = ctx.name(); - Socks4CmdResponseDecoder decoder = new Socks4CmdResponseDecoder(); + Socks4ClientDecoder decoder = new Socks4ClientDecoder(); p.addBefore(name, null, decoder); decoderName = p.context(decoder).name(); encoderName = decoderName + ".encoder"; - p.addBefore(name, encoderName, Socks4MessageEncoder.INSTANCE); + p.addBefore(name, encoderName, Socks4ClientEncoder.INSTANCE); } @Override @@ -99,18 +99,18 @@ public final class Socks4ProxyHandler extends ProxyHandler { } else { rhost = raddr.getAddress().getHostAddress(); } - return new Socks4CmdRequest( - username != null? username : "", Socks4CmdType.CONNECT, rhost, raddr.getPort()); + return new DefaultSocks4CommandRequest( + Socks4CommandType.CONNECT, rhost, raddr.getPort(), username != null? username : ""); } @Override protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception { - final Socks4CmdResponse res = (Socks4CmdResponse) response; - final Socks4CmdStatus status = res.cmdStatus(); - if (status == Socks4CmdStatus.SUCCESS) { + final Socks4CommandResponse res = (Socks4CommandResponse) response; + final Socks4CommandStatus status = res.status(); + if (status == Socks4CommandStatus.SUCCESS) { return true; } - throw new ProxyConnectException(exceptionMessage("cmdStatus: " + status)); + throw new ProxyConnectException(exceptionMessage("status: " + status)); } } diff --git a/handler-proxy/src/main/java/io/netty/handler/proxy/Socks5ProxyHandler.java b/handler-proxy/src/main/java/io/netty/handler/proxy/Socks5ProxyHandler.java index a96c2838df..e448e7f5d3 100644 --- a/handler-proxy/src/main/java/io/netty/handler/proxy/Socks5ProxyHandler.java +++ b/handler-proxy/src/main/java/io/netty/handler/proxy/Socks5ProxyHandler.java @@ -18,21 +18,22 @@ package io.netty.handler.proxy; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; +import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialRequest; +import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandRequest; +import io.netty.handler.codec.socksx.v5.DefaultSocks5PasswordAuthRequest; import io.netty.handler.codec.socksx.v5.Socks5AddressType; -import io.netty.handler.codec.socksx.v5.Socks5AuthRequest; -import io.netty.handler.codec.socksx.v5.Socks5AuthResponse; -import io.netty.handler.codec.socksx.v5.Socks5AuthResponseDecoder; -import io.netty.handler.codec.socksx.v5.Socks5AuthScheme; -import io.netty.handler.codec.socksx.v5.Socks5AuthStatus; -import io.netty.handler.codec.socksx.v5.Socks5CmdRequest; -import io.netty.handler.codec.socksx.v5.Socks5CmdResponse; -import io.netty.handler.codec.socksx.v5.Socks5CmdResponseDecoder; -import io.netty.handler.codec.socksx.v5.Socks5CmdStatus; -import io.netty.handler.codec.socksx.v5.Socks5CmdType; -import io.netty.handler.codec.socksx.v5.Socks5InitRequest; -import io.netty.handler.codec.socksx.v5.Socks5InitResponse; -import io.netty.handler.codec.socksx.v5.Socks5InitResponseDecoder; -import io.netty.handler.codec.socksx.v5.Socks5MessageEncoder; +import io.netty.handler.codec.socksx.v5.Socks5AuthMethod; +import io.netty.handler.codec.socksx.v5.Socks5InitialRequest; +import io.netty.handler.codec.socksx.v5.Socks5InitialResponse; +import io.netty.handler.codec.socksx.v5.Socks5InitialResponseDecoder; +import io.netty.handler.codec.socksx.v5.Socks5ClientEncoder; +import io.netty.handler.codec.socksx.v5.Socks5CommandResponse; +import io.netty.handler.codec.socksx.v5.Socks5CommandResponseDecoder; +import io.netty.handler.codec.socksx.v5.Socks5CommandStatus; +import io.netty.handler.codec.socksx.v5.Socks5CommandType; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponse; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthResponseDecoder; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus; import io.netty.util.NetUtil; import io.netty.util.internal.StringUtil; @@ -46,11 +47,11 @@ public final class Socks5ProxyHandler extends ProxyHandler { private static final String PROTOCOL = "socks5"; private static final String AUTH_PASSWORD = "password"; - private static final Socks5InitRequest INIT_REQUEST_NO_AUTH = - new Socks5InitRequest(Collections.singletonList(Socks5AuthScheme.NO_AUTH)); + private static final Socks5InitialRequest INIT_REQUEST_NO_AUTH = + new DefaultSocks5InitialRequest(Collections.singletonList(Socks5AuthMethod.NO_AUTH)); - private static final Socks5InitRequest INIT_REQUEST_PASSWORD = - new Socks5InitRequest(Arrays.asList(Socks5AuthScheme.NO_AUTH, Socks5AuthScheme.AUTH_PASSWORD)); + private static final Socks5InitialRequest INIT_REQUEST_PASSWORD = + new DefaultSocks5InitialRequest(Arrays.asList(Socks5AuthMethod.NO_AUTH, Socks5AuthMethod.PASSWORD)); private final String username; private final String password; @@ -81,7 +82,7 @@ public final class Socks5ProxyHandler extends ProxyHandler { @Override public String authScheme() { - return socksAuthScheme() == Socks5AuthScheme.AUTH_PASSWORD? AUTH_PASSWORD : AUTH_NONE; + return socksAuthMethod() == Socks5AuthMethod.PASSWORD? AUTH_PASSWORD : AUTH_NONE; } public String username() { @@ -97,13 +98,13 @@ public final class Socks5ProxyHandler extends ProxyHandler { ChannelPipeline p = ctx.pipeline(); String name = ctx.name(); - Socks5InitResponseDecoder decoder = new Socks5InitResponseDecoder(); + Socks5InitialResponseDecoder decoder = new Socks5InitialResponseDecoder(); p.addBefore(name, null, decoder); decoderName = p.context(decoder).name(); encoderName = decoderName + ".encoder"; - p.addBefore(name, encoderName, Socks5MessageEncoder.INSTANCE); + p.addBefore(name, encoderName, Socks5ClientEncoder.DEFAULT); } @Override @@ -121,31 +122,28 @@ public final class Socks5ProxyHandler extends ProxyHandler { @Override protected Object newInitialMessage(ChannelHandlerContext ctx) throws Exception { - return socksAuthScheme() == Socks5AuthScheme.AUTH_PASSWORD? INIT_REQUEST_PASSWORD : INIT_REQUEST_NO_AUTH; + return socksAuthMethod() == Socks5AuthMethod.PASSWORD? INIT_REQUEST_PASSWORD : INIT_REQUEST_NO_AUTH; } @Override protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception { - if (response instanceof Socks5InitResponse) { - Socks5InitResponse res = (Socks5InitResponse) response; - Socks5AuthScheme authScheme = socksAuthScheme(); + if (response instanceof Socks5InitialResponse) { + Socks5InitialResponse res = (Socks5InitialResponse) response; + Socks5AuthMethod authMethod = socksAuthMethod(); - if (res.authScheme() != Socks5AuthScheme.NO_AUTH && authScheme != res.authScheme()) { + if (res.authMethod() != Socks5AuthMethod.NO_AUTH && res.authMethod() != authMethod) { // Server did not allow unauthenticated access nor accept the requested authentication scheme. - throw new ProxyConnectException(exceptionMessage("unexpected authScheme: " + res.authScheme())); + throw new ProxyConnectException(exceptionMessage("unexpected authMethod: " + res.authMethod())); } - switch (authScheme) { - case NO_AUTH: + if (authMethod == Socks5AuthMethod.NO_AUTH) { sendConnectCommand(ctx); - break; - case AUTH_PASSWORD: + } else if (authMethod == Socks5AuthMethod.PASSWORD) { // In case of password authentication, send an authentication request. - ctx.pipeline().addBefore(encoderName, decoderName, new Socks5AuthResponseDecoder()); - sendToProxyServer( - new Socks5AuthRequest(username != null? username : "", password != null? password : "")); - break; - default: + ctx.pipeline().replace(decoderName, decoderName, new Socks5PasswordAuthResponseDecoder()); + sendToProxyServer(new DefaultSocks5PasswordAuthRequest( + username != null? username : "", password != null? password : "")); + } else { // Should never reach here. throw new Error(); } @@ -153,11 +151,11 @@ public final class Socks5ProxyHandler extends ProxyHandler { return false; } - if (response instanceof Socks5AuthResponse) { + if (response instanceof Socks5PasswordAuthResponse) { // Received an authentication response from the server. - Socks5AuthResponse res = (Socks5AuthResponse) response; - if (res.authStatus() != Socks5AuthStatus.SUCCESS) { - throw new ProxyConnectException(exceptionMessage("authStatus: " + res.authStatus())); + Socks5PasswordAuthResponse res = (Socks5PasswordAuthResponse) response; + if (res.status() != Socks5PasswordAuthStatus.SUCCESS) { + throw new ProxyConnectException(exceptionMessage("authStatus: " + res.status())); } sendConnectCommand(ctx); @@ -165,22 +163,22 @@ public final class Socks5ProxyHandler extends ProxyHandler { } // This should be the last message from the server. - Socks5CmdResponse res = (Socks5CmdResponse) response; - if (res.cmdStatus() != Socks5CmdStatus.SUCCESS) { - throw new ProxyConnectException(exceptionMessage("cmdStatus: " + res.cmdStatus())); + Socks5CommandResponse res = (Socks5CommandResponse) response; + if (res.status() != Socks5CommandStatus.SUCCESS) { + throw new ProxyConnectException(exceptionMessage("status: " + res.status())); } return true; } - private Socks5AuthScheme socksAuthScheme() { - Socks5AuthScheme authScheme; + private Socks5AuthMethod socksAuthMethod() { + Socks5AuthMethod authMethod; if (username == null && password == null) { - authScheme = Socks5AuthScheme.NO_AUTH; + authMethod = Socks5AuthMethod.NO_AUTH; } else { - authScheme = Socks5AuthScheme.AUTH_PASSWORD; + authMethod = Socks5AuthMethod.PASSWORD; } - return authScheme; + return authMethod; } private void sendConnectCommand(ChannelHandlerContext ctx) throws Exception { @@ -202,7 +200,7 @@ public final class Socks5ProxyHandler extends ProxyHandler { } } - ctx.pipeline().addBefore(encoderName, decoderName, new Socks5CmdResponseDecoder()); - sendToProxyServer(new Socks5CmdRequest(Socks5CmdType.CONNECT, addrType, rhost, raddr.getPort())); + ctx.pipeline().replace(decoderName, decoderName, new Socks5CommandResponseDecoder()); + sendToProxyServer(new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, addrType, rhost, raddr.getPort())); } } diff --git a/handler-proxy/src/test/java/io/netty/handler/proxy/ProxyHandlerTest.java b/handler-proxy/src/test/java/io/netty/handler/proxy/ProxyHandlerTest.java index cab48b0096..06f0053c25 100644 --- a/handler-proxy/src/test/java/io/netty/handler/proxy/ProxyHandlerTest.java +++ b/handler-proxy/src/test/java/io/netty/handler/proxy/ProxyHandlerTest.java @@ -219,12 +219,12 @@ public class ProxyHandlerTest { new FailureTestItem( "Anonymous SOCKS4: rejected connection", - BAD_DESTINATION, "cmdStatus: REJECTED_OR_FAILED", + BAD_DESTINATION, "status: REJECTED_OR_FAILED", new Socks4ProxyHandler(anonSocks4Proxy.address())), new FailureTestItem( "SOCKS4: rejected anonymous connection", - DESTINATION, "cmdStatus: IDENTD_AUTH_FAILURE", + DESTINATION, "status: IDENTD_AUTH_FAILURE", new Socks4ProxyHandler(socks4Proxy.address())), new SuccessTestItem( @@ -234,12 +234,12 @@ public class ProxyHandlerTest { new FailureTestItem( "SOCKS4: rejected connection", - BAD_DESTINATION, "cmdStatus: REJECTED_OR_FAILED", + BAD_DESTINATION, "status: REJECTED_OR_FAILED", new Socks4ProxyHandler(socks4Proxy.address(), USERNAME)), new FailureTestItem( "SOCKS4: authentication failure", - DESTINATION, "cmdStatus: IDENTD_AUTH_FAILURE", + DESTINATION, "status: IDENTD_AUTH_FAILURE", new Socks4ProxyHandler(socks4Proxy.address(), BAD_USERNAME)), new TimeoutTestItem( @@ -255,12 +255,12 @@ public class ProxyHandlerTest { new FailureTestItem( "Anonymous SOCKS5: rejected connection", - BAD_DESTINATION, "cmdStatus: FORBIDDEN", + BAD_DESTINATION, "status: FORBIDDEN", new Socks5ProxyHandler(anonSocks5Proxy.address())), new FailureTestItem( "SOCKS5: rejected anonymous connection", - DESTINATION, "unexpected authScheme: AUTH_PASSWORD", + DESTINATION, "unexpected authMethod: PASSWORD", new Socks5ProxyHandler(socks5Proxy.address())), new SuccessTestItem( @@ -270,7 +270,7 @@ public class ProxyHandlerTest { new FailureTestItem( "SOCKS5: rejected connection", - BAD_DESTINATION, "cmdStatus: FORBIDDEN", + BAD_DESTINATION, "status: FORBIDDEN", new Socks5ProxyHandler(socks5Proxy.address(), USERNAME, PASSWORD)), new FailureTestItem( diff --git a/handler-proxy/src/test/java/io/netty/handler/proxy/Socks4ProxyServer.java b/handler-proxy/src/test/java/io/netty/handler/proxy/Socks4ProxyServer.java index 4a01cebe1d..01b2a49f14 100644 --- a/handler-proxy/src/test/java/io/netty/handler/proxy/Socks4ProxyServer.java +++ b/handler-proxy/src/test/java/io/netty/handler/proxy/Socks4ProxyServer.java @@ -21,18 +21,19 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; -import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; -import io.netty.handler.codec.socksx.v4.Socks4CmdRequestDecoder; -import io.netty.handler.codec.socksx.v4.Socks4CmdResponse; -import io.netty.handler.codec.socksx.v4.Socks4CmdStatus; -import io.netty.handler.codec.socksx.v4.Socks4CmdType; -import io.netty.handler.codec.socksx.v4.Socks4MessageEncoder; +import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandResponse; +import io.netty.handler.codec.socksx.v4.Socks4CommandRequest; +import io.netty.handler.codec.socksx.v4.Socks4CommandResponse; +import io.netty.handler.codec.socksx.v4.Socks4CommandStatus; +import io.netty.handler.codec.socksx.v4.Socks4CommandType; +import io.netty.handler.codec.socksx.v4.Socks4ServerDecoder; +import io.netty.handler.codec.socksx.v4.Socks4ServerEncoder; import io.netty.util.CharsetUtil; import java.net.InetSocketAddress; import java.net.SocketAddress; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; final class Socks4ProxyServer extends ProxyServer { @@ -50,13 +51,13 @@ final class Socks4ProxyServer extends ProxyServer { ChannelPipeline p = ch.pipeline(); switch (testMode) { case INTERMEDIARY: - p.addLast(new Socks4CmdRequestDecoder()); - p.addLast(Socks4MessageEncoder.INSTANCE); + p.addLast(new Socks4ServerDecoder()); + p.addLast(Socks4ServerEncoder.INSTANCE); p.addLast(new Socks4IntermediaryHandler()); break; case TERMINAL: - p.addLast(new Socks4CmdRequestDecoder()); - p.addLast(Socks4MessageEncoder.INSTANCE); + p.addLast(new Socks4ServerDecoder()); + p.addLast(Socks4ServerEncoder.INSTANCE); p.addLast(new Socks4TerminalHandler()); break; case UNRESPONSIVE: @@ -65,8 +66,8 @@ final class Socks4ProxyServer extends ProxyServer { } } - private boolean authenticate(ChannelHandlerContext ctx, Socks4CmdRequest req) { - assertThat(req.cmdType(), is(Socks4CmdType.CONNECT)); + private boolean authenticate(ChannelHandlerContext ctx, Socks4CommandRequest req) { + assertThat(req.type(), is(Socks4CommandType.CONNECT)); if (testMode != TestMode.INTERMEDIARY) { ctx.pipeline().addBefore(ctx.name(), "lineDecoder", new LineBasedFrameDecoder(64, false, true)); @@ -87,18 +88,20 @@ final class Socks4ProxyServer extends ProxyServer { @Override protected boolean handleProxyProtocol(ChannelHandlerContext ctx, Object msg) throws Exception { - Socks4CmdRequest req = (Socks4CmdRequest) msg; - Socks4CmdResponse res; + Socks4CommandRequest req = (Socks4CommandRequest) msg; + Socks4CommandResponse res; if (!authenticate(ctx, req)) { - res = new Socks4CmdResponse(Socks4CmdStatus.IDENTD_AUTH_FAILURE); + res = new DefaultSocks4CommandResponse(Socks4CommandStatus.IDENTD_AUTH_FAILURE); } else { - res = new Socks4CmdResponse(Socks4CmdStatus.SUCCESS); - intermediaryDestination = new InetSocketAddress(req.host(), req.port()); + res = new DefaultSocks4CommandResponse(Socks4CommandStatus.SUCCESS); + intermediaryDestination = new InetSocketAddress(req.dstAddr(), req.dstPort()); } ctx.write(res); - ctx.pipeline().remove(Socks4MessageEncoder.class); + + ctx.pipeline().remove(Socks4ServerDecoder.class); + ctx.pipeline().remove(Socks4ServerEncoder.class); return true; } @@ -112,23 +115,25 @@ final class Socks4ProxyServer extends ProxyServer { private final class Socks4TerminalHandler extends TerminalHandler { @Override protected boolean handleProxyProtocol(ChannelHandlerContext ctx, Object msg) throws Exception { - Socks4CmdRequest req = (Socks4CmdRequest) msg; + Socks4CommandRequest req = (Socks4CommandRequest) msg; boolean authzSuccess = authenticate(ctx, req); - Socks4CmdResponse res; + Socks4CommandResponse res; boolean sendGreeting = false; if (!authzSuccess) { - res = new Socks4CmdResponse(Socks4CmdStatus.IDENTD_AUTH_FAILURE); - } else if (!req.host().equals(destination.getHostString()) || - req.port() != destination.getPort()) { - res = new Socks4CmdResponse(Socks4CmdStatus.REJECTED_OR_FAILED); + res = new DefaultSocks4CommandResponse(Socks4CommandStatus.IDENTD_AUTH_FAILURE); + } else if (!req.dstAddr().equals(destination.getHostString()) || + req.dstPort() != destination.getPort()) { + res = new DefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED); } else { - res = new Socks4CmdResponse(Socks4CmdStatus.SUCCESS); + res = new DefaultSocks4CommandResponse(Socks4CommandStatus.SUCCESS); sendGreeting = true; } ctx.write(res); - ctx.pipeline().remove(Socks4MessageEncoder.class); + + ctx.pipeline().remove(Socks4ServerDecoder.class); + ctx.pipeline().remove(Socks4ServerEncoder.class); if (sendGreeting) { ctx.write(Unpooled.copiedBuffer("0\n", CharsetUtil.US_ASCII)); diff --git a/handler-proxy/src/test/java/io/netty/handler/proxy/Socks5ProxyServer.java b/handler-proxy/src/test/java/io/netty/handler/proxy/Socks5ProxyServer.java index 4b5caa5ce9..94924b835c 100644 --- a/handler-proxy/src/test/java/io/netty/handler/proxy/Socks5ProxyServer.java +++ b/handler-proxy/src/test/java/io/netty/handler/proxy/Socks5ProxyServer.java @@ -21,31 +21,35 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.LineBasedFrameDecoder; +import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse; +import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialResponse; +import io.netty.handler.codec.socksx.v5.DefaultSocks5PasswordAuthResponse; import io.netty.handler.codec.socksx.v5.Socks5AddressType; -import io.netty.handler.codec.socksx.v5.Socks5AuthRequest; -import io.netty.handler.codec.socksx.v5.Socks5AuthRequestDecoder; -import io.netty.handler.codec.socksx.v5.Socks5AuthResponse; -import io.netty.handler.codec.socksx.v5.Socks5AuthScheme; -import io.netty.handler.codec.socksx.v5.Socks5AuthStatus; -import io.netty.handler.codec.socksx.v5.Socks5CmdRequest; -import io.netty.handler.codec.socksx.v5.Socks5CmdRequestDecoder; -import io.netty.handler.codec.socksx.v5.Socks5CmdResponse; -import io.netty.handler.codec.socksx.v5.Socks5CmdStatus; -import io.netty.handler.codec.socksx.v5.Socks5CmdType; -import io.netty.handler.codec.socksx.v5.Socks5InitRequest; -import io.netty.handler.codec.socksx.v5.Socks5InitRequestDecoder; -import io.netty.handler.codec.socksx.v5.Socks5InitResponse; -import io.netty.handler.codec.socksx.v5.Socks5MessageEncoder; +import io.netty.handler.codec.socksx.v5.Socks5AuthMethod; +import io.netty.handler.codec.socksx.v5.Socks5CommandRequest; +import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder; +import io.netty.handler.codec.socksx.v5.Socks5CommandResponse; +import io.netty.handler.codec.socksx.v5.Socks5CommandStatus; +import io.netty.handler.codec.socksx.v5.Socks5CommandType; +import io.netty.handler.codec.socksx.v5.Socks5InitialRequest; +import io.netty.handler.codec.socksx.v5.Socks5InitialRequestDecoder; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequest; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequestDecoder; +import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus; +import io.netty.handler.codec.socksx.v5.Socks5ServerEncoder; import io.netty.util.CharsetUtil; import java.net.InetSocketAddress; import java.net.SocketAddress; -import static org.hamcrest.CoreMatchers.*; +import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; final class Socks5ProxyServer extends ProxyServer { + private static final String ENCODER = "encoder"; + private static final String DECODER = "decoder"; + Socks5ProxyServer(boolean useSsl, TestMode testMode, InetSocketAddress destination) { super(useSsl, testMode, destination); } @@ -60,13 +64,13 @@ final class Socks5ProxyServer extends ProxyServer { ChannelPipeline p = ch.pipeline(); switch (testMode) { case INTERMEDIARY: - p.addLast("decoder", new Socks5InitRequestDecoder()); - p.addLast("encoder", Socks5MessageEncoder.INSTANCE); + p.addLast(DECODER, new Socks5InitialRequestDecoder()); + p.addLast(ENCODER, Socks5ServerEncoder.DEFAULT); p.addLast(new Socks5IntermediaryHandler()); break; case TERMINAL: - p.addLast("decoder", new Socks5InitRequestDecoder()); - p.addLast("encoder", Socks5MessageEncoder.INSTANCE); + p.addLast(DECODER, new Socks5InitialRequestDecoder()); + p.addLast(ENCODER, Socks5ServerEncoder.DEFAULT); p.addLast(new Socks5TerminalHandler()); break; case UNRESPONSIVE: @@ -75,28 +79,28 @@ final class Socks5ProxyServer extends ProxyServer { } } - private boolean authenticate(ChannelHandlerContext ctx, Object msg) { + boolean authenticate(ChannelHandlerContext ctx, Object msg) { if (username == null) { - ctx.pipeline().addBefore("encoder", "decoder", new Socks5CmdRequestDecoder()); - ctx.write(new Socks5InitResponse(Socks5AuthScheme.NO_AUTH)); + ctx.pipeline().replace(DECODER, DECODER, new Socks5CommandRequestDecoder()); + ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH)); return true; } - if (msg instanceof Socks5InitRequest) { - ctx.pipeline().addBefore("encoder", "decoder", new Socks5AuthRequestDecoder()); - ctx.write(new Socks5InitResponse(Socks5AuthScheme.AUTH_PASSWORD)); + if (msg instanceof Socks5InitialRequest) { + ctx.pipeline().replace(DECODER, DECODER, new Socks5PasswordAuthRequestDecoder()); + ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.PASSWORD)); return false; } - Socks5AuthRequest req = (Socks5AuthRequest) msg; + Socks5PasswordAuthRequest req = (Socks5PasswordAuthRequest) msg; if (req.username().equals(username) && req.password().equals(password)) { - ctx.pipeline().addBefore("encoder", "decoder", new Socks5CmdRequestDecoder()); - ctx.write(new Socks5AuthResponse(Socks5AuthStatus.SUCCESS)); + ctx.pipeline().replace(DECODER, DECODER, new Socks5CommandRequestDecoder()); + ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.SUCCESS)); return true; } - ctx.pipeline().addBefore("encoder", "decoder", new Socks5AuthRequestDecoder()); - ctx.write(new Socks5AuthResponse(Socks5AuthStatus.FAILURE)); + ctx.pipeline().replace(DECODER, DECODER, new Socks5PasswordAuthRequestDecoder()); + ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.FAILURE)); return false; } @@ -112,15 +116,17 @@ final class Socks5ProxyServer extends ProxyServer { return false; } - Socks5CmdRequest req = (Socks5CmdRequest) msg; - assertThat(req.cmdType(), is(Socks5CmdType.CONNECT)); + Socks5CommandRequest req = (Socks5CommandRequest) msg; + assertThat(req.type(), is(Socks5CommandType.CONNECT)); - Socks5CmdResponse res; - res = new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4); - intermediaryDestination = new InetSocketAddress(req.host(), req.port()); + Socks5CommandResponse res = + new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4); + intermediaryDestination = new InetSocketAddress(req.dstAddr(), req.dstPort()); ctx.write(res); - ctx.pipeline().remove(Socks5MessageEncoder.class); + + ctx.pipeline().remove(ENCODER); + ctx.pipeline().remove(DECODER); return true; } @@ -142,23 +148,25 @@ final class Socks5ProxyServer extends ProxyServer { return false; } - Socks5CmdRequest req = (Socks5CmdRequest) msg; - assertThat(req.cmdType(), is(Socks5CmdType.CONNECT)); + Socks5CommandRequest req = (Socks5CommandRequest) msg; + assertThat(req.type(), is(Socks5CommandType.CONNECT)); ctx.pipeline().addBefore(ctx.name(), "lineDecoder", new LineBasedFrameDecoder(64, false, true)); - Socks5CmdResponse res; + Socks5CommandResponse res; boolean sendGreeting = false; - if (!req.host().equals(destination.getHostString()) || - req.port() != destination.getPort()) { - res = new Socks5CmdResponse(Socks5CmdStatus.FORBIDDEN, Socks5AddressType.IPv4); + if (!req.dstAddr().equals(destination.getHostString()) || + req.dstPort() != destination.getPort()) { + res = new DefaultSocks5CommandResponse(Socks5CommandStatus.FORBIDDEN, Socks5AddressType.IPv4); } else { - res = new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4); + res = new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4); sendGreeting = true; } ctx.write(res); - ctx.pipeline().remove(Socks5MessageEncoder.class); + + ctx.pipeline().remove(ENCODER); + ctx.pipeline().remove(DECODER); if (sendGreeting) { ctx.write(Unpooled.copiedBuffer("0\n", CharsetUtil.US_ASCII));