⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 socks5proxy.java

📁 一个功能强大的聊天程序.....基本实现所有功能....强烈推荐下载
💻 JAVA
字号:
/*
* LumaQQ - Java QQ Client
*
* Copyright (C) 2004 luma <stubma@163.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package edu.tsinghua.lumaqq.qq.net;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectionKey;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import edu.tsinghua.lumaqq.qq.Util;
import edu.tsinghua.lumaqq.qq.packets.PacketParseException;


/**
 * <pre>
 * Socks5 代理服务器的处理类。
 * </pre>
 * 
 * @see RFC 1928
 * @see RFC 1929
 * @author luma
 */
public class Socks5Proxy extends AbstractProxy {
    /** Log类 */
	private static final Log log = LogFactory.getLog(Socks5Proxy.class);
    /** Socks版本号 */
    public static final byte VER_5 = 0x5;
    /** 验证类型 - 不需验证 */
    public static final byte AUTH_NONE = 0x0;
    /** 验证类型 - 用户名/密码 */
    public static final byte AUTH_USERNAME_PASSWORD = 0x2;
    /** 请求类型 - 连接 */
    public static final byte REQ_CONNECT = 0x1;
    /** 请求类型 - UDP绑定 */
    public static final byte REQ_UDP_ASSOCIATE = 0x3;
    /** 地址类型 - ipv4 */
    public static final byte ATYP_IPV4 = 0x1;
    /** 地址类型 - 域名 */
    public static final byte ATYP_DOMAIN_NAME = 0x3;
    /** 地址类型 - IPV6 */
    public static final byte ATYP_IPV6 = 0x4;
    /** 回复码 - 成功 */
    public static final byte REPLY_SUCCESS = 0x0;
    
    /** 状态 - 无动作 */
    public static final int STATUS_NONE = 0;
    /** 状态 - 最初请求 */
    public static final int STATUS_INIT = 1;
    /** 状态 - 验证 */
    public static final int STATUS_AUTH = 2;
    /** 状态 - CONNECT */
    public static final int STATUS_CONNECT = 3;
    /** 状态 - UDP绑定 */
    public static final int STATUS_UDP_ASSOCIATE = 4;
    /** 状态 - Ready */
    public static final int STATUS_READY = 5;

    /** 远程主机地址字节数组,这个数组里面可能是ip,也可能是域名 */
    protected byte[] remoteAddress;
    /** 远程主机端口 */
    protected int remotePort;
    /** 地址是IP还是域名 */
    protected boolean isIp;
    /**是否需要验证?
     * socks5 协议比较诡异,
     * 还是根据是否传入了用户名和密码来决定是否验证
     * 如果数据发送的是05 02 00 02
     * 代理服务器返回的就会是05 02
     * 然后就会出错
     */
    protected boolean NeedVerify;

    /**
     * 构造函数
     * 
     * @param handler
     * @throws IOException
     */
    public Socks5Proxy(IProxyHandler handler, String u, String p, DatagramChannel channel) throws IOException {
        super(handler, channel);
        status = STATUS_NONE;
        NeedVerify = false;
        if(u != null){
            NeedVerify = true;
            username = u;
        }
        if(p != null)
            password = p;
    }
    
    /**
     * 构造函数,带验证参数
     * 
     * @param handler
     * @param u 用户名
     * @param p 密码
     * @throws IOException
     */
    public Socks5Proxy(IProxyHandler handler, String u, String p) throws IOException {
        super(handler);
        status = STATUS_NONE;
        NeedVerify = false;
        if(u != null){
            NeedVerify = true;
            username = u;
        }
        if(p != null)
            password = p;
    }

    /**
     * 发送初始请求包,其格式为:
     * +----+----------+----------+
     * |VER | NMETHODS | METHODS  |
     * +----+----------+----------+
     * | 1  |    1     | 1 to 255 |
     * +----+----------+----------+
     * 
     * @see edu.tsinghua.lumaqq.qq.net.IProxy#init()
     */
    public void init() {
        log.trace("Socks5 init");
        // 构造初始请求包
        buffer.clear();
        buffer.put(VER_5)
        	.put((byte)0x1)
        	.put(NeedVerify?AUTH_USERNAME_PASSWORD:AUTH_NONE);
        // 发送
        buffer.flip();
        send();
        status = STATUS_INIT;           
    }
    
    /**
     * 发送验证包
     * Socks5 验证包的格式是
     * +----+------+----------+------+----------+
     * |VER | ULEN |  UNAME   | PLEN |  PASSWD  |
     * +----+------+----------+------+----------+
     * | 1  |  1   | 1 to 255 |  1   | 1 to 255 |
     * +----+------+----------+------+----------+
     */
    protected void auth() {
        log.trace("Socks5 auth");
        // 发送验证包,如果用户名密码为null,则置为空
        buffer.clear();
        buffer.put(VER_5)
        	.put((byte)(username.length() & 0xFF))
        	.put(username.getBytes())
        	.put((byte)(password.length() & 0xFF))
        	.put(password.getBytes());
        // 发送
        buffer.flip();
        send();
        status = STATUS_AUTH;            
    }
    
    /**
     * 连接一个远程主机,Socks命令的格式为
     * +----+-----+-------+------+----------+----------+
     * |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
     * +----+-----+-------+------+----------+----------+
     * | 1  |  1  | X'00' |  1   | Variable |    2     |
     * +----+-----+-------+------+----------+----------+
     */
    protected void connect() {
        log.trace("Socks5 connect");
        // 构造请求包
        buffer.clear();
        buffer.put(VER_5)
        	.put(REQ_CONNECT)
        	.put((byte)0x0)
        	.put(isIp ? ATYP_IPV4 : ATYP_DOMAIN_NAME)
        	.put(remoteAddress)
        	.putChar((char)remotePort);
        // 发送
        buffer.flip();
        send();
        status = STATUS_CONNECT;            
    }

    /**
     * 发送UDP绑定命令,命令格式为
     * +----+-----+-------+------+----------+----------+
     * |VER | CMD |  RSV  | ATYP | DST.ADDR | DST.PORT |
     * +----+-----+-------+------+----------+----------+
     * | 1  |  1  | X'00' |  1   | Variable |    2     |
     * +----+-----+-------+------+----------+----------+
     */
    protected void associate() {
        log.trace("Socks5 associate");
        // 构造请求包
        buffer.clear();
        buffer.put(VER_5)
        	.put(REQ_UDP_ASSOCIATE)
        	.put((byte)0x0)
        	.put(ATYP_IPV4)
        	.putInt(0)
        	.putChar((char)clientPort);
        // 发送
        buffer.flip();
        send();
        status = STATUS_UDP_ASSOCIATE;            
    }    

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.qq.IHandler#processRead(java.nio.channels.SelectionKey)
     */
    public void processRead(SelectionKey sk) throws IOException, PacketParseException {
        // 接收数据
        receive();
        // 检查数据长度
        if(!buffer.hasRemaining()) return;
        // 检查版本号,如果版本号不是5,返回
        if(buffer.get(0) != VER_5)
            return;
        // 根据当前状态做出反应
        switch(status) {
            case STATUS_INIT:
                /* 当前正在初始化,如果服务器需要认证,就认证,不需要认证,就可以连了 */
                if(buffer.get(1) == AUTH_NONE) {
                    log.debug("代理服务器连接成功,不需要认证");
                    if(udp)
                        associate();
                    else
                        connect();
                } else if(buffer.get(1) == AUTH_USERNAME_PASSWORD) {
                    log.debug("代理服务器连接成功,需要认证");
                    auth();
                } else {
                    log.debug("不支持的验证方法");
                    handler.proxyAuthFail();
                }
                break;
            case STATUS_AUTH:
                /* 如果验证通过了,就继续连接;否则返回 */
                if(buffer.get(1) == REPLY_SUCCESS) {
                    log.debug("验证通过");
                    if(udp)
                        associate();
                    else
                        connect();                    
                } else {
                    log.debug("验证失败");
                    handler.proxyAuthFail();
                }
                break;
            case STATUS_CONNECT:
                /* 如果连接成功了,置当前状态为ready */
                if(buffer.get(1) == REPLY_SUCCESS)    {      
                    log.debug("CONNECT命令完成");
                    status = STATUS_READY;
                    handler.proxyReady(null);
                } else {
                    /* 命令失败 */
                    log.debug("代理服务器连接失败,代理不可用");
                    handler.proxyError("代理服务器连接失败,代理不可用");
                }
                break;
            case STATUS_UDP_ASSOCIATE:
                /*
                 * 如果连接成功了,置当前状态为ready。目前我们直接连接bind address,但是
                 * 实际上,这个bind address可能不是Socks5代理的外部IP,因为可能连接不上。
                 * 对于这种情况,没有处理。拒猜测,QQ会首先发出0x0031命令来试探这个bind 
                 * address是否可用,如果不可用,它会自动切换到TCP登录方式 
                 */
                if(buffer.get(1) == REPLY_SUCCESS) {
                    // 得到地址类型
                    byte addressType = buffer.get(3);                    
                    // 得到bind address的字节数组
                    byte[] a = new byte[buffer.limit() - 6];
                    buffer.position(4);
                    buffer.get(a);
                    // 得到bind port
                    int p = buffer.getChar();
                    // 根据地址类型转换成字符串
                    String addressStr = null;
                    if(addressType == ATYP_IPV4)
                        addressStr = Util.getIpStringFromBytes(a);
                    else if(addressType == ATYP_DOMAIN_NAME)
                        addressStr = new String(a);
                    else {
                        log.debug("不支持IPV6");
                        return;
                    }
                    // 生成地址
                    InetSocketAddress address = new InetSocketAddress(addressStr, p);
                    // log
                    log.debug("UDP绑定已完成,BND.ADDR: " + addressStr + " BND.PORT: " + p);
                    // 设置状态为已准备好
                    status = STATUS_READY;    
                    // 对于UDP Socks5,代理的事件不再有用处,注销
                    sk.cancel();
                    // 回调
                    handler.proxyReady(address);
                } else  {
                    /* 命令失败 */
                    log.debug("代理服务器连接失败,代理不可用");
                    handler.proxyError("代理服务器连接失败,代理不可用");
                }
                break;
            default:
                break;
        }
    }

    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.qq.IHandler#processWrite()
     */
    public void processWrite() throws IOException {      
        log.trace("Socks5Proxy processWrite");
        if(connected && status == STATUS_NONE)
            init();
    }
        
    /* (non-Javadoc)
     * @see edu.tsinghua.lumaqq.qq.IProxy#setServerAddress(java.net.InetSocketAddress)
     */
    public void setRemoteAddress(InetSocketAddress serverAddress) {
        super.setRemoteAddress(serverAddress);
		// 解析远程主机的地址和端口
        if(serverAddress.isUnresolved()) {
            /* 没有解析,是域名 */
            isIp = false;
		    byte[] b = serverAddress.getHostName().getBytes();
		    remoteAddress = new byte[b.length + 1];
		    remoteAddress[0] = (byte)(b.length & 0xFF);
		    System.arraycopy(b, 0, remoteAddress, 1, b.length);
        } else {
            /* 已经解析,可以得到IP。有的Socks5服务器的实现可能不支持域名,能得到ip就用ip */
            isIp = true;
            remoteAddress = serverAddress.getAddress().getAddress();            
        }
		remotePort = serverAddress.getPort();
    }
    
    /**
     * @param clientPort The clientPort to set.
     */
    public void setClientPort(int clientPort) {
        this.clientPort = clientPort;
    }
}

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -