fix: packet processing for http request (#230)

* fix: http proxy

* chore: make a function for setting connection timeout

* chore: rename parameters for consistency
This commit is contained in:
xvzc 2024-09-03 16:07:11 +09:00 committed by GitHub
parent 19ec6980ba
commit 79d255719e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 369 additions and 19 deletions

View File

@ -2,6 +2,7 @@ package packet
import (
"bufio"
"bytes"
"io"
"net"
"net/http"
@ -96,29 +97,26 @@ func (p *HttpRequest) IsConnectMethod() bool {
func (p *HttpRequest) Tidy() {
s := string(p.raw)
lines := strings.Split(s, "\r\n")
parts := strings.Split(s, "\r\n\r\n")
meta := strings.Split(parts[0], "\r\n")
lines[0] = p.method + " " + p.path + " " + p.version
meta[0] = p.method + " " + p.path + " " + p.version
for i := 0; i < len(lines); i++ {
if strings.HasPrefix(lines[i], "Proxy-Connection") {
lines[i] = ""
}
}
var buf bytes.Buffer
buf.Grow(len(p.raw))
result := ""
for i := 0; i < len(lines); i++ {
if lines[i] == "" {
crLF := []byte{0xD, 0xA}
for _, m := range meta {
if strings.HasPrefix(m, "Proxy-Connection") {
continue
}
result += lines[i] + "\r\n"
buf.WriteString(m)
buf.Write(crLF)
}
buf.Write(crLF)
buf.WriteString(parts[1])
result += "\r\n"
p.raw = []byte(result)
p.raw = buf.Bytes()
}
func parse(rdr io.Reader) (*HttpRequest, error) {

14
proxy/handler/conn.go Normal file
View File

@ -0,0 +1,14 @@
package handler
import (
"net"
"time"
)
func setConnectionTimeout(conn *net.TCPConn, timeout int) error {
if timeout <= 0 {
return nil
}
return conn.SetReadDeadline(time.Now().Add(time.Millisecond * time.Duration(timeout)))
}

124
proxy/handler/http.go Normal file
View File

@ -0,0 +1,124 @@
package handler
import (
"context"
"net"
"strconv"
"time"
"github.com/xvzc/SpoofDPI/packet"
"github.com/xvzc/SpoofDPI/util"
"github.com/xvzc/SpoofDPI/util/log"
)
type HttpHandler struct {
bufferSize int
protocol string
port int
timeout int
}
func NewHttpHandler(timeout int) *HttpHandler {
return &HttpHandler{
bufferSize: 1024,
protocol: "HTTP",
port: 80,
timeout: timeout,
}
}
func (h *HttpHandler) Serve(ctx context.Context, lConn *net.TCPConn, pkt *packet.HttpRequest, ip string) {
ctx = util.GetCtxWithScope(ctx, h.protocol)
logger := log.GetCtxLogger(ctx)
// Create a connection to the requested server
var port int = 80
var err error
if pkt.Port() != "" {
port, err = strconv.Atoi(pkt.Port())
if err != nil {
logger.Debug().Msgf("error while parsing port for %s aborting..", pkt.Domain())
}
}
rConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.ParseIP(ip), Port: port})
if err != nil {
lConn.Close()
logger.Debug().Msgf("%s", err)
return
}
logger.Debug().Msgf("new connection to the server %s -> %s", rConn.LocalAddr(), pkt.Domain())
go h.deliverResponse(ctx, rConn, lConn, pkt.Domain(), lConn.RemoteAddr().String())
go h.deliverRequest(ctx, lConn, rConn, lConn.RemoteAddr().String(), pkt.Domain())
_, err = rConn.Write(pkt.Raw())
if err != nil {
logger.Debug().Msgf("error sending request to %s: %s", pkt.Domain(), err)
return
}
}
func (h *HttpHandler) deliverRequest(ctx context.Context, from *net.TCPConn, to *net.TCPConn, fd string, td string) {
ctx = util.GetCtxWithScope(ctx, h.protocol)
logger := log.GetCtxLogger(ctx)
defer func() {
from.Close()
to.Close()
logger.Debug().Msgf("closing proxy connection: %s -> %s", fd, td)
}()
for {
err := setConnectionTimeout(from, h.timeout)
if err != nil {
logger.Debug().Msgf("error while setting connection deadline for %s: %s", fd, err)
}
pkt, err := packet.ReadHttpRequest(from)
if err != nil {
logger.Debug().Msgf("error reading from %s: %s", fd, err)
return
}
pkt.Tidy()
if _, err := to.Write(pkt.Raw()); err != nil {
logger.Debug().Msgf("error Writing to %s", td)
return
}
}
}
func (h *HttpHandler) deliverResponse(ctx context.Context, from *net.TCPConn, to *net.TCPConn, fd string, td string) {
ctx = util.GetCtxWithScope(ctx, h.protocol)
logger := log.GetCtxLogger(ctx)
defer func() {
from.Close()
to.Close()
logger.Debug().Msgf("closing proxy connection: %s -> %s", fd, td)
}()
buf := make([]byte, h.bufferSize)
for {
err := setConnectionTimeout(from, h.timeout)
if err != nil {
logger.Debug().Msgf("error while setting connection deadline for %s: %s", fd, err)
}
bytesRead, err := ReadBytes(ctx, from, buf)
if err != nil {
logger.Debug().Msgf("error reading from %s: %s", fd, err)
return
}
if _, err := to.Write(bytesRead); err != nil {
logger.Debug().Msgf("error Writing to %s", td)
return
}
}
}

178
proxy/handler/https.go Normal file
View File

@ -0,0 +1,178 @@
package handler
import (
"context"
"net"
"regexp"
"strconv"
"time"
"github.com/xvzc/SpoofDPI/packet"
"github.com/xvzc/SpoofDPI/util"
"github.com/xvzc/SpoofDPI/util/log"
)
type HttpsHandler struct {
bufferSize int
protocol string
port int
timeout int
windowsize int
exploit bool
allowedPatterns []*regexp.Regexp
}
func NewHttpsHandler(timeout int, windowSize int, allowedPatterns []*regexp.Regexp, exploit bool) *HttpsHandler {
return &HttpsHandler{
bufferSize: 1024,
protocol: "HTTPS",
port: 443,
timeout: timeout,
windowsize: windowSize,
allowedPatterns: allowedPatterns,
exploit: exploit,
}
}
func (h *HttpsHandler) Serve(ctx context.Context, lConn *net.TCPConn, initPkt *packet.HttpRequest, ip string) {
ctx = util.GetCtxWithScope(ctx, h.protocol)
logger := log.GetCtxLogger(ctx)
// Create a connection to the requested server
var err error
if initPkt.Port() != "" {
h.port, err = strconv.Atoi(initPkt.Port())
if err != nil {
logger.Debug().Msgf("error parsing port for %s aborting..", initPkt.Domain())
}
}
rConn, err := net.DialTCP("tcp", nil, &net.TCPAddr{IP: net.ParseIP(ip), Port: h.port})
if err != nil {
lConn.Close()
logger.Debug().Msgf("%s", err)
return
}
logger.Debug().Msgf("new connection to the server %s -> %s", rConn.LocalAddr(), initPkt.Domain())
_, err = lConn.Write([]byte(initPkt.Version() + " 200 Connection Established\r\n\r\n"))
if err != nil {
logger.Debug().Msgf("error sending 200 connection established to the client: %s", err)
return
}
logger.Debug().Msgf("sent connection estabalished to %s", lConn.RemoteAddr())
// Read client hello
m, err := packet.ReadTLSMessage(lConn)
if err != nil || !m.IsClientHello() {
logger.Debug().Msgf("error reading client hello from %s: %s", lConn.RemoteAddr().String(), err)
return
}
clientHello := m.Raw
logger.Debug().Msgf("client sent hello %d bytes", len(clientHello))
// Generate a go routine that reads from the server
go h.communicate(ctx, rConn, lConn, initPkt.Domain(), lConn.RemoteAddr().String())
go h.communicate(ctx, lConn, rConn, lConn.RemoteAddr().String(), initPkt.Domain())
if h.exploit {
logger.Debug().Msgf("writing chunked client hello to %s", initPkt.Domain())
chunks := splitInChunks(ctx, clientHello, h.windowsize)
if _, err := writeChunks(rConn, chunks); err != nil {
logger.Debug().Msgf("error writing chunked client hello to %s: %s", initPkt.Domain(), err)
return
}
} else {
logger.Debug().Msgf("writing plain client hello to %s", initPkt.Domain())
if _, err := rConn.Write(clientHello); err != nil {
logger.Debug().Msgf("error writing plain client hello to %s: %s", initPkt.Domain(), err)
return
}
}
}
func (h *HttpsHandler) communicate(ctx context.Context, from *net.TCPConn, to *net.TCPConn, fd string, td string) {
ctx = util.GetCtxWithScope(ctx, h.protocol)
logger := log.GetCtxLogger(ctx)
defer func() {
from.Close()
to.Close()
logger.Debug().Msgf("closing proxy connection: %s -> %s", fd, td)
}()
buf := make([]byte, h.bufferSize)
for {
err := setConnectionTimeout(from, h.timeout)
if err != nil {
logger.Debug().Msgf("error while setting connection deadline for %s: %s", fd, err)
}
bytesRead, err := ReadBytes(ctx, from, buf)
if err != nil {
logger.Debug().Msgf("error reading from %s: %s", fd, err)
return
}
if _, err := to.Write(bytesRead); err != nil {
logger.Debug().Msgf("error Writing to %s", td)
return
}
}
}
func splitInChunks(ctx context.Context, bytes []byte, size int) [][]byte {
logger := log.GetCtxLogger(ctx)
var chunks [][]byte
var raw []byte = bytes
logger.Debug().Msgf("window-size: %d", size)
if size > 0 {
for {
if len(raw) == 0 {
break
}
// necessary check to avoid slicing beyond
// slice capacity
if len(raw) < size {
size = len(raw)
}
chunks = append(chunks, raw[0:size])
raw = raw[size:]
}
return chunks
}
// When the given window-size <= 0
if len(raw) < 1 {
return [][]byte{raw}
}
logger.Debug().Msg("using legacy fragmentation")
return [][]byte{raw[:1], raw[1:]}
}
func writeChunks(conn *net.TCPConn, c [][]byte) (n int, err error) {
total := 0
for i := 0; i < len(c); i++ {
b, err := conn.Write(c[i])
if err != nil {
return 0, nil
}
total += b
}
return total, nil
}

26
proxy/handler/io.go Normal file
View File

@ -0,0 +1,26 @@
package handler
import (
"context"
"errors"
"net"
)
func ReadBytes(ctx context.Context, conn *net.TCPConn, dest []byte) ([]byte, error) {
n, err := readBytesInternal(ctx, conn, dest)
return dest[:n], err
}
func readBytesInternal(ctx context.Context, conn *net.TCPConn, dest []byte) (int, error) {
totalRead, err := conn.Read(dest)
if err != nil {
var opError *net.OpError
switch {
case errors.As(err, &opError) && opError.Timeout():
return totalRead, errors.New("timed out")
default:
return totalRead, err
}
}
return totalRead, nil
}

View File

@ -9,6 +9,7 @@ import (
"github.com/xvzc/SpoofDPI/dns"
"github.com/xvzc/SpoofDPI/packet"
"github.com/xvzc/SpoofDPI/proxy/handler"
"github.com/xvzc/SpoofDPI/util"
"github.com/xvzc/SpoofDPI/util/log"
)
@ -25,6 +26,10 @@ type Proxy struct {
allowedPattern []*regexp.Regexp
}
type Handler interface {
Serve(ctx context.Context, lConn *net.TCPConn, pkt *packet.HttpRequest, ip string)
}
func New(config *util.Config) *Proxy {
return &Proxy{
addr: config.Addr,
@ -74,7 +79,9 @@ func (pxy *Proxy) Start(ctx context.Context) {
return
}
logger.Debug().Msgf("request from %s\n\n%s", conn.RemoteAddr(), pkt.Raw())
pkt.Tidy()
logger.Debug().Msgf("request from %s\n\n%s", conn.RemoteAddr(), string(pkt.Raw()))
if !pkt.IsValidMethod() {
logger.Debug().Msgf("unsupported method: %s", pkt.Method())
@ -100,11 +107,14 @@ func (pxy *Proxy) Start(ctx context.Context) {
return
}
var h Handler
if pkt.IsConnectMethod() {
pxy.handleHttps(ctx, conn.(*net.TCPConn), matched, pkt, ip)
h = handler.NewHttpsHandler(pxy.timeout, pxy.windowSize, pxy.allowedPattern, matched)
} else {
pxy.handleHttp(ctx, conn.(*net.TCPConn), pkt, ip)
h = handler.NewHttpHandler(pxy.timeout)
}
h.Serve(ctx, conn.(*net.TCPConn), pkt, ip)
}()
}
}