Revamp io.netty.handler.codec.socksx

While implementing netty-handler-proxy, I realized various issues in our
current socksx package. Here's the list of the modifications and their
background:

- Split message types into interfaces and default implementations
  - so that a user can implement an alternative message implementations
- Use classes instead of enums when a user might want to define a new
  constant
  - so that a user can extend SOCKS5 protocol, such as:
    - defining a new error code
    - defining a new address type
- Rename the message classes
  - to avoid abbreviated class names. e.g:
    - Cmd -> Command
    - Init -> Initial
  - so that the class names align better with the protocol
    specifications. e.g:
    - AuthRequest -> PasswordAuthRequest
    - AuthScheme -> AuthMethod
- Rename the property names of the messages
  - so that the property names align better when the field names in the
    protocol specifications
- Improve the decoder implementations
  - Give a user more control over when a decoder has to be removed
  - Use DecoderResult and DecoderResultProvider to handle decode failure
    gracefully. i.e. no more Unknown* message classes
- Add SocksPortUnifinicationServerHandler since it's useful to the users
  who write a SOCKS server
  - Cleaned up and moved from the socksproxy example
This commit is contained in:
Trustin Lee 2014-12-22 22:25:28 +09:00
parent 865a83c15d
commit 976db9269d
104 changed files with 3623 additions and 2967 deletions

View File

@ -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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -16,27 +16,25 @@
package io.netty.handler.codec.socksx; package io.netty.handler.codec.socksx;
public enum SocksProtocolVersion { import io.netty.handler.codec.DecoderResult;
SOCKS4a((byte) 0x04),
SOCKS5((byte) 0x05),
UNKNOWN((byte) 0xff);
private final byte b; /**
* An abstract {@link SocksMessage}.
*/
public abstract class AbstractSocksMessage implements SocksMessage {
SocksProtocolVersion(byte b) { private DecoderResult decoderResult = DecoderResult.SUCCESS;
this.b = b;
@Override
public DecoderResult decoderResult() {
return decoderResult;
} }
public static SocksProtocolVersion valueOf(byte b) { @Override
for (SocksProtocolVersion code : values()) { public void setDecoderResult(DecoderResult decoderResult) {
if (code.b == b) { if (decoderResult == null) {
return code; throw new NullPointerException("decoderResult");
}
} }
return UNKNOWN; this.decoderResult = decoderResult;
}
public byte byteValue() {
return b;
} }
} }

View File

@ -15,40 +15,15 @@
*/ */
package io.netty.handler.codec.socksx; package io.netty.handler.codec.socksx;
import io.netty.handler.codec.DecoderResultProvider;
/** /**
* An abstract class that defines a SocksMessage, providing common properties for * An interface that all SOCKS protocol messages implement.
* {@link SocksRequest} and {@link SocksResponse}.
*/ */
public abstract class SocksMessage { public interface SocksMessage extends DecoderResultProvider {
private final SocksMessageType type;
private final SocksProtocolVersion protocolVersion;
protected SocksMessage(SocksProtocolVersion protocolVersion, SocksMessageType type) {
if (protocolVersion == null) {
throw new NullPointerException("protocolVersion");
}
if (type == null) {
throw new NullPointerException("type");
}
this.protocolVersion = protocolVersion;
this.type = type;
}
/** /**
* Returns the {@link SocksMessageType} of this {@link SocksMessage} * Returns the protocol version of this message.
*
* @return The {@link SocksMessageType} of this {@link SocksMessage}
*/ */
public SocksMessageType type() { SocksVersion version();
return type;
}
/**
* Returns the {@link SocksProtocolVersion} of this {@link SocksMessage}
*
* @return The {@link SocksProtocolVersion} of this {@link SocksMessage}
*/
public SocksProtocolVersion protocolVersion() {
return protocolVersion;
}
} }

View File

@ -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
}

View File

@ -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<Object> 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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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, * 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 * 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 * License for the specific language governing permissions and limitations
* under the License. * under the License.
*/ */
package io.netty.handler.codec.socksx.v4; 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. * An abstract {@link Socks4Message}.
*
* @see Socks4CmdResponseDecoder
*/ */
public final class UnknownSocks4Response extends Socks4Response { public abstract class AbstractSocks4Message extends AbstractSocksMessage implements Socks4Message {
public static final UnknownSocks4Response INSTANCE = new UnknownSocks4Response();
private UnknownSocks4Response() { }
@Override @Override
void encodeAsByteBuf(ByteBuf byteBuf) { public final SocksVersion version() {
// NOOP return SocksVersion.SOCKS4a;
} }
} }

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<State> {
enum State {
START,
SUCCESS,
FAILURE
}
public Socks4ClientDecoder() {
super(State.START);
setSingleDecode(true);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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<Object> 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);
}
}

View File

@ -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<Socks4CommandRequest> {
/**
* 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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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);
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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;
}
}

View File

@ -15,27 +15,28 @@
*/ */
package io.netty.handler.codec.socksx.v4; package io.netty.handler.codec.socksx.v4;
public enum Socks4CmdType { /**
CONNECT((byte) 0x01), * A SOCKS4a {@code CONNECT} or {@code BIND} request.
BIND((byte) 0x02), */
UNKNOWN((byte) 0xff); 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()) { * Returns the {@code DSTIP} field of this request.
if (code.b == b) { */
return code; String dstAddr();
}
}
return UNKNOWN;
}
public byte byteValue() { /**
return b; * Returns the {@code DSTPORT} field of this request.
} */
int dstPort();
} }

View File

@ -15,21 +15,23 @@
*/ */
package io.netty.handler.codec.socksx.v4; package io.netty.handler.codec.socksx.v4;
import io.netty.buffer.ByteBuf;
/** /**
* An unknown socks request. * A SOCKS4a response.
*
* @see Socks4CmdRequestDecoder
*/ */
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) { * Returns the {@code DSTPORT} field of this response.
// NOOP */
} int dstPort();
} }

View File

@ -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<Socks4CommandStatus> {
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;
}
}

View File

@ -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<Socks4CommandType> {
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;
}
}

View File

@ -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);
}
}

View File

@ -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, * 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 * version 2.0 (the "License"); you may not use this file except in compliance
@ -14,14 +14,13 @@
* under the License. * 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 { public interface Socks4Message extends SocksMessage {
INIT, // Tag interface
AUTH,
CMD,
UNKNOWN
} }

View File

@ -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<SocksMessage> {
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();
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<State> {
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<Object> 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<Object> 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;
}
}

View File

@ -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<Socks4CommandResponse> {
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()));
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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<Socks5AuthMethod> authMethods;
public DefaultSocks5InitialRequest(Socks5AuthMethod... authMethods) {
if (authMethods == null) {
throw new NullPointerException("authMethods");
}
List<Socks5AuthMethod> list = new ArrayList<Socks5AuthMethod>(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<Socks5AuthMethod> authMethods) {
if (authMethods == null) {
throw new NullPointerException("authSchemes");
}
List<Socks5AuthMethod> list = new ArrayList<Socks5AuthMethod>();
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<Socks5AuthMethod> 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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -16,29 +16,74 @@
package io.netty.handler.codec.socksx.v5; package io.netty.handler.codec.socksx.v5;
public enum Socks5AddressType { /**
IPv4((byte) 0x01), * The type of address in {@link Socks5CommandRequest} and {@link Socks5CommandResponse}.
DOMAIN((byte) 0x03), */
IPv6((byte) 0x04), public class Socks5AddressType implements Comparable<Socks5AddressType> {
UNKNOWN((byte) 0xff);
private final byte b; public static final Socks5AddressType IPv4 = new Socks5AddressType(0x01, "IPv4");
public static final Socks5AddressType DOMAIN = new Socks5AddressType(0x03, "DOMAIN");
Socks5AddressType(byte b) { public static final Socks5AddressType IPv6 = new Socks5AddressType(0x04, "IPv6");
this.b = b;
}
public static Socks5AddressType valueOf(byte b) { public static Socks5AddressType valueOf(byte b) {
for (Socks5AddressType code : values()) { switch (b) {
if (code.b == b) { case 0x01:
return code; 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() { 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;
} }
} }

View File

@ -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<Socks5AuthMethod> {
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;
}
}

View File

@ -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));
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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());
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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<Socks5Message> {
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<Socks5AuthMethod> 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());
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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;
}
}
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -15,25 +15,29 @@
*/ */
package io.netty.handler.codec.socksx.v5; package io.netty.handler.codec.socksx.v5;
import io.netty.buffer.ByteBuf;
/** /**
* An unknown socks request. * A SOCKS5 request detail message, as defined in
* * <a href="http://tools.ietf.org/html/rfc1928#section-4">the section 4, RFC1928</a>.
* @see Socks5InitRequestDecoder
* @see Socks5AuthRequestDecoder
* @see Socks5CmdRequestDecoder
*/ */
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) { * Returns the {@code DST.ADDR} field of this request.
// NOOP */
} String dstAddr();
/**
* Returns the {@code DST.PORT} field of this request.
*/
int dstPort();
} }

View File

@ -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<State> {
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<Object> 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<Object> 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);
}
}

View File

@ -15,25 +15,29 @@
*/ */
package io.netty.handler.codec.socksx.v5; package io.netty.handler.codec.socksx.v5;
import io.netty.buffer.ByteBuf;
/** /**
* An unknown socks response. * A response to a SOCKS5 request detail message, as defined in
* * <a href="http://tools.ietf.org/html/rfc1928#section-6">the section 6, RFC1928</a>.
* @see Socks5InitResponseDecoder
* @see Socks5AuthResponseDecoder
* @see Socks5CmdResponseDecoder
*/ */
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) { * Returns the {@code BND.ADDR} field of this response.
// NOOP */
} String bndAddr();
/**
* Returns the {@code BND.PORT} field of this response.
*/
int bndPort();
} }

View File

@ -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<State> {
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<Object> 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<Object> 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);
}
}

View File

@ -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<Socks5CommandStatus> {
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;
}
}

View File

@ -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<Socks5CommandType> {
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;
}
}

View File

@ -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);
}
}

View File

@ -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<Socks5AuthScheme> authSchemes;
public Socks5InitRequest(List<Socks5AuthScheme> 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<Socks5AuthScheme> 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());
}
}
}

View File

@ -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<State> {
private final List<Socks5AuthScheme> authSchemes = new ArrayList<Socks5AuthScheme>();
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<Object> 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
}
}

View File

@ -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());
}
}

View File

@ -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<State> {
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<Object> 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
}
}

View File

@ -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
* <a href="http://tools.ietf.org/html/rfc1928#section-3">the section 3, RFC1928</a>.
*/
public interface Socks5InitialRequest extends Socks5Message {
/**
* Returns the list of desired authentication methods.
*/
List<Socks5AuthMethod> authMethods();
}

View File

@ -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<State> {
enum State {
INIT,
SUCCESS,
FAILURE
}
public Socks5InitialRequestDecoder() {
super(State.INIT);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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<Object> 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);
}
}

View File

@ -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
* <a href="http://tools.ietf.org/html/rfc1928#section-3">the section 3, RFC1928</a>.
*/
public interface Socks5InitialResponse extends Socks5Message {
/**
* Returns the {@code METHOD} field of this response.
*/
Socks5AuthMethod authMethod();
}

View File

@ -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<State> {
enum State {
INIT,
SUCCESS,
FAILURE
}
public Socks5InitialResponseDecoder() {
super(State.INIT);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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<Object> 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);
}
}

View File

@ -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, * 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 * 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; 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 { public interface Socks5Message extends SocksMessage {
INIT, // Tag interface
AUTH,
CMD,
UNKNOWN
} }

View File

@ -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<SocksMessage> {
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();
}
}
}

View File

@ -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
* <a href="http://tools.ietf.org/html/rfc1929#section-2">the section 2, RFC1929</a>.
*/
public interface Socks5PasswordAuthRequest extends Socks5Message {
/**
* Returns the username of this request.
*/
String username();
/**
* Returns the password of this request.
*/
String password();
}

View File

@ -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<State> {
enum State {
INIT,
SUCCESS,
FAILURE
}
public Socks5PasswordAuthRequestDecoder() {
super(State.INIT);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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<Object> 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);
}
}

View File

@ -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
* <a href="http://tools.ietf.org/html/rfc1929#section-2">the section 2, RFC1929</a>.
*/
public interface Socks5PasswordAuthResponse extends Socks5Message {
/**
* Returns the status of this response.
*/
Socks5PasswordAuthStatus status();
}

View File

@ -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<State> {
enum State {
INIT,
SUCCESS,
FAILURE
}
public Socks5PasswordAuthResponseDecoder() {
super(State.INIT);
}
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> 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<Object> 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);
}
}

View File

@ -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<Socks5PasswordAuthStatus> {
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;
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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<Socks5Message> {
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());
}
}

View File

@ -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;
}
}

View File

@ -22,23 +22,21 @@ import org.slf4j.LoggerFactory;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks4CmdResponseDecoderTest { public class Socks4ClientDecoderTest {
private static final Logger logger = LoggerFactory.getLogger(Socks4CmdResponseDecoderTest.class); private static final Logger logger = LoggerFactory.getLogger(Socks4ClientDecoderTest.class);
private static void testSocksCmdResponseDecoderWithDifferentParams( private static void test(Socks4CommandStatus cmdStatus, String dstAddr, int dstPort) {
Socks4CmdStatus cmdStatus, String host, int port) {
logger.debug("Testing cmdStatus: " + cmdStatus); logger.debug("Testing cmdStatus: " + cmdStatus);
Socks4Response msg = new Socks4CmdResponse(cmdStatus, host, port); Socks4CommandResponse msg = new DefaultSocks4CommandResponse(cmdStatus, dstAddr, dstPort);
Socks4CmdResponseDecoder decoder = new Socks4CmdResponseDecoder(); EmbeddedChannel embedder = new EmbeddedChannel(new Socks4ClientDecoder());
EmbeddedChannel embedder = new EmbeddedChannel(decoder);
Socks4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); Socks4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
msg = embedder.readInbound(); msg = embedder.readInbound();
assertEquals(((Socks4CmdResponse) msg).cmdStatus(), cmdStatus); assertEquals(msg.status(), cmdStatus);
if (host != null) { if (dstAddr != null) {
assertEquals(((Socks4CmdResponse) msg).host(), host); assertEquals(msg.dstAddr(), dstAddr);
} }
assertEquals(((Socks4CmdResponse) msg).port(), port); assertEquals(msg.dstPort(), dstPort);
assertNull(embedder.readInbound()); assertNull(embedder.readInbound());
} }
@ -47,8 +45,9 @@ public class Socks4CmdResponseDecoderTest {
*/ */
@Test @Test
public void testSocksCmdResponseDecoder() { public void testSocksCmdResponseDecoder() {
for (Socks4CmdStatus cmdStatus : Socks4CmdStatus.values()) { test(Socks4CommandStatus.IDENTD_AUTH_FAILURE, null, 0);
testSocksCmdResponseDecoderWithDifferentParams(cmdStatus, null, 0); test(Socks4CommandStatus.IDENTD_UNREACHABLE, null, 0);
} test(Socks4CommandStatus.REJECTED_OR_FAILED, null, 0);
test(Socks4CommandStatus.SUCCESS, null, 0);
} }
} }

View File

@ -15,8 +15,6 @@
*/ */
package io.netty.handler.codec.socksx.v4; package io.netty.handler.codec.socksx.v4;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
final class Socks4CommonTestUtils { final class Socks4CommonTestUtils {
@ -27,15 +25,15 @@ final class Socks4CommonTestUtils {
//NOOP //NOOP
} }
public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks4Request msg) { public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks4Message msg) {
ByteBuf buf = Unpooled.buffer(); EmbeddedChannel out;
msg.encodeAsByteBuf(buf); if (msg instanceof Socks4CommandRequest) {
embedder.writeInbound(buf); out = new EmbeddedChannel(Socks4ClientEncoder.INSTANCE);
} } else {
out = new EmbeddedChannel(Socks4ServerEncoder.INSTANCE);
public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks4Response msg) { }
ByteBuf buf = Unpooled.buffer(); out.writeOutbound(msg);
msg.encodeAsByteBuf(buf); embedder.writeInbound(out.readOutbound());
embedder.writeInbound(buf); out.finish();
} }
} }

View File

@ -20,40 +20,41 @@ import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.Test; import org.junit.Test;
import java.util.Arrays;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks4CmdRequestDecoderTest { public class Socks4ServerDecoderTest {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks4CmdRequestDecoderTest.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks4ServerDecoderTest.class);
private static void testSocksV4CmdRequestDecoderWithDifferentParams(String userId, private static void test(String userId, Socks4CommandType type, String dstAddr, int dstPort) {
Socks4CmdType cmdType, logger.debug(
String host, "Testing type: " + type + " dstAddr: " + dstAddr + " dstPort: " + dstPort +
int port) { " userId: " + userId);
logger.debug("Testing cmdType: " + cmdType + " userId: " + userId + " host: " + host +
" port: " + port); Socks4CommandRequest msg = new DefaultSocks4CommandRequest(type, dstAddr, dstPort, userId);
Socks4CmdRequest msg = new Socks4CmdRequest(userId, cmdType, host, port); EmbeddedChannel embedder = new EmbeddedChannel(new Socks4ServerDecoder());
Socks4CmdRequestDecoder decoder = new Socks4CmdRequestDecoder();
EmbeddedChannel embedder = new EmbeddedChannel(decoder);
Socks4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); Socks4CommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
Object obj = embedder.readInbound(); msg = embedder.readInbound();
msg = (Socks4CmdRequest) obj; assertSame(msg.type(), type);
assertSame(msg.cmdType(), cmdType); assertEquals(msg.dstAddr(), dstAddr);
assertEquals(msg.dstPort(), dstPort);
assertEquals(msg.userId(), userId); assertEquals(msg.userId(), userId);
assertEquals(msg.host(), host);
assertEquals(msg.port(), port);
assertNull(embedder.readInbound()); assertNull(embedder.readInbound());
} }
@Test @Test
public void testCmdRequestDecoder() { public void testCmdRequestDecoder() {
String[] hosts = {"127.0.0.1", }; String[] hosts = { "127.0.0.1", };
String[] userIds = {"test", }; String[] userIds = { "test", };
int[] ports = {1, 32769, 65535}; 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 userId : userIds) {
for (String host : hosts) { for (String host : hosts) {
for (int port : ports) { for (int port : ports) {
testSocksV4CmdRequestDecoderWithDifferentParams(userId, cmdType, host, port); test(userId, cmdType, host, port);
} }
} }
} }

View File

@ -19,23 +19,23 @@ import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks5CmdRequestTest { public class DefaultSocks5CommandRequestTest {
@Test @Test
public void testConstructorParamsAreNotNull() { public void testConstructorParamsAreNotNull() {
try { try {
new Socks5CmdRequest(null, Socks5AddressType.UNKNOWN, "", 1); new DefaultSocks5CommandRequest(null, Socks5AddressType.DOMAIN, "", 1);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
try { try {
new Socks5CmdRequest(Socks5CmdType.UNKNOWN, null, "", 1); new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, null, "", 1);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
try { try {
new Socks5CmdRequest(Socks5CmdType.UNKNOWN, Socks5AddressType.UNKNOWN, null, 1); new DefaultSocks5CommandRequest(Socks5CommandType.CONNECT, Socks5AddressType.DOMAIN, null, 1);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
@ -44,7 +44,7 @@ public class Socks5CmdRequestTest {
@Test @Test
public void testIPv4CorrectAddress() { public void testIPv4CorrectAddress() {
try { 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) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
@ -53,7 +53,7 @@ public class Socks5CmdRequestTest {
@Test @Test
public void testIPv6CorrectAddress() { public void testIPv6CorrectAddress() {
try { try {
new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.IPv6, "xxx:xxx:xxx", 1); new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.IPv6, "xxx:xxx:xxx", 1);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
@ -62,7 +62,7 @@ public class Socks5CmdRequestTest {
@Test @Test
public void testIDNNotExceeds255CharsLimit() { public void testIDNNotExceeds255CharsLimit() {
try { try {
new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.DOMAIN, new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.DOMAIN,
"παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" +
"παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" +
"παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" + "παράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμήπαράδειγμα.δοκιμή" +
@ -75,14 +75,14 @@ public class Socks5CmdRequestTest {
@Test @Test
public void testValidPortRange() { public void testValidPortRange() {
try { try {
new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.DOMAIN, new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.DOMAIN,
"παράδειγμα.δοκιμήπαράδει", 0); "παράδειγμα.δοκιμήπαράδει", 0);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
try { try {
new Socks5CmdRequest(Socks5CmdType.BIND, Socks5AddressType.DOMAIN, new DefaultSocks5CommandRequest(Socks5CommandType.BIND, Socks5AddressType.DOMAIN,
"παράδειγμα.δοκιμήπαράδει", 65536); "παράδειγμα.δοκιμήπαράδει", 65536);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);

View File

@ -16,21 +16,20 @@
package io.netty.handler.codec.socksx.v5; package io.netty.handler.codec.socksx.v5;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks5CmdResponseTest { public class DefaultSocks5CommandResponseTest {
@Test @Test
public void testConstructorParamsAreNotNull() { public void testConstructorParamsAreNotNull() {
try { try {
new Socks5CmdResponse(null, Socks5AddressType.UNKNOWN); new DefaultSocks5CommandResponse(null, Socks5AddressType.DOMAIN);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
try { try {
new Socks5CmdResponse(Socks5CmdStatus.UNASSIGNED, null); new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, null);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
@ -41,12 +40,12 @@ public class Socks5CmdResponseTest {
*/ */
@Test @Test
public void testEmptyDomain() { public void testEmptyDomain() {
Socks5CmdResponse socks5CmdResponse = new Socks5CmdResponse( Socks5CommandResponse socks5CmdResponse = new DefaultSocks5CommandResponse(
Socks5CmdStatus.SUCCESS, Socks5AddressType.DOMAIN); Socks5CommandStatus.SUCCESS, Socks5AddressType.DOMAIN);
assertNull(socks5CmdResponse.host()); assertNull(socks5CmdResponse.bndAddr());
assertEquals(0, socks5CmdResponse.port()); assertEquals(0, socks5CmdResponse.bndPort());
ByteBuf buffer = Unpooled.buffer(20);
socks5CmdResponse.encodeAsByteBuf(buffer); ByteBuf buffer = Socks5CommonTestUtils.encodeServer(socks5CmdResponse);
byte[] expected = { byte[] expected = {
0x05, // version 0x05, // version
0x00, // success reply 0x00, // success reply
@ -65,12 +64,12 @@ public class Socks5CmdResponseTest {
*/ */
@Test @Test
public void testIPv4Host() { public void testIPv4Host() {
Socks5CmdResponse socks5CmdResponse = new Socks5CmdResponse( Socks5CommandResponse socks5CmdResponse = new DefaultSocks5CommandResponse(
Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0.1", 80); Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0.1", 80);
assertEquals("127.0.0.1", socks5CmdResponse.host()); assertEquals("127.0.0.1", socks5CmdResponse.bndAddr());
assertEquals(80, socks5CmdResponse.port()); assertEquals(80, socks5CmdResponse.bndPort());
ByteBuf buffer = Unpooled.buffer(20);
socks5CmdResponse.encodeAsByteBuf(buffer); ByteBuf buffer = Socks5CommonTestUtils.encodeServer(socks5CmdResponse);
byte[] expected = { byte[] expected = {
0x05, // version 0x05, // version
0x00, // success reply 0x00, // success reply
@ -91,12 +90,12 @@ public class Socks5CmdResponseTest {
*/ */
@Test @Test
public void testEmptyBoundAddress() { public void testEmptyBoundAddress() {
Socks5CmdResponse socks5CmdResponse = new Socks5CmdResponse( Socks5CommandResponse socks5CmdResponse = new DefaultSocks5CommandResponse(
Socks5CmdStatus.SUCCESS, Socks5AddressType.DOMAIN, "", 80); Socks5CommandStatus.SUCCESS, Socks5AddressType.DOMAIN, "", 80);
assertEquals("", socks5CmdResponse.host()); assertEquals("", socks5CmdResponse.bndAddr());
assertEquals(80, socks5CmdResponse.port()); assertEquals(80, socks5CmdResponse.bndPort());
ByteBuf buffer = Unpooled.buffer(20);
socks5CmdResponse.encodeAsByteBuf(buffer); ByteBuf buffer = Socks5CommonTestUtils.encodeServer(socks5CmdResponse);
byte[] expected = { byte[] expected = {
0x05, // version 0x05, // version
0x00, // success reply 0x00, // success reply
@ -114,7 +113,8 @@ public class Socks5CmdResponseTest {
*/ */
@Test(expected = IllegalArgumentException.class) @Test(expected = IllegalArgumentException.class)
public void testInvalidBoundAddress() { 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) { private static void assertByteBufEquals(byte[] expected, ByteBuf actual) {
@ -127,13 +127,13 @@ public class Socks5CmdResponseTest {
@Test @Test
public void testValidPortRange() { public void testValidPortRange() {
try { try {
new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 0); new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 0);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
try { try {
new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 65536); new DefaultSocks5CommandResponse(Socks5CommandStatus.SUCCESS, Socks5AddressType.IPv4, "127.0.0", 65536);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }

View File

@ -18,13 +18,13 @@ package io.netty.handler.codec.socksx.v5;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class Socks5InitRequestTest { public class DefaultSocks5InitialRequestTest {
@Test @Test
public void testConstructorParamsAreNotNull() { public void testConstructorParamsAreNotEmpty() {
try { try {
new Socks5InitRequest(null); new DefaultSocks5InitialRequest();
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof IllegalArgumentException);
} }
} }
} }

View File

@ -18,11 +18,11 @@ package io.netty.handler.codec.socksx.v5;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class Socks5InitResponseTest { public class DefaultSocks5InitialResponseTest {
@Test @Test
public void testConstructorParamsAreNotNull() { public void testConstructorParamsAreNotNull() {
try { try {
new Socks5InitResponse(null); new DefaultSocks5InitialResponse(null);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }

View File

@ -18,16 +18,16 @@ package io.netty.handler.codec.socksx.v5;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class Socks5AuthRequestTest { public class DefaultSocks5PasswordAuthRequestTest {
@Test @Test
public void testConstructorParamsAreNotNull() { public void testConstructorParamsAreNotNull() {
try { try {
new Socks5AuthRequest(null, ""); new DefaultSocks5PasswordAuthRequest(null, "");
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
try { try {
new Socks5AuthRequest("", null); new DefaultSocks5PasswordAuthRequest("", null);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
@ -36,12 +36,12 @@ public class Socks5AuthRequestTest {
@Test @Test
public void testUsernameOrPasswordIsNotAscii() { public void testUsernameOrPasswordIsNotAscii() {
try { try {
new Socks5AuthRequest("παράδειγμα.δοκιμή", "password"); new DefaultSocks5PasswordAuthRequest("παράδειγμα.δοκιμή", "password");
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
try { try {
new Socks5AuthRequest("username", "παράδειγμα.δοκιμή"); new DefaultSocks5PasswordAuthRequest("username", "παράδειγμα.δοκιμή");
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
@ -50,7 +50,7 @@ public class Socks5AuthRequestTest {
@Test @Test
public void testUsernameOrPasswordLengthIsLessThan255Chars() { public void testUsernameOrPasswordLengthIsLessThan255Chars() {
try { try {
new Socks5AuthRequest( new DefaultSocks5PasswordAuthRequest(
"passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
"passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
"passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
@ -64,7 +64,7 @@ public class Socks5AuthRequestTest {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
try { try {
new Socks5AuthRequest("password", new DefaultSocks5PasswordAuthRequest("password",
"passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
"passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
"passwordpasswordpasswordpasswordpasswordpasswordpassword" + "passwordpasswordpasswordpasswordpasswordpasswordpassword" +
@ -77,5 +77,4 @@ public class Socks5AuthRequestTest {
assertTrue(e instanceof IllegalArgumentException); assertTrue(e instanceof IllegalArgumentException);
} }
} }
} }

View File

@ -18,14 +18,13 @@ package io.netty.handler.codec.socksx.v5;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class Socks5AuthResponseTest { public class DefaultSocks5PasswordAuthResponseTest {
@Test @Test
public void testConstructorParamsAreNotNull() { public void testConstructorParamsAreNotNull() {
try { try {
new Socks5AuthResponse(null); new DefaultSocks5PasswordAuthResponse(null);
} catch (Exception e) { } catch (Exception e) {
assertTrue(e instanceof NullPointerException); assertTrue(e instanceof NullPointerException);
} }
} }
} }

View File

@ -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);
}
}
}

View File

@ -16,35 +16,36 @@
package io.netty.handler.codec.socksx.v5; package io.netty.handler.codec.socksx.v5;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.NetUtil;
import io.netty.util.internal.logging.InternalLogger; import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
import org.junit.Test; import org.junit.Test;
import sun.net.util.IPAddressUtil; import sun.net.util.IPAddressUtil;
import java.net.IDN;
import java.util.Arrays;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks5CmdRequestDecoderTest { public class Socks5CommandRequestDecoderTest {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Socks5CmdRequestDecoderTest.class); private static final InternalLogger logger =
InternalLoggerFactory.getInstance(Socks5CommandRequestDecoderTest.class);
private static void testSocksCmdRequestDecoderWithDifferentParams(Socks5CmdType cmdType, private static void test(
Socks5AddressType addressType, Socks5CommandType type, Socks5AddressType dstAddrType, String dstAddr, int dstPort) {
String host, logger.debug(
int port) { "Testing type: " + type + " dstAddrType: " + dstAddrType +
logger.debug("Testing cmdType: " + cmdType + " addressType: " + addressType + " host: " + host + " dstAddr: " + dstAddr + " dstPort: " + dstPort);
" port: " + port);
Socks5CmdRequest msg = new Socks5CmdRequest(cmdType, addressType, host, port); Socks5CommandRequest msg =
Socks5CmdRequestDecoder decoder = new Socks5CmdRequestDecoder(); new DefaultSocks5CommandRequest(type, dstAddrType, dstAddr, dstPort);
EmbeddedChannel embedder = new EmbeddedChannel(decoder); EmbeddedChannel embedder = new EmbeddedChannel(new Socks5CommandRequestDecoder());
Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg); Socks5CommonTestUtils.writeFromClientToServer(embedder, msg);
if (msg.addressType() == Socks5AddressType.UNKNOWN) { msg = embedder.readInbound();
assertTrue(embedder.readInbound() instanceof UnknownSocks5Request); assertSame(msg.type(), type);
} else { assertSame(msg.dstAddrType(), dstAddrType);
msg = embedder.readInbound(); assertEquals(msg.dstAddr(), IDN.toASCII(dstAddr));
assertSame(msg.cmdType(), cmdType); assertEquals(msg.dstPort(), dstPort);
assertSame(msg.addressType(), addressType);
assertEquals(msg.host(), host);
assertEquals(msg.port(), port);
}
assertNull(embedder.readInbound()); assertNull(embedder.readInbound());
} }
@ -52,10 +53,12 @@ public class Socks5CmdRequestDecoderTest {
public void testCmdRequestDecoderIPv4() { public void testCmdRequestDecoderIPv4() {
String[] hosts = {"127.0.0.1", }; String[] hosts = {"127.0.0.1", };
int[] ports = {1, 32769, 65535 }; 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 (String host : hosts) {
for (int port : ports) { for (int port : ports) {
testSocksCmdRequestDecoderWithDifferentParams(cmdType, Socks5AddressType.IPv4, host, port); test(cmdType, Socks5AddressType.IPv4, host, port);
} }
} }
} }
@ -63,12 +66,15 @@ public class Socks5CmdRequestDecoderTest {
@Test @Test
public void testCmdRequestDecoderIPv6() { public void testCmdRequestDecoderIPv6() {
String[] hosts = { Socks5CommonUtils.ipv6toStr(IPAddressUtil.textToNumericFormatV6("::1"))}; String[] hosts = {
NetUtil.bytesToIpAddress(IPAddressUtil.textToNumericFormatV6("::1"), 0, 16) };
int[] ports = {1, 32769, 65535}; 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 (String host : hosts) {
for (int port : ports) { 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}; 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 (String host : hosts) {
for (int port : ports) { 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);
}
}
} }

View File

@ -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);
}
}
}

View File

@ -16,7 +16,6 @@
package io.netty.handler.codec.socksx.v5; package io.netty.handler.codec.socksx.v5;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.embedded.EmbeddedChannel; import io.netty.channel.embedded.EmbeddedChannel;
final class Socks5CommonTestUtils { final class Socks5CommonTestUtils {
@ -27,15 +26,31 @@ final class Socks5CommonTestUtils {
//NOOP //NOOP
} }
public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks5Request msg) { public static void writeFromClientToServer(EmbeddedChannel embedder, Socks5Message msg) {
ByteBuf buf = Unpooled.buffer(); embedder.writeInbound(encodeClient(msg));
msg.encodeAsByteBuf(buf);
embedder.writeInbound(buf);
} }
public static void writeMessageIntoEmbedder(EmbeddedChannel embedder, Socks5Response msg) { public static void writeFromServerToClient(EmbeddedChannel embedder, Socks5Message msg) {
ByteBuf buf = Unpooled.buffer(); embedder.writeInbound(encodeServer(msg));
msg.encodeAsByteBuf(buf); }
embedder.writeInbound(buf);
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;
} }
} }

View File

@ -20,16 +20,15 @@ import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks5AuthRequestDecoderTest { public class Socks5PasswordAuthRequestDecoderTest {
@Test @Test
public void testAuthRequestDecoder() { public void testAuthRequestDecoder() {
String username = "test"; String username = "test";
String password = "test"; String password = "test";
Socks5AuthRequest msg = new Socks5AuthRequest(username, password); Socks5PasswordAuthRequest msg = new DefaultSocks5PasswordAuthRequest(username, password);
Socks5AuthRequestDecoder decoder = new Socks5AuthRequestDecoder(); EmbeddedChannel embedder = new EmbeddedChannel(new Socks5PasswordAuthRequestDecoder());
EmbeddedChannel embedder = new EmbeddedChannel(decoder); Socks5CommonTestUtils.writeFromClientToServer(embedder, msg);
Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
msg = embedder.readInbound(); msg = embedder.readInbound();
assertEquals(msg.username(), username); assertEquals(msg.username(), username);
assertEquals(msg.username(), password); assertEquals(msg.username(), password);

View File

@ -22,25 +22,23 @@ import org.junit.Test;
import static org.junit.Assert.*; import static org.junit.Assert.*;
public class Socks5AuthResponseDecoderTest { public class Socks5PasswordAuthResponseDecoderTest {
private static final InternalLogger logger = InternalLoggerFactory.getInstance( private static final InternalLogger logger = InternalLoggerFactory.getInstance(
Socks5AuthResponseDecoderTest.class); Socks5PasswordAuthResponseDecoderTest.class);
private static void testSocksAuthResponseDecoderWithDifferentParams(Socks5AuthStatus authStatus) { private static void test(Socks5PasswordAuthStatus status) {
logger.debug("Testing SocksAuthResponseDecoder with authStatus: " + authStatus); logger.debug("Testing Socks5PasswordAuthResponseDecoder with status: " + status);
Socks5AuthResponse msg = new Socks5AuthResponse(authStatus); Socks5PasswordAuthResponse msg = new DefaultSocks5PasswordAuthResponse(status);
Socks5AuthResponseDecoder decoder = new Socks5AuthResponseDecoder(); EmbeddedChannel embedder = new EmbeddedChannel(new Socks5PasswordAuthResponseDecoder());
EmbeddedChannel embedder = new EmbeddedChannel(decoder); Socks5CommonTestUtils.writeFromServerToClient(embedder, msg);
Socks5CommonTestUtils.writeMessageIntoEmbedder(embedder, msg);
msg = embedder.readInbound(); msg = embedder.readInbound();
assertSame(msg.authStatus(), authStatus); assertSame(msg.status(), status);
assertNull(embedder.readInbound()); assertNull(embedder.readInbound());
} }
@Test @Test
public void testSocksCmdResponseDecoder() { public void testSocksCmdResponseDecoder() {
for (Socks5AuthStatus authStatus: Socks5AuthStatus.values()) { test(Socks5PasswordAuthStatus.SUCCESS);
testSocksAuthResponseDecoderWithDifferentParams(authStatus); test(Socks5PasswordAuthStatus.FAILURE);
}
} }
} }

View File

@ -329,8 +329,7 @@ final class ReplayingDecoderBuffer extends ByteBuf {
@Override @Override
public int bytesBefore(int length, byte value) { public int bytesBefore(int length, byte value) {
final int readerIndex = buffer.readerIndex(); return bytesBefore(buffer.readerIndex(), length, value);
return bytesBefore(readerIndex, buffer.writerIndex() - readerIndex, value);
} }
@Override @Override

View File

@ -16,6 +16,7 @@
package io.netty.util; package io.netty.util;
import io.netty.util.internal.PlatformDependent; 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.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory; import io.netty.util.internal.logging.InternalLoggerFactory;
@ -395,8 +396,7 @@ public final class NetUtil {
ipByteArray[byteIndex + 1] |= charValue & 15; ipByteArray[byteIndex + 1] |= charValue & 15;
} }
static int getIntValue(char c) { private static int getIntValue(char c) {
switch (c) { switch (c) {
case '0': case '0':
return 0; return 0;
@ -438,6 +438,58 @@ public final class NetUtil {
return 0; 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) { public static boolean isValidIpV6Address(String ipAddress) {
int length = ipAddress.length(); int length = ipAddress.length();
boolean doubleColon = false; boolean doubleColon = false;

View File

@ -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<Object> 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);
}
}

View File

@ -24,46 +24,47 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOption; import io.netty.channel.ChannelOption;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.socksx.SocksRequest; import io.netty.handler.codec.socksx.SocksMessage;
import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandResponse;
import io.netty.handler.codec.socksx.v4.Socks4CmdResponse; import io.netty.handler.codec.socksx.v4.Socks4CommandRequest;
import io.netty.handler.codec.socksx.v4.Socks4CmdStatus; import io.netty.handler.codec.socksx.v4.Socks4CommandStatus;
import io.netty.handler.codec.socksx.v5.Socks5CmdRequest; import io.netty.handler.codec.socksx.v5.DefaultSocks5CommandResponse;
import io.netty.handler.codec.socksx.v5.Socks5CmdResponse; import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
import io.netty.handler.codec.socksx.v5.Socks5CmdStatus; import io.netty.handler.codec.socksx.v5.Socks5CommandStatus;
import io.netty.util.concurrent.Future; import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.Promise; import io.netty.util.concurrent.Promise;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public final class SocksServerConnectHandler extends SimpleChannelInboundHandler<SocksRequest> { public final class SocksServerConnectHandler extends SimpleChannelInboundHandler<SocksMessage> {
private final Bootstrap b = new Bootstrap(); private final Bootstrap b = new Bootstrap();
@Override @Override
public void channelRead0(final ChannelHandlerContext ctx, final SocksRequest message) throws Exception { public void channelRead0(final ChannelHandlerContext ctx, final SocksMessage message) throws Exception {
if (message instanceof Socks4CmdRequest) { if (message instanceof Socks4CommandRequest) {
final Socks4CmdRequest request = (Socks4CmdRequest) message; final Socks4CommandRequest request = (Socks4CommandRequest) message;
Promise<Channel> promise = ctx.executor().newPromise(); Promise<Channel> promise = ctx.executor().newPromise();
promise.addListener( promise.addListener(
new GenericFutureListener<Future<Channel>>() { new FutureListener<Channel>() {
@Override @Override
public void operationComplete(final Future<Channel> future) throws Exception { public void operationComplete(final Future<Channel> future) throws Exception {
final Channel outboundChannel = future.getNow(); final Channel outboundChannel = future.getNow();
if (future.isSuccess()) { if (future.isSuccess()) {
ctx.channel().writeAndFlush(new Socks4CmdResponse(Socks4CmdStatus.SUCCESS)) ChannelFuture responseFuture = ctx.channel().writeAndFlush(
.addListener(new ChannelFutureListener() { new DefaultSocks4CommandResponse(Socks4CommandStatus.SUCCESS));
@Override
public void operationComplete(ChannelFuture channelFuture) { responseFuture.addListener(new ChannelFutureListener() {
ctx.pipeline().remove(SocksServerConnectHandler.this); @Override
outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel())); public void operationComplete(ChannelFuture channelFuture) {
ctx.pipeline().addLast(new RelayHandler(outboundChannel)); ctx.pipeline().remove(SocksServerConnectHandler.this);
} outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel()));
}); ctx.pipeline().addLast(new RelayHandler(outboundChannel));
}
});
} else { } else {
ctx.channel().writeAndFlush( ctx.channel().writeAndFlush(
new Socks4CmdResponse(Socks4CmdStatus.REJECTED_OR_FAILED) new DefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED));
);
SocksServerUtils.closeOnFlush(ctx.channel()); SocksServerUtils.closeOnFlush(ctx.channel());
} }
} }
@ -76,7 +77,7 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler
.option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.SO_KEEPALIVE, true)
.handler(new DirectClientHandler(promise)); .handler(new DirectClientHandler(promise));
b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) { if (future.isSuccess()) {
@ -84,35 +85,36 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler
} else { } else {
// Close the connection if the connection attempt has failed. // Close the connection if the connection attempt has failed.
ctx.channel().writeAndFlush( ctx.channel().writeAndFlush(
new Socks4CmdResponse(Socks4CmdStatus.REJECTED_OR_FAILED) new DefaultSocks4CommandResponse(Socks4CommandStatus.REJECTED_OR_FAILED)
); );
SocksServerUtils.closeOnFlush(ctx.channel()); SocksServerUtils.closeOnFlush(ctx.channel());
} }
} }
}); });
} else if (message instanceof Socks5CmdRequest) { } else if (message instanceof Socks5CommandRequest) {
final Socks5CmdRequest request = (Socks5CmdRequest) message; final Socks5CommandRequest request = (Socks5CommandRequest) message;
Promise<Channel> promise = ctx.executor().newPromise(); Promise<Channel> promise = ctx.executor().newPromise();
promise.addListener( promise.addListener(
new GenericFutureListener<Future<Channel>>() { new FutureListener<Channel>() {
@Override @Override
public void operationComplete(final Future<Channel> future) throws Exception { public void operationComplete(final Future<Channel> future) throws Exception {
final Channel outboundChannel = future.getNow(); final Channel outboundChannel = future.getNow();
if (future.isSuccess()) { if (future.isSuccess()) {
ctx.channel().writeAndFlush( ChannelFuture responseFuture =
new Socks5CmdResponse(Socks5CmdStatus.SUCCESS, request.addressType()) ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
).addListener(new ChannelFutureListener() { Socks5CommandStatus.SUCCESS, request.dstAddrType()));
@Override
public void operationComplete(ChannelFuture channelFuture) { responseFuture.addListener(new ChannelFutureListener() {
ctx.pipeline().remove(SocksServerConnectHandler.this); @Override
outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel())); public void operationComplete(ChannelFuture channelFuture) {
ctx.pipeline().addLast(new RelayHandler(outboundChannel)); ctx.pipeline().remove(SocksServerConnectHandler.this);
} outboundChannel.pipeline().addLast(new RelayHandler(ctx.channel()));
} ctx.pipeline().addLast(new RelayHandler(outboundChannel));
); }
});
} else { } else {
ctx.channel().writeAndFlush( ctx.channel().writeAndFlush(new DefaultSocks5CommandResponse(
new Socks5CmdResponse(Socks5CmdStatus.FAILURE, request.addressType())); Socks5CommandStatus.FAILURE, request.dstAddrType()));
SocksServerUtils.closeOnFlush(ctx.channel()); SocksServerUtils.closeOnFlush(ctx.channel());
} }
} }
@ -125,7 +127,7 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler
.option(ChannelOption.SO_KEEPALIVE, true) .option(ChannelOption.SO_KEEPALIVE, true)
.handler(new DirectClientHandler(promise)); .handler(new DirectClientHandler(promise));
b.connect(request.host(), request.port()).addListener(new ChannelFutureListener() { b.connect(request.dstAddr(), request.dstPort()).addListener(new ChannelFutureListener() {
@Override @Override
public void operationComplete(ChannelFuture future) throws Exception { public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) { if (future.isSuccess()) {
@ -133,7 +135,7 @@ public final class SocksServerConnectHandler extends SimpleChannelInboundHandler
} else { } else {
// Close the connection if the connection attempt has failed. // Close the connection if the connection attempt has failed.
ctx.channel().writeAndFlush( ctx.channel().writeAndFlush(
new Socks5CmdResponse(Socks5CmdStatus.FAILURE, request.addressType())); new DefaultSocks5CommandResponse(Socks5CommandStatus.FAILURE, request.dstAddrType()));
SocksServerUtils.closeOnFlush(ctx.channel()); SocksServerUtils.closeOnFlush(ctx.channel());
} }
} }

View File

@ -18,31 +18,32 @@ package io.netty.example.socksproxy;
import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.socksx.SocksRequest; import io.netty.handler.codec.socksx.SocksMessage;
import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; import io.netty.handler.codec.socksx.v4.Socks4CommandRequest;
import io.netty.handler.codec.socksx.v4.Socks4CmdType; import io.netty.handler.codec.socksx.v4.Socks4CommandType;
import io.netty.handler.codec.socksx.v5.Socks5AuthScheme; import io.netty.handler.codec.socksx.v5.DefaultSocks5InitialResponse;
import io.netty.handler.codec.socksx.v5.Socks5CmdRequestDecoder; import io.netty.handler.codec.socksx.v5.DefaultSocks5PasswordAuthResponse;
import io.netty.handler.codec.socksx.v5.Socks5InitResponse; import io.netty.handler.codec.socksx.v5.Socks5AuthMethod;
import io.netty.handler.codec.socksx.v5.Socks5Request; import io.netty.handler.codec.socksx.v5.Socks5InitialRequest;
import io.netty.handler.codec.socksx.v5.Socks5AuthResponse; import io.netty.handler.codec.socksx.v5.Socks5CommandRequest;
import io.netty.handler.codec.socksx.v5.Socks5AuthStatus; import io.netty.handler.codec.socksx.v5.Socks5CommandRequestDecoder;
import io.netty.handler.codec.socksx.v5.Socks5CmdRequest; import io.netty.handler.codec.socksx.v5.Socks5CommandType;
import io.netty.handler.codec.socksx.v5.Socks5CmdType; import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthRequest;
import io.netty.handler.codec.socksx.v5.Socks5PasswordAuthStatus;
@ChannelHandler.Sharable @ChannelHandler.Sharable
public final class SocksServerHandler extends SimpleChannelInboundHandler<SocksRequest> { public final class SocksServerHandler extends SimpleChannelInboundHandler<SocksMessage> {
public static final SocksServerHandler INSTANCE = new SocksServerHandler(); public static final SocksServerHandler INSTANCE = new SocksServerHandler();
private SocksServerHandler() { } private SocksServerHandler() { }
@Override @Override
public void channelRead0(ChannelHandlerContext ctx, SocksRequest socksRequest) throws Exception { public void channelRead0(ChannelHandlerContext ctx, SocksMessage socksRequest) throws Exception {
switch (socksRequest.protocolVersion()) { switch (socksRequest.version()) {
case SOCKS4a: case SOCKS4a:
Socks4CmdRequest socksV4CmdRequest = (Socks4CmdRequest) socksRequest; Socks4CommandRequest socksV4CmdRequest = (Socks4CommandRequest) socksRequest;
if (socksV4CmdRequest.cmdType() == Socks4CmdType.CONNECT) { if (socksV4CmdRequest.type() == Socks4CommandType.CONNECT) {
ctx.pipeline().addLast(new SocksServerConnectHandler()); ctx.pipeline().addLast(new SocksServerConnectHandler());
ctx.pipeline().remove(this); ctx.pipeline().remove(this);
ctx.fireChannelRead(socksRequest); ctx.fireChannelRead(socksRequest);
@ -51,32 +52,26 @@ public final class SocksServerHandler extends SimpleChannelInboundHandler<SocksR
} }
break; break;
case SOCKS5: case SOCKS5:
switch (((Socks5Request) socksRequest).requestType()) { if (socksRequest instanceof Socks5InitialRequest) {
case INIT: { // auth support example
// auth support example //ctx.pipeline().addFirst(new Socks5PasswordAuthRequestDecoder());
//ctx.pipeline().addFirst(new SocksV5AuthRequestDecoder()); //ctx.write(new DefaultSocks5AuthMethodResponse(Socks5AuthMethod.PASSWORD));
//ctx.write(new SocksV5InitResponse(SocksV5AuthScheme.AUTH_PASSWORD)); ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
ctx.pipeline().addFirst(new Socks5CmdRequestDecoder()); ctx.write(new DefaultSocks5InitialResponse(Socks5AuthMethod.NO_AUTH));
ctx.write(new Socks5InitResponse(Socks5AuthScheme.NO_AUTH)); } else if (socksRequest instanceof Socks5PasswordAuthRequest) {
break; ctx.pipeline().addFirst(new Socks5CommandRequestDecoder());
} ctx.write(new DefaultSocks5PasswordAuthResponse(Socks5PasswordAuthStatus.SUCCESS));
case AUTH: } else if (socksRequest instanceof Socks5CommandRequest) {
ctx.pipeline().addFirst(new Socks5CmdRequestDecoder()); Socks5CommandRequest socks5CmdRequest = (Socks5CommandRequest) socksRequest;
ctx.write(new Socks5AuthResponse(Socks5AuthStatus.SUCCESS)); if (socks5CmdRequest.type() == Socks5CommandType.CONNECT) {
break; ctx.pipeline().addLast(new SocksServerConnectHandler());
case CMD: ctx.pipeline().remove(this);
Socks5CmdRequest socks5CmdRequest = (Socks5CmdRequest) socksRequest; ctx.fireChannelRead(socksRequest);
if (socks5CmdRequest.cmdType() == Socks5CmdType.CONNECT) { } else {
ctx.pipeline().addLast(new SocksServerConnectHandler());
ctx.pipeline().remove(this);
ctx.fireChannelRead(socksRequest);
} else {
ctx.close();
}
break;
case UNKNOWN:
ctx.close(); ctx.close();
break; }
} else {
ctx.close();
} }
break; break;
case UNKNOWN: case UNKNOWN:

View File

@ -16,16 +16,17 @@
package io.netty.example.socksproxy; package io.netty.example.socksproxy;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.socksx.SocksPortUnificationServerHandler;
import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler; import io.netty.handler.logging.LoggingHandler;
public final class SocksServerInitializer extends ChannelInitializer<SocketChannel> { public final class SocksServerInitializer extends ChannelInitializer<SocketChannel> {
@Override @Override
public void initChannel(SocketChannel socketChannel) throws Exception { public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = socketChannel.pipeline(); ch.pipeline().addLast(
p.addFirst(new LoggingHandler(LogLevel.DEBUG)); new LoggingHandler(LogLevel.DEBUG),
p.addLast(new SocksPortUnificationServerHandler()); new SocksPortUnificationServerHandler(),
SocksServerHandler.INSTANCE);
} }
} }

View File

@ -18,12 +18,12 @@ package io.netty.handler.proxy;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline; import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.socksx.v4.Socks4CmdRequest; import io.netty.handler.codec.socksx.v4.DefaultSocks4CommandRequest;
import io.netty.handler.codec.socksx.v4.Socks4CmdResponse; import io.netty.handler.codec.socksx.v4.Socks4ClientDecoder;
import io.netty.handler.codec.socksx.v4.Socks4CmdResponseDecoder; import io.netty.handler.codec.socksx.v4.Socks4ClientEncoder;
import io.netty.handler.codec.socksx.v4.Socks4CmdStatus; import io.netty.handler.codec.socksx.v4.Socks4CommandResponse;
import io.netty.handler.codec.socksx.v4.Socks4CmdType; import io.netty.handler.codec.socksx.v4.Socks4CommandStatus;
import io.netty.handler.codec.socksx.v4.Socks4MessageEncoder; import io.netty.handler.codec.socksx.v4.Socks4CommandType;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
@ -69,13 +69,13 @@ public final class Socks4ProxyHandler extends ProxyHandler {
ChannelPipeline p = ctx.pipeline(); ChannelPipeline p = ctx.pipeline();
String name = ctx.name(); String name = ctx.name();
Socks4CmdResponseDecoder decoder = new Socks4CmdResponseDecoder(); Socks4ClientDecoder decoder = new Socks4ClientDecoder();
p.addBefore(name, null, decoder); p.addBefore(name, null, decoder);
decoderName = p.context(decoder).name(); decoderName = p.context(decoder).name();
encoderName = decoderName + ".encoder"; encoderName = decoderName + ".encoder";
p.addBefore(name, encoderName, Socks4MessageEncoder.INSTANCE); p.addBefore(name, encoderName, Socks4ClientEncoder.INSTANCE);
} }
@Override @Override
@ -99,18 +99,18 @@ public final class Socks4ProxyHandler extends ProxyHandler {
} else { } else {
rhost = raddr.getAddress().getHostAddress(); rhost = raddr.getAddress().getHostAddress();
} }
return new Socks4CmdRequest( return new DefaultSocks4CommandRequest(
username != null? username : "", Socks4CmdType.CONNECT, rhost, raddr.getPort()); Socks4CommandType.CONNECT, rhost, raddr.getPort(), username != null? username : "");
} }
@Override @Override
protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception { protected boolean handleResponse(ChannelHandlerContext ctx, Object response) throws Exception {
final Socks4CmdResponse res = (Socks4CmdResponse) response; final Socks4CommandResponse res = (Socks4CommandResponse) response;
final Socks4CmdStatus status = res.cmdStatus(); final Socks4CommandStatus status = res.status();
if (status == Socks4CmdStatus.SUCCESS) { if (status == Socks4CommandStatus.SUCCESS) {
return true; return true;
} }
throw new ProxyConnectException(exceptionMessage("cmdStatus: " + status)); throw new ProxyConnectException(exceptionMessage("status: " + status));
} }
} }

Some files were not shown because too many files have changed in this diff Show More