Skip to content

Instantly share code, notes, and snippets.

@vmihailenco
Created November 20, 2011 15:22
Show Gist options
  • Save vmihailenco/1380352 to your computer and use it in GitHub Desktop.
Save vmihailenco/1380352 to your computer and use it in GitHub Desktop.
Simple TCP proxy in Golang
package main
import (
"bytes"
"encoding/hex"
"flag"
"fmt"
"io"
"log"
"net"
)
var localAddr *string = flag.String("l", "localhost:9999", "local address")
var remoteAddr *string = flag.String("r", "localhost:80", "remote address")
func proxyConn(conn *net.TCPConn) {
rAddr, err := net.ResolveTCPAddr("tcp", *remoteAddr)
if err != nil {
panic(err)
}
rConn, err := net.DialTCP("tcp", nil, rAddr)
if err != nil {
panic(err)
}
defer rConn.Close()
buf := &bytes.Buffer{}
for {
data := make([]byte, 256)
n, err := conn.Read(data)
if err != nil {
panic(err)
}
buf.Write(data[:n])
if data[0] == 13 && data[1] == 10 {
break
}
}
if _, err := rConn.Write(buf.Bytes()); err != nil {
panic(err)
}
log.Printf("sent:\n%v", hex.Dump(buf.Bytes()))
data := make([]byte, 1024)
n, err := rConn.Read(data)
if err != nil {
if err != io.EOF {
panic(err)
} else {
log.Printf("received err: %v", err)
}
}
log.Printf("received:\n%v", hex.Dump(data[:n]))
}
func handleConn(in <-chan *net.TCPConn, out chan<- *net.TCPConn) {
for conn := range in {
proxyConn(conn)
out <- conn
}
}
func closeConn(in <-chan *net.TCPConn) {
for conn := range in {
conn.Close()
}
}
func main() {
flag.Parse()
fmt.Printf("Listening: %v\nProxying: %v\n\n", *localAddr, *remoteAddr)
addr, err := net.ResolveTCPAddr("tcp", *localAddr)
if err != nil {
panic(err)
}
listener, err := net.ListenTCP("tcp", addr)
if err != nil {
panic(err)
}
pending, complete := make(chan *net.TCPConn), make(chan *net.TCPConn)
for i := 0; i < 5; i++ {
go handleConn(pending, complete)
}
go closeConn(complete)
for {
conn, err := listener.AcceptTCP()
if err != nil {
panic(err)
}
pending <- conn
}
}
@apuppy
Copy link

apuppy commented May 5, 2021

fyi, here is a much simpler version that does pretty much the same thing:

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
)

var localAddr *string = flag.String("l", "localhost:9999", "local address")
var remoteAddr *string = flag.String("r", "localhost:80", "remote address")

func main() {
	flag.Parse()

	fmt.Printf("Listening: %v\nProxying: %v\n\n", *localAddr, *remoteAddr)

	listener, err := net.Listen("tcp", *localAddr)
	if err != nil {
		panic(err)
	}
	for {
		conn, err := listener.Accept()
		if err != nil {
			log.Println("error accepting connection", err)
			continue
		}
		go func() {
			conn2, err := net.Dial("tcp", *remoteAddr)
			if err != nil {
				log.Println("error dialing remote addr", err)
				return
			}
			go io.Copy(conn2, conn)
			io.Copy(conn, conn2)
			conn2.Close()
			conn.Close()
		}()
	}
}

Awesome.

@dragonly
Copy link

dragonly commented Jun 8, 2021

@maxmcd Good job!

@rjshrjndrn
Copy link

fyi, here is a much simpler version:

package main

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
)

var localAddr *string = flag.String("l", "localhost:9999", "local address")
var remoteAddr *string = flag.String("r", "localhost:80", "remote address")

func main() {
	flag.Parse()
	fmt.Printf("Listening: %v\nProxying: %v\n\n", *localAddr, *remoteAddr)

	listener, err := net.Listen("tcp", *localAddr)
	if err != nil {
		panic(err)
	}
	for {
		conn, err := listener.Accept()
		log.Println("New connection", conn.RemoteAddr())
		if err != nil {
			log.Println("error accepting connection", err)
			continue
		}
		go func() {
			defer conn.Close()
			conn2, err := net.Dial("tcp", *remoteAddr)
			if err != nil {
				log.Println("error dialing remote addr", err)
				return
			}
			defer conn2.Close()
			closer := make(chan struct{}, 2)
			go copy(closer, conn2, conn)
			go copy(closer, conn, conn2)
			<-closer
			log.Println("Connection complete", conn.RemoteAddr())
		}()
	}
}

func copy(closer chan struct{}, dst io.Writer, src io.Reader) {
	_, _ = io.Copy(dst, src)
	closer <- struct{}{} // connection is closed, send signal to stop proxy
}

edit: There was a bug in the first version. Now when either connection is closed it will shut down the entire proxy

Also added this here: https://github.com/maxmcd/tcp-proxy

If you want to print the data in console for debugging pupose,

func copy(closer chan struct{}, dst io.Writer, src io.Reader) {
	io.Copy(os.Stdout, io.TeeReader(src, dst))
	closer <- struct{}{} // connection is closed, send signal to stop proxy
}

@seamanm
Copy link

seamanm commented Oct 11, 2022

If I want to be before io.Copy(os.Stdout, io.TeeReader(src, dst)) print the data in console for debugging pupose,what I can do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment