应对 TCP 阻断 - HTTP 抢答式 302 跳转

#起因

因为 OpenAI 网站偶尔要重新登录,使用并不方便。前几天搭了一个 ChatGPT 的站,帮助自己快速使用 GPT,chat.acytoo.com,没想到就导致域名被墙了,细节请见这篇日记


#思路

虽然是面向世界建站,但是也有兴趣研究一下其中的过程。发现了一篇博客,介绍了 TCP 阻断的原理等,讲的很好:301海外跳转原理解析兼谈缓解假墙伪墙攻击勒索的多种技术手段(1),看了文章想自己试一下 HTTP 抢答式 302 跳转,并不能解决网站被墙的问题,可能有助于缓解历史上被墙初期的 TCP 阻断问题,并不适用与现在的防火墙。可能有助于搭建中转站。 因为域名已被 DNS 污染,救不回来了,只是学习技术。

因原文用 用Python编写的(甚至都没有使用asyncio),性能会比较差一些,这里用 golang 重写,并且用 caddy 反代,实战一下。

思路:在正常访问网站时,3 次握手后,由客户端请求,服务器完成客户的请求。被墙初期,连接到网站是可以完成 3 次握手的,然后客户端发送请求,被检测,被阻断。现在我们在 3 次握手后,服务器主动,直接发送 302/301 跳转给客户,客户没有主动过墙请求任何数据,理论上这个网站不会被干扰,以后如果墙了跳转到的域名,直接换跳转域名,而用户总有一个“网址发布站”可以访问。历史上,很多成人站,菠菜站,采用这种思路。

#代码

本代码由 ChatGPT 完成,ChatGPT 还提出了几种继续优化的方法,包括使用 sync.Pool 等。

package main

import (
	"log"
	"net"
)

var response = []byte("HTTP/1.1 302 Moved Temporarily\r\n" +
	"Content-Type: text/html\r\n" +
	"Content-Length: 0\r\n" +
	"Connection: close\r\n" +
	"Location: https://chat.acytoo.net/\r\n\r\n")

func main() {
	listener, err := net.Listen("tcp", "0.0.0.0:30081")
	if err != nil {
		log.Fatal(err)
	}

	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println(err)
			continue
		}

		go handleConnection(conn)
	}
}

func handleConnection(conn net.Conn) {
	defer conn.Close()
	conn.(*net.TCPConn).SetLinger(0)
	conn.(*net.TCPConn).SetNoDelay(true)

	_, err := conn.Write(response)
	if err != nil {
		log.Println("Error writing response:", err)
	}
}

为“尊重 ChatGPT 版权”😂,我并没有修改任何代码。实际上,golang net 默认不使用 Nagle 算法,因此不需要 SetLinger(0)SetNoDelay(true) 这两个控制语句。

上面的 golang 程序开了 30081 端口,接收 TCP 连接,在握手后,直接发送 302 redirection

可以用 Caddyfile 反代一下。

chat.acytoo.com {
       reverse_proxy localhost:30081
}

#效果

使用 wireshark 抓包,看连接过程。下图左为服务器发送给客户的 302 跳转包,右为客户端对服务器发的请求。

三次握手后,服务端主动发送了 302 包,seq 为 2793964333,ack 为 3053880358,客户端发送 ack 为 2793964333 的请求包,表示已经收到了 302 包。客户端这时发送的 seq 才为 3053879832。可惜客户端在收到了 302 跳转后,继续发送了请求,这在原博主的文章中,有介绍与解决方法,但是因为 HTTP 跳转实在没什么用,不继续搞了。