package main
import (
"fmt"
"io"
"log"
"net"
"os"
"strconv"
"syscall"
)
const network = "tcp"
const bufferSize = 4096
var destinationIP *[4]byte
var destinationPort int
func main() {
parseDestination()
listen, err := net.Listen(network, os.Args[1])
if err != nil {
log.Println(err)
os.Exit(1)
}
log.Printf("waiting for incomming connection...")
defer listen.Close()
for {
conn, err := listen.Accept()
if err != nil {
log.Println(err)
continue
}
log.Printf("client %s accepted", conn.RemoteAddr().String())
go clientHandler(conn, log.New(os.Stdout, "[" + conn.RemoteAddr().String() + "]", log.Ldate | log.Ltime))
}
}
func clientHandler(client net.Conn, logger *log.Logger) {
defer client.Close()
server, err := createTransparentSocket(client, os.Args[2])
if err != nil {
logger.Println(err)
return
}
defer server.Close()
streamCopy := func(dst io.Writer, src io.Reader, direction string, logger *log.Logger) {
n, _ := read2Write(dst, src)
logger.Printf("%s %d bytes copied", direction, n)
client.Close()
server.Close()
}
go streamCopy(client, server, "to client", logger)
streamCopy(server, client, "from client", logger)
}
func createTransparentSocket(client net.Conn, destination string) (net.Conn, error) {
var socketFD int
var file *os.File
var server net.Conn
var errorPrefix string
var clientIP *net.TCPAddr
var clientIPBytes [4]byte
var err error
var ok bool
// Create transparent socket
if socketFD, err = syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP); err != nil {
errorPrefix = "syscall.socket()"
goto ReturnError
}
if err = syscall.SetsockoptInt(socketFD, syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
errorPrefix = "syscall.SetsockoptInt()"
goto CloseSocket
}
if clientIP, ok = client.RemoteAddr().(*net.TCPAddr); ok == false {
err = fmt.Errorf("unsupport client type")
errorPrefix = "client.RemoteAddr().(*net.IPAddr)"
goto CloseSocket
}
copy(clientIPBytes[:], clientIP.IP.To4())
// Use client's IP address as socket local address
if err = syscall.Bind(socketFD, &syscall.SockaddrInet4{
Port: 0, // Port auto assignment
Addr: clientIPBytes,
}); err != nil {
errorPrefix = "syscall.Bind()"
goto CloseSocket
}
// Connect to server
if err = syscall.Connect(socketFD, &syscall.SockaddrInet4{
Port: destinationPort,
Addr: *destinationIP,
}); err != nil && err.Error() != "operation now in progress" {
errorPrefix = "syscall.Connect()"
goto CloseSocket
}
file = os.NewFile(uintptr(socketFD), destination)
server, err = net.FileConn(file)
if err == nil {
syscall.Close(socketFD) // net.FileConn() has already duplicated this file descriptor
return server, nil
}
errorPrefix = "net.FileConn()"
CloseSocket:
syscall.Close(socketFD)
ReturnError:
return nil, fmt.Errorf("%s: %s", errorPrefix, err)
}
func parseDestination() {
var err error
var host, port string
if host, port, err = net.SplitHostPort(os.Args[2]); err != nil {
println(err)
os.Exit(1)
}
ip := net.ParseIP(host).To4()
destinationIP = &[4]byte{ip[0], ip[1], ip[2], ip[3]}
destinationPort, err = strconv.Atoi(port)
if err != nil {
println(err)
os.Exit(1)
}
}
/**
from: https://github.com/xtaci/kcptun/blob/master/generic/copy.go
*/
func read2Write(dst io.Writer, src io.Reader) (written int64, err error) {
// If the reader has a WriteTo method, use it to do the copy.
// Avoids an allocation and a copy.
if wt, ok := src.(io.WriterTo); ok {
return wt.WriteTo(dst)
}
// Similarly, if the writer has a ReadFrom method, use it to do the copy.
if rt, ok := dst.(io.ReaderFrom); ok {
return rt.ReadFrom(src)
}
// fallback to standard io.CopyBuffer
buf := make([]byte, bufferSize)
return io.CopyBuffer(dst, src, buf)
}