/*
* Copyright (C) 2012-2016 Facebook, Inc.
*
* Licensed 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 com.facebook.nifty.client.socks;
import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelPipeline;
import org.jboss.netty.channel.ChannelPipelineFactory;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.handler.codec.frame.FixedLengthFrameDecoder;
import java.net.Inet4Address;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import static com.facebook.nifty.client.socks.SocksProtocols.createSock4aPacket;
import static com.facebook.nifty.client.socks.SocksProtocols.createSocks4packet;
import static com.google.common.net.InetAddresses.toAddrString;
/**
* ClientBootstrap for connecting via SOCKS proxy.
* Currently only SOCK4 is supported since we don't do authentication anyway.
* <br>
* See http://en.wikipedia.org/wiki/SOCKS
*/
public class Socks4ClientBootstrap extends ClientBootstrap
{
static final String FRAME_DECODER = "frameDecoder";
static final String HANDSHAKE = "handshake";
private final InetSocketAddress socksProxyAddr;
public Socks4ClientBootstrap(ChannelFactory channelFactory, InetSocketAddress socksProxyAddr)
{
super(channelFactory);
this.socksProxyAddr = socksProxyAddr;
}
public Socks4ClientBootstrap(InetSocketAddress socksProxyAddr)
{
this.socksProxyAddr = socksProxyAddr;
super.setPipeline(getPipeline());
}
/**
* Hijack super class's pipelineFactory and return our own that
* does the connect to SOCKS proxy and does the handshake.
*/
@Override
public ChannelPipelineFactory getPipelineFactory()
{
return new ChannelPipelineFactory()
{
@Override
public ChannelPipeline getPipeline()
throws Exception
{
final ChannelPipeline cp = Channels.pipeline();
cp.addLast(FRAME_DECODER, new FixedLengthFrameDecoder(8));
cp.addLast(HANDSHAKE, new Socks4HandshakeHandler(Socks4ClientBootstrap.super.getPipelineFactory()));
return cp;
}
};
}
/**
* Hijack the connect method to connect to socks proxy and then
* send the connection handshake once connection to proxy is established.
*
* @return returns a ChannelFuture, it will be ready once the connection to
* socks and the remote address is established ( i.e. after the handshake completes )
*/
@Override
public ChannelFuture connect(final SocketAddress remoteAddress)
{
if (!(remoteAddress instanceof InetSocketAddress)) {
throw new IllegalArgumentException("expecting InetSocketAddress");
}
final SettableChannelFuture settableChannelFuture = new SettableChannelFuture();
super.connect(socksProxyAddr).addListener(new ChannelFutureListener()
{
@Override
public void operationComplete(ChannelFuture future)
throws Exception
{
settableChannelFuture.setChannel(future.getChannel());
if (future.isSuccess()) {
socksConnect(future.getChannel(), (InetSocketAddress) remoteAddress).addListener(new ChannelFutureListener()
{
@Override
public void operationComplete(ChannelFuture innerFuture)
throws Exception
{
if (innerFuture.isSuccess()) {
settableChannelFuture.setSuccess();
}
else {
settableChannelFuture.setFailure(innerFuture.getCause());
}
}
});
}
else {
settableChannelFuture.setFailure(future.getCause());
}
}
});
return settableChannelFuture;
}
/**
* try to look at the remoteAddress and decide to use SOCKS4 or SOCKS4a handshake
* packet.
*/
private static ChannelFuture socksConnect(Channel channel, InetSocketAddress remoteAddress)
{
channel.write(createHandshake(remoteAddress));
return ((Socks4HandshakeHandler) channel.getPipeline().get("handshake")).getChannelFuture();
}
private static ChannelBuffer createHandshake(InetSocketAddress address)
{
if (address.getAddress() instanceof Inet4Address) {
return createSocks4packet(address.getAddress(), address.getPort());
}
if (address.getAddress() != null) {
return createSock4aPacket(toAddrString(address.getAddress()), address.getPort());
}
if (address.getHostName() != null) {
return createSock4aPacket(address.getHostName(), address.getPort());
}
throw new IllegalArgumentException("Invalid Address " + address);
}
}