# socks技术方案

  • Socket Secure,网络协议,处于会话层,用于管理网络连接并提供安全性和隐私保护;
  • 通过Socks代理服务器,实现隐藏真实ip和其他身份信息,匿名访问互联网资源。
  • 防火墙系统作为应用层网关,将内网与外网隔离开来,管理网络之间的通信。socks5 协议位于应用层和传输层之间,提供了一种能够在防火墙上透明且安全地通信的通用方式。

# 与VPN的对比

* 与VPN的技术路线不同;VPN基于stun技术路线;socks基于代理;

# 参考文档

  • socks5协议-https://www.cnblogs.com/chr1ce/articles/16246884.html
  • linux设置socks代理-https://blog.csdn.net/weixin_39712991/article/details/143665010

# socks的简易流程

  • 参考文档: socks5 代理服务器原理及实现--https://zhuanlan.zhihu.com/p/703624588

# 协商验证阶段

  • socks5客户端与服务端通信,进行认证;
  • 类似于stun的过程,本地启动客户端代理;

# 传输阶段

  • 宿主机某个应用正常的请求经过socks客户端报文封装后,传递给socks服务端
  • socks服务端解析地址,并创建stun类型的tcp连接到目标地址;
  • 宿主机应用通过socks客户端发送报文,经过服务端的stun完成整个链路;

# socks的关键

  • socks客户端代理与socks服务端;
  • socks服务端,即常说的节点;
  • 某个应用需要使用代理,应用本身需要支持支持客户端代理才行;

# 客户端代理

  • 大多数主流软件都提供了客户端代理功能,常用的代理参数:http_proxy,https_proxy;
  • 也有直接用proxy的,常见的还有ftp_proxy,需要注意,具体的代理协议支持程度由应用本身决定,而不是通过这个参数名称判断,如http_proxy不只是能代理http协议;
  • 代理机制是跟应用绑定的,比如: 开启魔法后,浏览器能访问google,youtube等,但是直接ping或者telnet不能通;这与vpn是明显不同的;

# 常用的socks客户端代理

  • ShadowSocksR
  • ClashForWindows,ClashForLinux
  • AriConnect
  • V2ray

# 备注

  • 大部分已经跑路
  • 通常被称之为机场

# linux常见应用代理配置

# 参考文档

  • linux常用配置代理proxy--https://www.cnblogs.com/mq0036/p/17351647.html

# 备注

  • linux通过设置http_proxy,https_proxy变量,可以设置全局代理;
  • yum,wget,curl配置代理都是有针对性的代理,即时配置了全局代理,仍然不能生效;

# docker配置代理

  • 全局代理配置后,docker不能生效,要单独配置!
  • 具体在/etc/docker/daemon.json中配置即可.参考:给docker配置socks代理--https://www.cnblogs.com/lrui1/p/18353634
  • 注意,代理最好使用局域网ip,使用127.0.0.1可能会踩坑
{
  "registry-mirrors": ["https://registry.devops-engineer.com.cn/"],
  "data-root":"/hd01/docker-root",
  "proxies":{
    "http-proxy":"http://127.0.0.1:7890",
    "https-proxy":"http://127.0.0.1:7890"
  },
  "dns": ["8.8.8.8", "8.8.4.4"]
}
systemctl daemon-reload
systemctl restart docker
  • 注意,如果daemon.json没有配置dns,内部将无法正常访问域名(如Dockerfile中安装文件将失败);下方配置示例
{
    "dns": ["8.8.8.8", "8.8.4.4"]
}

# yum设置代理

  • /etc/yum.conf中配置
  • 添加proxy变量

# wget配置代理

  • /etc/wgetrc中进行配置
  • 添加http_proxy,https_proxy配置;
  • 注意,实际使用中可能有出入,新版本可能已经支持全局代理;

# curl配置代理

  • 在使用过程中,使用参数 http_proxy=socks客户端;
  • 注意,实际使用中可能有出入,新版本可能已经支持全局代理;

# git使用代理

  • 在使用命令中,使用参数 http_proxy=socks客户端;
  • 注意,实际使用中可能有出入,新版本可能已经支持全局代理;
git config --global http.proxy http://127.0.0.1:7890  #代理
git config --global --unset http.proxy #取消代理
git config --global http.proxy  #查询是否使用代理

# 通过ng代理外网API接口方案

  • ng默认是直连目标地址,可以通过ng代理本地的地址,然后本地通过客户端代理再访问外网地址
  • 以openai为例:

# ng配置

location /openai/ {

            proxy_pass  http://127.0.0.1:8089/;

            # 保留原始 Host 头
            proxy_set_header Host api.openai.com;
            proxy_set_header Connection "";
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # 启用 SSL 代理
            proxy_ssl_server_name on;  # 重要:启用 SNI,让目标服务器知道是 api.openai.com

            # 传递原始请求头(OpenAI 需要 Authorization)
            proxy_set_header Authorization $http_authorization;
            proxy_pass_request_headers on;

            # 超时设置
            proxy_connect_timeout 30s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;

            # 缓冲
            proxy_buffering off;
        }

# 代理客户端

package com.automannn.demo.proxy;

import com.sun.net.httpserver.*;
import java.io.*;
import java.net.*;
import java.net.http.*;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SimpleReverseProxy {

    private final int listenPort;
    private final String targetHost;
    private final Proxy proxy;
    private HttpServer server;
    private final ExecutorService executor = Executors.newCachedThreadPool();

    public SimpleReverseProxy(int listenPort, String targetHost, String proxyHost, int proxyPort) {
        this.listenPort = listenPort;
        this.targetHost = targetHost;
        this.proxy = proxyHost != null ? 
                    new Proxy(Proxy.Type.HTTP, new InetSocketAddress(proxyHost, proxyPort)) : 
                    Proxy.NO_PROXY;
    }

    public void start() throws IOException {
        server = HttpServer.create(new InetSocketAddress(listenPort), 0);
        server.createContext("/", this::handleRequest);
        server.setExecutor(executor);
        server.start();
        System.out.println("Proxy server started on port " + listenPort);
        System.out.println("Forwarding requests to: " + targetHost);
        System.out.println("Using proxy: " + (proxy.address() != null ? proxy.address() : "None"));
    }

    private void handleRequest(HttpExchange exchange) throws IOException {
        try {
            // 创建 HTTP 客户端(使用代理)
            HttpClient client = HttpClient.newBuilder()
                .proxy(ProxySelector.of((InetSocketAddress) proxy.address()))
                .followRedirects(Redirect.NORMAL)
                .build();

            // 构建目标 URL
            String path = exchange.getRequestURI().getPath();
            String query = exchange.getRequestURI().getQuery();
            String targetUrl = targetHost + path + (query != null ? "?" + query : "");

            // 准备转发请求
            HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
                .uri(URI.create(targetUrl))
                .method(exchange.getRequestMethod(), getBodyPublisher(exchange));

            // 复制请求头
            for (Map.Entry<String, List<String>> entry : exchange.getRequestHeaders().entrySet()) {
                String headerName = entry.getKey();
                // 跳过代理相关头
                if ("Proxy-Connection".equalsIgnoreCase(headerName) || 
                    "Connection".equalsIgnoreCase(headerName)) {
                    continue;
                }
                    
                for (String value : entry.getValue()) {
                    requestBuilder.header(headerName, value);
                }
            }

            // 发送请求
            HttpResponse<byte[]> response = client.send(
                requestBuilder.build(),
                BodyHandlers.ofByteArray()
            );

            // 设置响应头
            response.headers().map().forEach((key, values) -> {
                if (!"Content-Length".equalsIgnoreCase(key)) {
                    exchange.getResponseHeaders().put(key, values);
                }
            });

            // 发送响应
            exchange.sendResponseHeaders(response.statusCode(), response.body().length);
            try (OutputStream os = exchange.getResponseBody()) {
                os.write(response.body());
            }
        } catch (Exception e) {
            e.printStackTrace();
            sendErrorResponse(exchange, 500, "Proxy error: " + e.getMessage());
        }
    }

    private HttpRequest.BodyPublisher getBodyPublisher(HttpExchange exchange) {
        if ("GET".equalsIgnoreCase(exchange.getRequestMethod()) || 
            exchange.getRequestBody() == null) {
            return BodyPublishers.noBody();
        }
        
        try {
            return BodyPublishers.ofByteArray(exchange.getRequestBody().readAllBytes());
        } catch (IOException e) {
            return BodyPublishers.noBody();
        }
    }

    private void sendErrorResponse(HttpExchange exchange, int code, String message) {
        try {
            byte[] response = message.getBytes();
            exchange.sendResponseHeaders(code, response.length);
            exchange.getResponseBody().write(response);
        } catch (IOException ex) {
            ex.printStackTrace();
        } finally {
            exchange.close();
        }
    }

    public void stop() {
        if (server != null) {
            server.stop(0);
            executor.shutdown();
            System.out.println("Proxy server stopped");
        }
    }

    public static void main(String[] args) throws IOException {

        // 允许设置受限制的 Host 头
        System.setProperty("jdk.httpclient.allowRestrictedHeaders", "host,Content-length");

        // 配置示例:
        // 监听端口: 8080
        // 目标服务器: https://api.openai.com
        // VPN 代理: 127.0.0.1:7890 (Clash 默认端口)
        SimpleReverseProxy proxy = new SimpleReverseProxy(
            8089,
            "https://api.openai.com", 
            "127.0.0.1", 
            7890
        );
        
        proxy.start();
        
        // 添加关闭钩子
        Runtime.getRuntime().addShutdownHook(new Thread(proxy::stop));
    }
}