1. 程式人生 > >Android Socks5代理伺服器程式開發

Android Socks5代理伺服器程式開發

原理

為處理移動端網路斷連問題,實現應用無關。我們考慮採用client<->proxy<->Internet的三層架構。從client->proxy這一環節,ProxyDroid已經能夠實現。

因此接下來主要需要完成的工作是

  1. proxy的開發
  2. proxyInternet互相之間的資訊轉發,以及剩餘的從proxy->client端資訊傳輸。

ProxyDroid端我們採用了Socks5協議。它的優勢是:無需proxy從報文內容中解析目的IP以及埠號,而是可以從正式資料傳輸前的握手資訊獲取。

Socks5代理工作模式

  1. client連線Socks5 proxy
    伺服器埠
  2. client端傳送命令{5,1,0}
  3. proxy返回應答{5,0},表示可以進行代理
  4. client傳送:{5,1,0,1}+目的地址(4位元組的16進製表示)+目的埠(2位元組的16進製表示)
  5. proxy提取出IP地址、埠號與外網建立socket
  6. proxyclient返回應答:{5,0,0,1}+外網套接字繫結的IP地址(4位元組的16進製表示)+外網套接字繫結的埠號(2位元組的16進製表示)
  7. proxy不斷檢測client套接字,讀出資料傳送給外網
  8. proxy不斷檢測外網套接字,讀出資料傳送給client

程式碼

基本思想如下:

  1. 主活動類中會設定一個ServerSocket
    用於接收proxydroid傳送過來的socks5報文從而建立socket,然後將該socket傳遞給伺服器執行緒;
  2. 伺服器執行緒與client端通訊,建立從client與外網之間的互動鏈路。其中伺服器執行緒自己作為中間媒介。

架構圖

主活動

public class MainActivity extends AppCompatActivity {

    private String TAG = "SocketServer";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    ServerSocket serverSocket = new ServerSocket(34500); //這裡隨機選擇了一個埠,需與proxydroid中設定的埠一致
                    Log.d(TAG, "Port=" + serverSocket.getLocalPort());
                    while (true) {
                        Socket socket = serverSocket.accept();//若獲取不到會一直阻塞
                        new Thread(new ServerThread(socket)).start();//觸發伺服器執行緒
                    }
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }).start();

    }
}

伺服器執行緒

public class ServerThread implements Runnable {

    private Socket socket;
    private String TAG = this.getClass().getName();
    private int BUFF_SIZE = 1024 * 100;

    public ServerThread(Socket socket) {
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            InputStream innerInputStream = socket.getInputStream();
            OutputStream innerOutputStream = socket.getOutputStream();
            byte[] buff = new byte[BUFF_SIZE];
            int rc;
            ByteArrayOutputStream byteArrayOutputStream;

            /**
             * client會向proxy傳送510,所以這裡執行的結果是buff={5,1,0}
             * Caution: 這裡不能跟下面的innerInputStream.read(buff, 0, 10);合併成innerInputStream.read(buff, 0, 13);
             *          我試過,大部分情況沒影響,但是偶爾會出現重大bug(讀不出外網ip),至於原因暫不詳
             *          看來這種input和output型別的操作還是穩重一點,不要太心急
             */
            innerInputStream.read(buff, 0, 3);

            /**
             *  proxy向client傳送應答{5,0}
             */
            byte[] firstAckMessage = new byte[]{5, 0};
            byte[] secondAckMessage = new byte[10];
            innerOutputStream.write(firstAckMessage);
            innerOutputStream.flush();

            /**
             *     client傳送命令5101+目的地址(4Bytes)+目的埠(2Bytes)
             *     即{5,1,0,1,IPx1,IPx2,IPx3,IPx4,PORTx1,PORTx2} 一共10位
             *     例如傳送給52.88.216.252伺服器的80埠,那麼這裡buff就是{5,1,0,1,52,88,-40,-4,0,80}(這裡每位都是byte,所以在-128~127之間,可以自己換算成0~255)
             */
            innerInputStream.read(buff, 0, 10);

            String IP = byte2int(buff[4]) + "." + byte2int(buff[5]) + "." + byte2int(buff[6]) + "." + byte2int(buff[7]);
            int port = byte2int(buff[8]) * 256 + byte2int(buff[9]);

            Log.e("ServerThread", "Connected to " + IP + ":" + port);
            Socket outerSocket = new Socket(IP, port);
            InputStream outerInputStream = outerSocket.getInputStream();
            OutputStream outerOutputStream = outerSocket.getOutputStream();

            /**
             * proxy 向 client 返回應答5+0+0+1+因特網套接字繫結的IP地址(4位元組的16進製表示)+因特網套接字繫結的埠號(2位元組的16進製表示)
             */
            byte ip1[] = new byte[4];
            int port1 = 0;
            ip1 = outerSocket.getLocalAddress().getAddress();
            port1 = outerSocket.getLocalPort();

            secondAckMessage[0] = 5;
            secondAckMessage[1] = 0;
            secondAckMessage[2] = 0;
            secondAckMessage[3] = 1;
            secondAckMessage[4] = ip1[0];
            secondAckMessage[5] = ip1[1];
            secondAckMessage[6] = ip1[2];
            secondAckMessage[7] = ip1[3];
            secondAckMessage[8] = (byte) (port1 >> 8);
            secondAckMessage[9] = (byte) (port1 & 0xff);
            innerOutputStream.write(secondAckMessage, 0, 10);
            innerOutputStream.flush();

            /**
             * 應答執行緒:從外網不斷讀資料發到client
             */
            SocksResponseThread responseThread = new SocksResponseThread(outerInputStream, innerOutputStream);
            responseThread.start();

            /**
             * 本執行緒:從client不斷讀資料發到外網
             */
            byteArrayOutputStream = new ByteArrayOutputStream();
            while ((rc = innerInputStream.read(buff, 0, BUFF_SIZE)) > 0) {
                outerOutputStream.write(buff, 0, rc);
                byteArrayOutputStream.write(buff, 0, rc);
                outerOutputStream.flush();
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (socket != null) socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }

    public int byte2int(byte b) {
        return b & 0xff;
    }

}

應答執行緒

public class SocksResponseThread extends Thread {

    private InputStream in;
    private OutputStream out;
    private int BUFF_SIZE = 1024 * 100;

    public SocksResponseThread(InputStream in, OutputStream out) {
        this.in = in;
        this.out = out;
    }

    @Override
    public void run() {
        int readbytes = 0;
        byte buf[] = new byte[BUFF_SIZE];
        while (true) {
            try {
                if (readbytes == -1) break;
                readbytes = in.read(buf, 0, BUFF_SIZE);
                if (readbytes > 0) {
                    out.write(buf, 0, readbytes);
                }
                out.flush();
            } catch (Exception e) {
                break;
            }
        }
    }
}