package com.sissi.server.netty.impl; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.handler.codec.socks.SocksAddressType; import io.netty.handler.codec.socks.SocksAuthScheme; import io.netty.handler.codec.socks.SocksCmdRequest; import io.netty.handler.codec.socks.SocksCmdResponse; import io.netty.handler.codec.socks.SocksCmdStatus; import io.netty.handler.codec.socks.SocksInitRequest; import io.netty.handler.codec.socks.SocksInitResponse; import io.netty.handler.codec.socks.SocksResponse; import io.netty.handler.timeout.IdleState; import io.netty.handler.timeout.IdleStateEvent; import io.netty.handler.timeout.IdleStateHandler; import io.netty.util.AttributeKey; import io.netty.util.ReferenceCountUtil; import io.netty.util.concurrent.Future; import io.netty.util.concurrent.GenericFutureListener; import java.io.Closeable; import java.io.IOException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.sissi.commons.Trace; import com.sissi.pipeline.TransferBuffer; import com.sissi.protocol.iq.bytestreams.BytestreamsProxy; import com.sissi.resource.ResourceCounter; import com.sissi.server.exchange.Exchanger; import com.sissi.server.exchange.ExchangerContext; import com.sissi.server.exchange.Terminal; import com.sissi.server.netty.ChannelHandlerBuilder; /** * @author kim 2013年12月22日 */ public class Socks5ProxyServerHandlerBuilder implements ChannelHandlerBuilder { private final AttributeKey<Exchanger> exchanger = AttributeKey.valueOf("exchanger"); private final String resource = Sock5ProxyServerHandler.class.getSimpleName(); private final Log log = LogFactory.getLog(this.getClass()); private final ExchangerContext exchangerContext; private final ResourceCounter resourceCounter; private final byte[] init; private final byte[] cmd; public Socks5ProxyServerHandlerBuilder(BytestreamsProxy proxy, ExchangerContext exchangerContext, ResourceCounter resourceCounter) { super(); this.resourceCounter = resourceCounter; this.exchangerContext = exchangerContext; this.init = this.prepareStatic(this.buildInit()); this.cmd = this.prepareStatic(this.buildCmd(proxy)); } private byte[] prepareStatic(ByteBuf buf) { byte[] staticInit = new byte[buf.readableBytes()]; buf.readBytes(staticInit); return staticInit; } private ByteBuf buildInit() { ByteBuf buf = Unpooled.buffer(); new SocksInitResponse(SocksAuthScheme.NO_AUTH).encodeAsByteBuf(buf); return buf; } private ByteBuf buildCmd(BytestreamsProxy proxy) { ByteBuf buf = Unpooled.buffer(); new SocksCmdResponseWrap(proxy, new SocksCmdResponse(SocksCmdStatus.SUCCESS, SocksAddressType.DOMAIN)).encodeAsByteBuf(buf); return buf; } public Sock5ProxyServerHandler build() throws IOException { return new Sock5ProxyServerHandler(); } private class Sock5ProxyServerHandler extends ChannelInboundHandlerAdapter { public void channelRegistered(final ChannelHandlerContext ctx) throws Exception { Socks5ProxyServerHandlerBuilder.this.resourceCounter.increment(Socks5ProxyServerHandlerBuilder.this.resource); } public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { // 接收方完毕后强制关闭发起方 ctx.attr(Socks5ProxyServerHandlerBuilder.this.exchanger).get().close(Terminal.SOURCE); Socks5ProxyServerHandlerBuilder.this.resourceCounter.decrement(Socks5ProxyServerHandlerBuilder.this.resource); } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { Socks5ProxyServerHandlerBuilder.this.log.debug(cause.toString()); Trace.trace(Socks5ProxyServerHandlerBuilder.this.log, cause); ctx.close(); } public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { // 接收方超时 if (evt.getClass() == IdleStateEvent.class && IdleStateEvent.class.cast(evt).state() == IdleState.WRITER_IDLE) { ctx.close(); } } public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception { try { this.prepare(ctx, msg, ctx.write(this.bytebuf(msg))).flush(); } finally { ReferenceCountUtil.release(msg); } } private ByteBuf bytebuf(Object msg) { return msg.getClass() == SocksInitRequest.class ? Unpooled.wrappedBuffer(Socks5ProxyServerHandlerBuilder.this.init) : Unpooled.wrappedBuffer(Socks5ProxyServerHandlerBuilder.this.cmd); } private ChannelHandlerContext prepare(final ChannelHandlerContext ctx, Object msg, ChannelFuture future) throws IOException { if (msg.getClass() == SocksCmdRequest.class) { SocksCmdRequest cmd = SocksCmdRequest.class.cast(msg); return Socks5ProxyServerHandlerBuilder.this.exchangerContext.exists(cmd.host()) ? this.activate(Socks5ProxyServerHandlerBuilder.this.exchangerContext.activate(cmd.host()), future, ctx) : this.wait(cmd, ctx); } return ctx; } /** * 禁止关闭接收方,cascade = false * * @param cmd * @param ctx * @return * @throws IOException */ private ChannelHandlerContext wait(SocksCmdRequest cmd, ChannelHandlerContext ctx) throws IOException { ctx.attr(Socks5ProxyServerHandlerBuilder.this.exchanger).set(Socks5ProxyServerHandlerBuilder.this.exchangerContext.wait(cmd.host(), false, new NetworkTransfer(ctx))); return ctx; } private ChannelHandlerContext activate(Exchanger exchanger, ChannelFuture future, ChannelHandlerContext ctx) throws IOException { future.addListener(new BridgeExchangeListener(ctx, exchanger)); return ctx; } private class BridgeExchangeListener implements GenericFutureListener<Future<Void>> { private final ChannelHandlerContext ctx; private final Exchanger exchanger; public BridgeExchangeListener(ChannelHandlerContext ctx, Exchanger exchanger) { super(); this.ctx = ctx; this.exchanger = exchanger.source(new ChannelHandlerContextCloseable(ctx)); } @Override public void operationComplete(Future<Void> future) throws Exception { if (future.isSuccess()) { ctx.pipeline().remove(IdleStateHandler.class); ctx.pipeline().addFirst(new BridgeExchangerServerHandler()); ctx.pipeline().context(BridgeExchangerServerHandler.class).attr(Socks5ProxyServerHandlerBuilder.this.exchanger).set(this.exchanger); } } } } @Sharable private class BridgeExchangerServerHandler extends ChannelInboundHandlerAdapter { public void channelUnregistered(final ChannelHandlerContext ctx) throws Exception { // 尝试关闭接收方 ctx.attr(Socks5ProxyServerHandlerBuilder.this.exchanger).get().close(Terminal.TARGET); Socks5ProxyServerHandlerBuilder.this.resourceCounter.decrement(Socks5ProxyServerHandlerBuilder.this.resource); } public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { Socks5ProxyServerHandlerBuilder.this.log.debug(cause.toString()); Trace.trace(Socks5ProxyServerHandlerBuilder.this.log, cause); ctx.close(); } public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { try { ctx.attr(Socks5ProxyServerHandlerBuilder.this.exchanger).get().write(new ByteBufWrapTransferBuffer(ByteBuf.class.cast(msg))); } catch (Exception e) { ReferenceCountUtil.release(msg); } } } private class ChannelHandlerContextCloseable implements Closeable { private final ChannelHandlerContext ctx; public ChannelHandlerContextCloseable(ChannelHandlerContext ctx) { super(); this.ctx = ctx; } @Override public void close() throws IOException { this.ctx.close(); } } private class ByteBufWrapTransferBuffer implements TransferBuffer { private final ByteBuf buffer; public ByteBufWrapTransferBuffer(ByteBuf buffer) { super(); this.buffer = buffer; } @Override public Object getBuffer() { return this.buffer; } @Override public TransferBuffer release() { if (this.buffer.refCnt() > 0) { ReferenceCountUtil.release(this.buffer); } return this; } } private class SocksCmdResponseWrap extends SocksResponse { private final byte START_DOMAIN = 4; private BytestreamsProxy proxy; private SocksCmdResponse cmd; protected SocksCmdResponseWrap(BytestreamsProxy proxy, SocksCmdResponse cmd) { super(cmd.responseType()); this.cmd = cmd; this.proxy = proxy; } @Override public void encodeAsByteBuf(ByteBuf buf) { try { this.cmd.encodeAsByteBuf(buf); byte[] proxy = this.proxy.getDomain().getBytes("UTF-8"); buf.writerIndex(START_DOMAIN).writeByte(proxy.length).writeBytes(proxy).writeByte(0).writeByte(0); } catch (Exception e) { throw new RuntimeException(e); } } } }