waf 同端口明文 HTTP 请求 301 重定向到 HTTPS

按连接首字节分发 TLS(0x16)与明文连接:明文走独立 handler
301 跳转 https 同路径(保留路径/查询串),替代被停用的
redirect80 的入口重定向职责;证书改为显式 LoadX509KeyPair
注入 TLSConfig。
This commit is contained in:
qing committed 2026-08-27 14:31:30 +00:00
1 parent 7d5d7b9e36
commit 2832566e87
1 file changed
+76 -2
+76 -2
View File
@@ -12,12 +12,15 @@
package main
import (
"bufio"
"context"
"crypto/tls"
"encoding/json"
"flag"
"fmt"
"io"
"log"
"net"
"net/http"
"os"
"os/signal"
@@ -200,9 +203,37 @@ func main() {
logclient.Info("waf", fmt.Sprintf("服务启动 instance=%s listen=%s upstream=%s rules=%s pid=%d",
*instance, cfg.Listen, cfg.Upstream, cfg.RulesDir, os.Getpid()), nil)
errCh := make(chan error, 1)
errCh := make(chan error, 2)
// 同一端口区分 TLS 与明文 HTTP:明文请求 301 重定向到 HTTPS(原 redirect80 的职责)
cert, err := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey)
if err != nil {
logclient.Error("waf", fmt.Sprintf("加载 TLS 证书失败: %v", err), nil)
logclient.Flush()
os.Exit(1)
}
server.TLSConfig.Certificates = []tls.Certificate{cert}
rawLn, err := net.Listen("tcp", cfg.Listen)
if err != nil {
logclient.Error("waf", fmt.Sprintf("监听失败: %v", err), nil)
logclient.Flush()
os.Exit(1)
}
tlsCh := make(chan net.Conn)
plainCh := make(chan net.Conn)
go acceptAndDispatch(rawLn, tlsCh, plainCh)
plainServer := &http.Server{
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.URL.RequestURI(), http.StatusMovedPermanently)
}),
ReadHeaderTimeout: 10 * time.Second,
}
go func() {
errCh <- server.ListenAndServeTLS(cfg.TLSCert, cfg.TLSKey)
errCh <- server.Serve(tls.NewListener(&chanListener{ch: tlsCh, addr: rawLn.Addr()}, server.TLSConfig))
}()
go func() {
errCh <- plainServer.Serve(&chanListener{ch: plainCh, addr: rawLn.Addr()})
}()
select {
@@ -219,10 +250,53 @@ func main() {
if err := server.Shutdown(ctx); err != nil {
logclient.Error("waf", fmt.Sprintf("优雅关闭超时: %v", err), nil)
}
plainServer.Shutdown(ctx)
logclient.Flush()
}
}
// bufferedConn 保留已 peek 的首字节,避免后续 Read 丢数据。
type bufferedConn struct {
net.Conn
r *bufio.Reader
}
func (c *bufferedConn) Read(p []byte) (int, error) { return c.r.Read(p) }
// chanListener 从 channel 取连接,供两个 http.Server 分别消费 TLS / 明文连接。
type chanListener struct {
ch chan net.Conn
addr net.Addr
}
func (l *chanListener) Accept() (net.Conn, error) {
c, ok := <-l.ch
if !ok {
return nil, io.EOF
}
return c, nil
}
func (l *chanListener) Close() error { return nil }
func (l *chanListener) Addr() net.Addr { return l.addr }
// acceptAndDispatch 按首字节分发:0x16 为 TLS ClientHello → TLS 通道,其余 → 明文 HTTP 通道。
func acceptAndDispatch(ln net.Listener, tlsCh, plainCh chan<- net.Conn) {
for {
c, err := ln.Accept()
if err != nil {
return
}
br := bufio.NewReader(c)
b, err := br.Peek(1)
if err == nil && len(b) == 1 && b[0] == 0x16 {
tlsCh <- &bufferedConn{Conn: c, r: br}
} else {
plainCh <- &bufferedConn{Conn: c, r: br}
}
}
}
func loadConfig(path string) *Config {
data, err := os.ReadFile(path)
if err != nil {