Skip to content

Instantly share code, notes, and snippets.

@time-river
Last active May 13, 2022 08:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save time-river/210c730a66f5bf62b1fcc3cfc163335c to your computer and use it in GitHub Desktop.
Save time-river/210c730a66f5bf62b1fcc3cfc163335c to your computer and use it in GitHub Desktop.
a golang demo that makes IPv4 http proxy to global tcp transparent proxy

Introduction

a golang demo that makes IPv4 http proxy to global tcp transparent proxy

Usage

# iptables -t nat -A PREROUTING -p tcp -m tcp --dport 80 -j REDIRECT --to-ports 1080
# go run main.go proxy.go

Key

// get original destination:port
addr, err := syscall.GetsockoptIPv6Mreq(int(file.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)

// http CONNECT method
httpHeader := fmt.Sprintf(
	"CONNECT %s HTTP/1.1\r\n"+
		"Host: %s\r\n"+
		"%s"+
		"Proxy-Connection: Keep-Alive\r\n"+
		"\r\n",
  origRemote, origRemote, auth)

"Proxy-Connection: Keep-Alive\r\n" is not necessory

Thanks

package main
import (
"fmt"
"io"
"log"
"net"
"os"
"sync"
"syscall"
)
const SO_ORIGINAL_DST = 80
func main() {
pscheme := "http"
paddr := "127.0.0.1"
pport := "3128"
pauth := "basic YWxhZGRpbjpvcGVuc2VzYW1l"
tcpAddr, err := net.ResolveTCPAddr("tcp", paddr+":"+pport)
if err != nil {
panic(err)
}
proxy := &Proxy{
scheme: pscheme,
addr: paddr,
port: pport,
auth: pauth,
tcpAddr: tcpAddr,
}
addr, err := net.ResolveTCPAddr("tcp", ":1080")
if err != nil {
log.Panicln(err)
}
server, err := net.ListenTCP("tcp", addr)
if err != nil {
log.Panicln(err)
}
for {
conn, err := server.AcceptTCP()
if err != nil {
log.Println("accept error: ", err)
continue
}
go connHandler(conn, proxy)
}
}
func connHandler(localConn *net.TCPConn, proxy *Proxy) {
defer localConn.Close()
connFile, err := localConn.File()
if err != nil {
log.Println("leftConn.File: ", err)
return
}
fmt.Println("......")
origRemote, err := getOrigAddr(connFile)
if err != nil {
log.Println("getOrigAddr: ", err)
return
}
remoteConn, resp, err := proxy.CreateSession(origRemote)
if err != nil && resp == nil {
log.Println("CreateSession: ", err)
return
} else if err != nil && resp != nil {
log.Println("CreateSession: ", err)
statusCode := []byte("HTTP/1.1 " + resp.Status + "\r\n")
localConn.Write(statusCode)
resp.Header.Write(localConn)
localConn.Write([]byte{'\r', '\n'})
io.Copy(localConn, resp.Body)
return
}
defer remoteConn.Close()
var streamWait sync.WaitGroup
streamWait.Add(2)
streamConn := func(dst io.Writer, src io.Reader) {
_, err := io.Copy(dst, src)
if err != nil {
log.Panicln("streamConn: ", err)
}
streamWait.Done()
}
go streamConn(remoteConn, localConn)
go streamConn(localConn, remoteConn)
streamWait.Wait()
}
func getOrigAddr(file *os.File) (string, error) {
addr, err := syscall.GetsockoptIPv6Mreq(int(file.Fd()), syscall.IPPROTO_IP, SO_ORIGINAL_DST)
if err != nil {
log.Println("syscall.GetsockoptIPv6Mreq error: %w", err)
return "", err
}
remote := fmt.Sprintf("%d.%d.%d.%d:%d",
addr.Multiaddr[4], addr.Multiaddr[5], addr.Multiaddr[6], addr.Multiaddr[7],
uint16(addr.Multiaddr[2])<<8+uint16(addr.Multiaddr[3]))
log.Println("remote: ", remote)
return remote, nil
}
package main
import (
"bufio"
"fmt"
"log"
"net"
"net/http"
"strings"
)
// Proxy -- http proxy client struct
type Proxy struct {
scheme string
addr string
port string
auth string
tcpAddr *net.TCPAddr
}
// CreateSession -- create proxy session
func (p *Proxy) CreateSession(origRemote string) (*net.TCPConn, *http.Response, error) {
var auth string = ""
if strings.Compare(p.scheme, "http") != 0 {
return nil, nil, fmt.Errorf("only support http, current: %s", p.scheme)
}
conn, err := net.DialTCP("tcp", nil, p.tcpAddr)
if err != nil {
return nil, nil, err
}
if strings.Contains(p.auth, "basic") {
auth = "Proxy-Authorization: " + p.auth + "\r\n"
}
httpHeader := fmt.Sprintf(
"CONNECT %s HTTP/1.1\r\n"+
"Host: %s\r\n"+
"%s"+
"Proxy-Connection: Keep-Alive\r\n"+
"\r\n",
origRemote, origRemote, auth)
log.Println("http header:\n", httpHeader)
_, err = conn.Write([]byte(httpHeader))
if err != nil {
conn.Close()
return nil, nil, err
}
reader := bufio.NewReader(conn)
resp, err := http.ReadResponse(reader, nil)
if err != nil {
conn.Close()
return nil, nil, err
}
if resp.StatusCode != http.StatusOK {
conn.Close()
return nil, resp, fmt.Errorf("http response code is not 200, got: %s", resp.Status)
}
log.Println("http CONNECT success")
return conn, nil, nil
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment