前一阵子突然对网络安全和数据传输这方面有了点兴趣,加上朋友介绍了新的CrossWall的工具,便也想自己做个小工具试试看。
因为我觉得如果只是简单的使用工具,而不去深入理解原理,作为一个程序员就不会有进步。
这里只是分享一下我的思路和简单的例子,并没有使用复杂的数据加密和协议(像SSR)。
但仍然需要购买海外服务器,具体哪个我就不介绍了,只要海外的都行。
一. 思路:
原理其实很简单,
①通过代理服务器拦截所有的本地Http和Https请求
②通过Sockets接收到请求后,截取请求头并加密(为了不让防火墙拦截),将加密后的请求头再拼回原请求,并发送到海外的服务器
③海外服务器接收到加密后的请求后,对header解密,并将请求发送到对应的目标服务器(如Google)
④将目标服务器返回的数据流加密后返回给本机
⑤将海外服务器返回的数据解密后返回给浏览器
第④⑤步加密解密我并没有做,因为我只是通过将header加密解密就通过了防火墙。
由此可见,防火墙似乎并没有对数据本身做Check,只是校验了Header。
这也可以理解,因为返回的数据本身是很难验证的,而且现在网络那么发达,如果对每一条请求做太多的验证会影响整个互联网的访问速度,防火墙服务器的处理压力也会变得很大。
防火墙能封国外IP还是因为它不仅能拦截,还能主动探测端口,这个就不在这里讨论了。
另外,我对Header的加密,也只是简单的将字节加了1位而已,毕竟加密不是这里讨论的重点。
二. 本机Client端:
2.1. 目录结构:
2.2. Client.java:
定义了客户端的启动界面,可以设置海外服务器的IP和Port。
/**
*
*/
/**
* @author Administrator
*
*/
package ssr;
import com.ice.jni.registry.RegistryException;
import ssr.com.*;
import javax.swing.*;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/*
1.创建一个ServerSocket对象;
2.调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
3.从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
4.处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
5.处理响应:根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;
6.关闭Socket对象;
7.转到步骤2,继续等待连接请求;
*/
public class Client implements ActionListener{
//Specify the look and feel to use. Valid values: s
//null (use the default), "Metal", "System", "Motif", "GTK+"
final static String LOOKANDFEEL = "System";
JButton jbutton;
JLabel lblServer;
JLabel lblPort;
JLabel lblPassword;
JTextField txtServer;
JTextField txtPort;
JTextField txtPassword;
Boolean blnStart = false;
static final int workerNumber = 4;//线程池保留数量,服务器为8核cpu,合适的数量应该小于8
static final int maxPoolSize=256;//最大线程数量,即最大并发量
static final int maxWorkerInQueue = 2500;// 最大工作队列数量
static final int waitTime = 10;// 超时等待时间
static final int listenPort=8788;
static final String listenIP="127.0.0.1";
static final String foreignIP="*.*.*.*";
static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(workerNumber,
maxPoolSize, waitTime, TimeUnit.SECONDS,
new ArrayBlockingQueue<Runnable>(maxWorkerInQueue));
public Client(){
doShutDownWork();
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
Client client = new Client();
client.createAndShowGUI();
}
});
}
private void doShutDownWork() {
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
Proxy proxy = new Proxy();
try {
// 设置代理服务器
proxy.disableProxy();
} catch (RegistryException ex) {
ex.printStackTrace();
}
}
});
}
public void actionPerformed(ActionEvent e) {
if (!blnStart)
{
jbutton.setText("Stop Proxy");
blnStart = true;
ThreadClient threadClient = new ThreadClient();
threadClient.start();
}else{
// 退出
System.exit(1);
}
}
class ThreadClient extends Thread{
@Override
public void run() {
try {
// 设置代理服务器
Proxy proxy = new Proxy();
// IE代理服务器
proxy.changeProxy(listenIP, listenPort);
} catch (Exception ex) {
System.out.println("PC Proxy Server Setting Error:" + ex.getMessage());
}
try {
// 开启本地代理服务器
Client client = new Client();
// 等待连接请求
client.await(txtServer.getText(), txtPort.getText());
} catch (Exception ex) {
System.out.println("Proxy Client Error:" + ex.getMessage());
}
}
}
private static void initLookAndFeel() {
String lookAndFeel = null;
if (LOOKANDFEEL != null) {
if (LOOKANDFEEL.equals("Metal")) {
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
} else if (LOOKANDFEEL.equals("System")) {
lookAndFeel = UIManager.getSystemLookAndFeelClassName();
} else if (LOOKANDFEEL.equals("Motif")) {
lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
} else if (LOOKANDFEEL.equals("GTK+")) { //new in 1.4.2
lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";
} else {
System.err.println("Unexpected value of LOOKANDFEEL specified: "
+ LOOKANDFEEL);
lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();
}
try {
UIManager.setLookAndFeel(lookAndFeel);
} catch (ClassNotFoundException e) {
System.err.println("Couldn't find class for specified look and feel:"
+ lookAndFeel);
System.err.println("Did you include the L&F library in the class path?");
System.err.println("Using the default look and feel.");
} catch (UnsupportedLookAndFeelException e) {
System.err.println("Can't use the specified look and feel ("
+ lookAndFeel
+ ") on this platform.");
System.err.println("Using the default look and feel.");
} catch (Exception e) {
System.err.println("Couldn't get specified look and feel ("
+ lookAndFeel
+ "), for some reason.");
System.err.println("Using the default look and feel.");
e.printStackTrace();
}
}
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private void createAndShowGUI() {
//Set the look and feel.---设置外观,可以忽略
initLookAndFeel();
//Make sure we have nice window decorations.
//设置为false的话,即为不改变外观
JFrame.setDefaultLookAndFeelDecorated(true);
//Create and set up the window.
JFrame frame = new JFrame("Client");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//----------------------Pannel Components---------------------------
Panel pn = new Panel(null);
pn.setSize(800, 600);
lblServer = new JLabel("Server");
lblServer.setBounds(50, 30, 100, 30);
pn.add(lblServer);
txtServer = new JTextField(listenIP);
txtServer.setBounds(150, 30, 100, 30);
pn.add(txtServer);
JLabel lblForeignIP = new JLabel(foreignIP);
lblForeignIP.setBounds(300, 30, 100, 30);
pn.add(lblForeignIP);
lblPort = new JLabel("Port");
lblPort.setBounds(50, 80, 100, 30);
pn.add(lblPort);
txtPort = new JTextField("8888");
txtPort.setBounds(150, 80, 100, 30);
pn.add(txtPort);
lblPassword = new JLabel("Password");
lblPassword.setBounds(50, 130, 100, 30);
pn.add(lblPassword);
txtPassword = new JTextField("sun");
txtPassword.setBounds(150, 130, 100, 30);
pn.add(txtPassword);
jbutton = new JButton("Start Proxy");
jbutton.setMnemonic(KeyEvent.VK_I);
jbutton.addActionListener(this);
jbutton.setBounds(100, 180, 200, 30);
pn.add(jbutton);
//----------------------Pannel Components---------------------------
//Display the window.
frame.add(pn);
frame.pack();
frame.setVisible(true);
frame.setSize(400, 280);
}
public void await(String serverIP, String serverPort) throws IOException {
// 创建一个ServerSocket对象
ServerSocket serverSocket = null;
try {
//服务器套接字对象
serverSocket = new ServerSocket(listenPort, 1, InetAddress.getByName(listenIP));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// 循环等待一个请求
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
socket.setKeepAlive(true);
//加入任务列表,等待处理
ClinetProxy cp = new ClinetProxy(socket, serverIP, serverPort);
Thread t = new Thread(cp);
t.start();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
2.3. Register.java:
通过register.jar和ICE_JNIRegistry.dll修改注册表,设置代理服务器拦截Http和Https请求。
package ssr.com;
import com.ice.jni.registry.RegDWordValue;
import com.ice.jni.registry.RegStringValue;
import com.ice.jni.registry.Registry;
import com.ice.jni.registry.RegistryException;
import com.ice.jni.registry.RegistryKey;
import ssr.Client;
import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class Register {
// 把ICE_JNIRegistry.dll在的路径加载到java.library.path中,这里是放在classpath下面了
static {
// ①编译成Jar包后的DLL路径设置
String basePath = new Register().getClass().getProtectionDomain().getCodeSource().getLocation().getPath();
try {
basePath = URLDecoder.decode(basePath,"utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
if(basePath.endsWith(".jar")){
basePath = basePath.substring(0,basePath.lastIndexOf("/")+1);
}
File f = new File(basePath);
basePath = f.getAbsolutePath(); //得到windows下的正确路径
System.setProperty("java.library.path", basePath+"/");
// ②本机调试用
//System.setProperty("java.library.path", Register.class.getResource("/").getPath());
}
public String getValue(String folder, String subKeyNode, String subKeyName)
throws SecurityException,
IllegalArgumentException,
RegistryException {
RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);
RegistryKey subKey = software.openSubKey(subKeyNode);
String value = subKey.getStringValue(subKeyName);
subKey.closeKey();
return value;
}
public int getIntValue(String folder, String subKeyNode, String subKeyName)
throws SecurityException,
IllegalArgumentException,
RegistryException {
RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);
RegistryKey subKey = software.openSubKey(subKeyNode);
int value = ((RegDWordValue) subKey.getValue(subKeyName)).getData();
subKey.closeKey();
return value;
}
public boolean setIntValue(String folder, String subKeyNode,
String subKeyName, int subKeyValue) throws RegistryException {
RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);
RegistryKey subKey = software.createSubKey(subKeyNode, "");
RegDWordValue value = new RegDWordValue(subKey, subKeyName);
value.setData(subKeyValue);
subKey.setValue(value);
subKey.flushKey();
subKey.closeKey();
return true;
}
public boolean setValue(String folder, String subKeyNode,
String subKeyName, String subKeyValue) throws RegistryException {
RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);
RegistryKey subKey = software.createSubKey(subKeyNode, "");
subKey.setValue(new RegStringValue(subKey, subKeyName, subKeyValue));
subKey.flushKey();
subKey.closeKey();
return true;
}
}
注意测试的时候和打成Jar包以后,static方法不太一样。
2.4. Proxy.java:
代理服务器的修改方法类
package ssr.com;
import com.ice.jni.registry.NoSuchKeyException;
import com.ice.jni.registry.NoSuchValueException;
import com.ice.jni.registry.RegistryException;
public class Proxy {
private static String folder = "SOFTWARE";
private static String subKeyNode = "Microsoft\\Windows\\CurrentVersion\\Internet Settings";
private static String subKeyNameServer = "ProxyServer";
private static String subKeyNameEnable = "ProxyEnable";
private static String subKeyNameOverride = "ProxyOverride";
private static String subKeyOverrideValue = "<local>";
private int originProxyEnable;
private String originProxyServer;
private String originProxyOverride;
private Register register = new Register();
public boolean backToOriginValue() {
try {
register.setIntValue(folder, subKeyNode, subKeyNameEnable,
originProxyEnable);
register.setValue(folder, subKeyNode, subKeyNameServer,
originProxyServer);
try {
System.out.println("backed key: "
+ register.getValue(folder, subKeyNode,
subKeyNameServer)
+ " "
+ register.getIntValue(folder, subKeyNode,
subKeyNameEnable));
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
register.setValue(folder, subKeyNode, subKeyNameOverride,
originProxyOverride);
} catch (NoSuchKeyException e) {
e.printStackTrace();
return false;
} catch (RegistryException e) {
e.printStackTrace();
return false;
}
return true;
}
public boolean changeProxy(String proxyIp, int proxyPort) {
try {
enableProxy();
setProxy(proxyIp, proxyPort);
try {
System.out.println("after change key: "
+ register.getValue(folder, subKeyNode,
subKeyNameServer)
+ " "
+ register.getIntValue(folder, subKeyNode,
subKeyNameEnable));
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
}
setOverride();
} catch (NoSuchKeyException e) {
e.printStackTrace();
return false;
} catch (RegistryException e) {
e.printStackTrace();
return false;
}
return true;
}
public boolean saveOriginValue() {
try {
originProxyServer = register.getValue(folder, subKeyNode,
subKeyNameServer);
originProxyEnable = register.getIntValue(folder, subKeyNode,
subKeyNameEnable);
System.out.println("save origin value: " + originProxyServer + " "
+ originProxyEnable);
} catch (SecurityException e) {
e.printStackTrace();
return false;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return false;
} catch (NoSuchKeyException e) {
e.printStackTrace();
return false;
} catch (RegistryException e) {
e.printStackTrace();
return false;
}
// 没有勾选跳过本地代理服务器时,没有proxyoverride,此时保存为“”,并且返回true
try {
originProxyOverride = register.getValue(folder, subKeyNode,
subKeyNameOverride);
} catch (SecurityException e) {
e.printStackTrace();
return false;
} catch (IllegalArgumentException e) {
e.printStackTrace();
return false;
} catch (NoSuchKeyException e) {
e.printStackTrace();
return false;
} catch (NoSuchValueException e) {
originProxyOverride = "";
return true;
} catch (RegistryException e) {
e.printStackTrace();
return false;
}
return true;
}
public void enableProxy() throws NoSuchKeyException, RegistryException {
register.setIntValue(folder, subKeyNode, subKeyNameEnable, 1);
}
public void disableProxy() throws NoSuchKeyException, RegistryException {
register.setIntValue(folder, subKeyNode, subKeyNameEnable, 0);
}
private void setProxy(String ip, int port) throws NoSuchKeyException,
RegistryException {
register.setValue(folder, subKeyNode, subKeyNameServer, ip + ":" + port);
}
private void setOverride() throws NoSuchKeyException, RegistryException {
register.setValue(folder, subKeyNode, subKeyNameOverride,
subKeyOverrideValue);
}
public void setRegister(Register register) {
this.register = register;
}
}
2.5. ClientProxy.java:
将客户端发送过来的数据转发海外服务器,并将海外服务器返回的数据转发给客户端。
package ssr.com;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 将客户端发送过来的数据转发给海外服务器,并将海外服务器返回的数据转发给客户端
*
*/
public class ClinetProxy implements Runnable {
private Socket socketIn;
private Socket socketOut;
private long totalUpload=0l;//总计上行比特数
private long totalDownload=0l;//总计下行比特数
private String serverIP;
private String serverPort;
public ClinetProxy(Socket socket, String serverIP, String serverPort) {
this.socketIn = socket;
this.serverIP = serverIP;
this.serverPort = serverPort;
}
private static final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
/** 已连接到请求的服务器 */
private static final String AUTHORED = "HTTP/1.1 200 Connection established\r\n\r\n";
/** 内部错误 */
private static final String SERVERERROR = "HTTP/1.1 500 Connection FAILED\r\n\r\n";
@Override
public void run() {
StringBuilder builder=new StringBuilder();
try {
builder.append("\r\n").append("Request Time :" + sdf.format(new Date()));
InputStream isIn = socketIn.getInputStream();
OutputStream osIn = socketIn.getOutputStream();
// 设置外部服务器主机和端口
socketOut = new Socket(serverIP, Integer.parseInt(serverPort));
socketOut.setKeepAlive(true);
InputStream isOut = socketOut.getInputStream();
OutputStream osOut = socketOut.getOutputStream();
//新开一个线程将返回的数据转发给客户端
//由于涉及到TCP协议,必须采用异步的方式,同时收发数据,并不是等所有Input结束后再Output
Thread stc = new ServerToClientDataSendThread(isOut, osIn);
stc.start();
// 对header加密
HttpHeaderClient header = HttpHeaderClient.readHeader(isIn);
byte[] headerData=header.toString().getBytes();
osOut.write(codeUtils.enCode(headerData));
osOut.flush();
//读取客户端请求过来的数据转发给服务器
Thread cts = new ClientToServerDataSendThread(isIn, osOut);
cts.start();
cts.join();
//等待向客户端转发的线程结束
stc.join(); // 将服务器返回的数据写回客户端,主线程等待ot结束再关闭。
} catch (Exception e) {
e.printStackTrace();
if(!socketIn.isOutputShutdown()){
//如果还可以返回错误状态的话,返回内部错误
try {
socketIn.getOutputStream().write(SERVERERROR.getBytes());
} catch (IOException e1) {}
}
} finally {
try {
if (socketIn != null) {
socketIn.close();
}
} catch (IOException e) {}
if (socketOut != null) {
try {
socketOut.close();
} catch (IOException e) {}
}
//纪录上下行数据量和最后结束时间并打印
builder.append("\r\n").append("Up Bytes :" + totalUpload);
builder.append("\r\n").append("Down Bytes :" + totalDownload);
builder.append("\r\n").append("Closed Time :" + sdf.format(new Date()));
builder.append("\r\n");
logRequestMsg(builder.toString());
}
}
/**
* 避免多线程竞争把日志打串行了
* @param msg
*/
private synchronized void logRequestMsg(String msg){
System.out.println(msg);
}
/**
* 将客户端返回的数据转发给服务器端
*
*/
class ClientToServerDataSendThread extends Thread {
private InputStream isIn;
private OutputStream osOut;
ClientToServerDataSendThread(InputStream isIn, OutputStream osOut) {
this.isIn = isIn;
this.osOut = osOut;
}
@Override
public void run() {
byte[] buffer = new byte[4096];
try {
int len;
while ((len = isIn.read(buffer)) != -1) {
if (len > 0) {
osOut.write(buffer, 0, len);
}
totalUpload+=len;
if (socketIn.isClosed() || socketOut.isClosed()) {
break;
}
}
osOut.flush();
osOut.close();
} catch (Exception e) {
try {
socketOut.close();// 尝试关闭远程服务器连接,中断转发线程的读阻塞状态
} catch (IOException e1) {
System.out.println(e.getMessage());
}
System.out.println(e.getMessage());
}finally{
}
}
}
/**
* 将服务器端返回的数据转发给客户端
*
*/
class ServerToClientDataSendThread extends Thread {
private InputStream isOut;
private OutputStream osIn;
ServerToClientDataSendThread(InputStream isOut, OutputStream osIn) {
this.isOut = isOut;
this.osIn = osIn;
}
@Override
public void run() {
byte[] buffer = new byte[4096];
try {
int len;
while ((len = isOut.read(buffer)) != -1) {
if (len > 0) {
// logData(buffer, 0, len);
osIn.write(buffer, 0, len);
totalDownload+=len;
}
if (socketIn.isOutputShutdown() || socketOut.isClosed()) {
break;
}
}
osIn.flush();
osIn.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
2.6. HttpHeaderClient.java
客户端解析头部
package ssr.com;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 解析头部信息
*
*/
public final class HttpHeaderClient {
private List<String> header=new ArrayList<String>();
private String method;
private String host;
private String port;
public static final int MAXLINESIZE = 4096;
public static final String METHOD_GET="GET";
public static final String METHOD_POST="POST";
public static final String METHOD_CONNECT="CONNECT";
private HttpHeaderClient(){}
/**
* 从数据流中读取请求头部信息,必须在放在流开启之后,任何数据读取之前
* @param in
* @return
* @throws IOException
*/
public static final HttpHeaderClient readHeader(InputStream in) throws IOException {
HttpHeaderClient header = new HttpHeaderClient();
StringBuilder sb = new StringBuilder();
//先读出交互协议来,
char c = 0;
while ((c = (char) in.read()) != '\n') {
sb.append(c);
if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段
break;
}
}
//如能识别出请求方式则则继续,不能则退出
if(header.addHeaderMethod(sb.toString())!=null){
do {
sb = new StringBuilder();
while ((c = (char) in.read()) != '\n') {
sb.append(c);
if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段
break;
}
}
if (sb.length() > 1 && header.notTooLong()) {//如果头部包含信息过多,抛弃剩下的部分
header.addHeaderString(sb.substring(0, sb.length() - 1));
} else {
break;
}
} while (true);
}
return header;
}
/**
*
* @param str
*/
private void addHeaderString(String str){
str=str.replaceAll("\r", "");
header.add(str);
if(str.startsWith("Host")){//解析主机和端口
String[] hosts= str.split(":");
host=hosts[1].trim();
if(method.endsWith(METHOD_CONNECT)){
port=hosts.length==3?hosts[2]:"443";//https默认端口为443
}else if(method.endsWith(METHOD_GET)||method.endsWith(METHOD_POST)){
port=hosts.length==3?hosts[2]:"80";//http默认端口为80
}
System.out.println("Header:" + str);
}
}
/**
* 判定请求方式
* @param str
* @return
*/
private String addHeaderMethod(String str){
str=str.replaceAll("\r", "");
header.add(str);
if(str.startsWith(METHOD_CONNECT)){//https链接请求代理
method=METHOD_CONNECT;
}else if(str.startsWith(METHOD_GET)){//http GET请求
method=METHOD_GET;
}else if(str.startsWith(METHOD_POST)){//http POST请求
method=METHOD_POST;
}
return method;
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
for(String str : header){
sb.append(str).append("\r\n");
}
sb.append("\r\n");
return sb.toString();
}
public boolean notTooLong(){
return header.size()<=16;
}
}
2.7. codeUtils.java
加密解密类
package ssr.com;
public class codeUtils {
public static byte[] enCode(byte[] bytesIn){
byte[] bytesOut = new byte[bytesIn.length];
int i = 0;
for(byte bt:bytesIn){
bytesOut[i] = (byte)(bt+1);
i++;
}
return bytesOut;
}
public static char deCodeChar(char charIn){
char charOut = (char)((int)charIn-1);
return charOut;
}
}
三. 海外服务器Server端:
3.1. Server.java
海外服务器的Socket启动程序
/**
*
*/
/**
* @author Administrator
*
*/
package ssr;
import ssr.com.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
/*
1.创建一个ServerSocket对象;
2.调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
3.从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
4.处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
5.处理响应:根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;
6.关闭Socket对象;
7.转到步骤2,继续等待连接请求;
*/
public class Server {
static final int listenPort=8888;
static final String listenIP="0.0.0.0";
public static void main(String[] args) {
// 开启客户端代理服务器
Server server = new Server();
// 等待连接请求
server.await();
}
public void await() {
// 创建一个ServerSocket对象
ServerSocket serverSocket = null;
try {
//服务器套接字对象
serverSocket = new ServerSocket(listenPort, 1, InetAddress.getByName(listenIP));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
// 循环等待一个请求
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
socket.setKeepAlive(true);
//加入任务列表,等待处理
ServerProxy sp = new ServerProxy(socket);
Thread t = new Thread(sp);
t.start();
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
3.2. ServerProxy.java
海外服务器的数据处理
package ssr.com;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 将客户端发送过来的数据转发给请求的服务器端,并将服务器返回的数据转发给客户端
*
*/
public class ServerProxy implements Runnable {
private Socket socketIn;
private Socket socketOut;
private long totalUpload=0l;//总计上行比特数
private long totalDownload=0l;//总计下行比特数
public ServerProxy(Socket socket) {
this.socketIn = socket;
}
private static final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
/** 已连接到请求的服务器 */
private static final String AUTHORED = "HTTP/1.1 200 Connection established\r\n\r\n";
/** 本代理登陆失败(此应用暂时不涉及登陆操作) */
//private static final String UNAUTHORED="HTTP/1.1 407 Unauthorized\r\n\r\n";
/** 内部错误 */
private static final String SERVERERROR = "HTTP/1.1 500 Connection FAILED\r\n\r\n";
@Override
public void run() {
StringBuilder builder=new StringBuilder();
try {
builder.append("\r\n").append("Request Time :" + sdf.format(new Date()));
InputStream isIn = socketIn.getInputStream();
OutputStream osIn = socketIn.getOutputStream();
//从客户端流数据中读取头部,获得请求主机和端口
HttpHeaderServer header = HttpHeaderServer.readHeader(isIn);
//添加请求日志信息
builder.append("\r\n").append("From Host :" + socketIn.getInetAddress());
builder.append("\r\n").append("From Port :" + socketIn.getPort());
builder.append("\r\n").append("Proxy Method:" + header.getMethod());
builder.append("\r\n").append("Request Host :" + header.getHost());
builder.append("\r\n").append("Request Port :" + header.getPort());
//如果没解析出请求请求地址和端口,则返回错误信息
if (header.getHost() == null || header.getPort() == null) {
osIn.write(SERVERERROR.getBytes());
osIn.flush();
return ;
}
// 查找主机和端口
socketOut = new Socket(header.getHost(), Integer.parseInt(header.getPort()));
socketOut.setKeepAlive(true);
InputStream isOut = socketOut.getInputStream();
OutputStream osOut = socketOut.getOutputStream();
//新开一个线程将返回的数据转发给客户端
//由于涉及到TCP协议,必须采用异步的方式,同时收发数据,并不是等所有Input结束后再Output
Thread stc = new ServerToClientDataSendThread(isOut, osIn);
stc.start();
if (header.getMethod().equals(HttpHeaderClient.METHOD_CONNECT)) {
// 将已联通信号返回给请求页面
osIn.write(AUTHORED.getBytes());
osIn.flush();
}else{
//http请求需要将请求头部也转发出去
byte[] headerData=header.toString().getBytes();
totalUpload+=headerData.length;
osOut.write(headerData);
osOut.flush();
}
//读取客户端请求过来的数据转发给服务器
Thread cts = new ClientToServerDataSendThread(isIn, osOut);
cts.start();
cts.join();
//等待向客户端转发的线程结束
stc.join(); // 将服务器返回的数据写回客户端,主线程等待ot结束再关闭。
} catch (Exception e) {
e.printStackTrace();
if(!socketIn.isOutputShutdown()){
//如果还可以返回错误状态的话,返回内部错误
try {
socketIn.getOutputStream().write(SERVERERROR.getBytes());
} catch (IOException e1) {}
}
} finally {
try {
if (socketIn != null) {
socketIn.close();
}
} catch (IOException e) {}
if (socketOut != null) {
try {
socketOut.close();
} catch (IOException e) {}
}
//纪录上下行数据量和最后结束时间并打印
builder.append("\r\n").append("Up Bytes :" + totalUpload);
builder.append("\r\n").append("Down Bytes :" + totalDownload);
builder.append("\r\n").append("Closed Time :" + sdf.format(new Date()));
builder.append("\r\n");
logRequestMsg(builder.toString());
}
}
/**
* 避免多线程竞争把日志打串行了
* @param msg
*/
private synchronized void logRequestMsg(String msg){
System.out.println(msg);
}
/**
* 将客户端返回的数据转发给服务器端
*
*/
class ClientToServerDataSendThread extends Thread {
private InputStream isIn;
private OutputStream osOut;
ClientToServerDataSendThread(InputStream isIn, OutputStream osOut) {
this.isIn = isIn;
this.osOut = osOut;
}
@Override
public void run() {
byte[] buffer = new byte[4096];
try {
int len;
while ((len = isIn.read(buffer)) != -1) {
if (len > 0) {
osOut.write(buffer, 0, len);
}
totalUpload+=len;
if (socketIn.isClosed() || socketOut.isClosed()) {
break;
}
}
osOut.flush();
osOut.close();
} catch (Exception e) {
try {
socketOut.close();// 尝试关闭远程服务器连接,中断转发线程的读阻塞状态
} catch (IOException e1) {
System.out.println(e.getMessage());
}
System.out.println(e.getMessage());
}finally{
}
}
}
/**
* 将服务器端返回的数据转发给客户端
*
*/
class ServerToClientDataSendThread extends Thread {
private InputStream isOut;
private OutputStream osIn;
ServerToClientDataSendThread(InputStream isOut, OutputStream osIn) {
this.isOut = isOut;
this.osIn = osIn;
}
@Override
public void run() {
byte[] buffer = new byte[4096];
try {
int len;
while ((len = isOut.read(buffer)) != -1) {
if (len > 0) {
// logData(buffer, 0, len);
osIn.write(buffer, 0, len);
totalDownload+=len;
}
if (socketIn.isOutputShutdown() || socketOut.isClosed()) {
break;
}
}
osIn.flush();
osIn.close();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
}
3.3. HttpHeaderServer.java
海外服务器的头部处理
package ssr.com;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
/**
* 解析头部信息
*
*/
public final class HttpHeaderServer {
private List<String> header=new ArrayList<String>();
private String method;
private String host;
private String port;
public static final int MAXLINESIZE = 4096;
public static final String METHOD_GET="GET";
public static final String METHOD_POST="POST";
public static final String METHOD_CONNECT="CONNECT";
private HttpHeaderServer(){}
/**
* 从数据流中读取请求头部信息,必须在放在流开启之后,任何数据读取之前
* @param in
* @return
* @throws IOException
*/
public static final HttpHeaderServer readHeader(InputStream in) throws IOException {
HttpHeaderServer header = new HttpHeaderServer();
StringBuilder sb = new StringBuilder();
//先读出交互协议来,
char c = 0;
while ((c = codeUtils.deCodeChar((char) in.read())) != '\n') {
sb.append(c);
if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段
break;
}
}
//如能识别出请求方式则则继续,不能则退出
if(header.addHeaderMethod(sb.toString())!=null){
do {
sb = new StringBuilder();
while ((c = codeUtils.deCodeChar((char) in.read())) != '\n') {
sb.append(c);
if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段
break;
}
}
if (sb.length() > 1 && header.notTooLong()) {//如果头部包含信息过多,抛弃剩下的部分
header.addHeaderString(sb.substring(0, sb.length() - 1));
} else {
break;
}
} while (true);
}
return header;
}
/**
*
* @param str
*/
private void addHeaderString(String str){
str=str.replaceAll("\r", "");
header.add(str);
if(str.startsWith("Host")){//解析主机和端口
String[] hosts= str.split(":");
host=hosts[1].trim();
if(method.endsWith(METHOD_CONNECT)){
port=hosts.length==3?hosts[2]:"443";//https默认端口为443
}else if(method.endsWith(METHOD_GET)||method.endsWith(METHOD_POST)){
port=hosts.length==3?hosts[2]:"80";//http默认端口为80
}
System.out.println("Header:" + str);
}
}
/**
* 判定请求方式
* @param str
* @return
*/
private String addHeaderMethod(String str){
str=str.replaceAll("\r", "");
header.add(str);
if(str.startsWith(METHOD_CONNECT)){//https链接请求代理
method=METHOD_CONNECT;
}else if(str.startsWith(METHOD_GET)){//http GET请求
method=METHOD_GET;
}else if(str.startsWith(METHOD_POST)){//http POST请求
method=METHOD_POST;
}
return method;
}
@Override
public String toString() {
StringBuilder sb=new StringBuilder();
for(String str : header){
sb.append(str).append("\r\n");
}
sb.append("\r\n");
return sb.toString();
}
public boolean notTooLong(){
return header.size()<=16;
}
public List<String> getHeader() {
return header;
}
public void setHeader(List<String> header) {
this.header = header;
}
public String getMethod() {
return method;
}
public void setMethod(String method) {
this.method = method;
}
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public String getPort() {
return port;
}
public void setPort(String port) {
this.port = port;
}
}
3.4. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sun</groupId>
<artifactId>project</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.source>1.8</maven.compiler.source>
<netty.version>4.1.25.Final</netty.version>
</properties>
<dependencies>
<!-- 添加servlet3.0核心包 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>javax.servlet.jsp-api</artifactId>
<version>2.3.2-b01</version>
</dependency>
<!-- jstl -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler-proxy</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.58</version>
</dependency>
<dependency>
<groupId>registry</groupId>
<artifactId>registry</artifactId>
<version>1.0</version>
<scope>system</scope>
<systemPath>${basedir}/lib/registry.jar</systemPath>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>utf8</encoding>
</configuration>
</plugin>
</plugins>
<resources>
<!--指定资源的位置 -->
<resource>
<directory>lib</directory>
</resource>
</resources>
</build>
</project>
3.5. 其他:
将register.jar和ICE_JNIRegistry.dll放在lib目录下(64位的Register注册表修改包网上自己搜吧)
四. 运行方法:
4.1.由于需要修改注册表,用到了lib下面的ICE_JNIRegistry.dll文件。
在Register.java里面的static方法中,有设置dll的路径。
要执行编译后的jar包就用上面那种方法,调试的时候可以用下面的。
4.2.我是通过IDEA中的Build Artifacts编译了两个Jar包
ssrClient.jar是在本机运行。
ssrServer.jar是在国外的服务器运行。
4.3.运行本机的ssrClient.jar:
(1)把ssrClient.jar和ICE_JNIRegistry.dll放在C:\ssr目录下
(2)cmd中执行
>cd c:\ssr
>java -jar ssrClient.jar
(另外说一句,我这里的Password是一开始准备使用AES加密用的,现在没用到)
(3)界面中修改海外服务器的IP地址,点击StartProxy
(4)修改注册表中的代理服务器好像有延迟,如果不行,就手动在IE里面改代理服务器算了。
4.4.运行海外服务器的ssrServer.jar:
(1)环境CentOS+jdk1.8.0_172,配置文件/etc/profile修改好
(2)复制ssrServer.jar到/usr/local/ssr目录中(没有就自己建)
(3)执行
>java -jar ssrServer.jar
4.5.测试:
输入打开www.google.com:
本机的ssrClient显示如下:
海外服务器的ssrServer显示如下:
五. 资料:
Github代码:https://github.com/sunroyi/sunSocks