Skip to content

Instantly share code, notes, and snippets.

@korc
Last active July 9, 2017 17:05
Show Gist options
  • Save korc/6538214e8de2e1dcc4cd491b8fc36356 to your computer and use it in GitHub Desktop.
Save korc/6538214e8de2e1dcc4cd491b8fc36356 to your computer and use it in GitHub Desktop.
// This program is intented to use with iptables REDIRECT directive
// for example if your provider is blocking outgoing port 25 connections
// 1) connect to server in datacenter which allows connections
// ssh -D 1080 user@example.com
// 2) redirect all outgoing SMTP connections through this here
// iptables -t nat -A OUTPUT -p tcp --dport 25 -j REDIRECT --to-ports 4567
// *Note* for router configuration (iptables PREROUTING chain) you have to listen on other IP than 127.0.0.1
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"sync"
"syscall"
"unsafe"
"golang.org/x/net/proxy"
)
const SO_ORIGINAL_DST = 80
func getsockopt(s int, level int, name int, val uintptr, vallen *uint32) (err error) {
_, _, errno := syscall.Syscall6(syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(name), uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
if errno != 0 {
err = errno
}
return
}
func getOrigDest(conn net.Conn) (ret *net.TCPAddr, err error) {
f, err := conn.(*net.TCPConn).File()
if err != nil {
return
}
fd := f.Fd()
var addr syscall.RawSockaddrAny
size := uint32(unsafe.Sizeof(addr))
if err = getsockopt(int(fd), syscall.SOL_IP, SO_ORIGINAL_DST, uintptr(unsafe.Pointer(&addr)), &size); err != nil {
return
}
f.Close()
switch addr.Addr.Family {
case syscall.AF_INET:
pp := (*syscall.RawSockaddrInet4)(unsafe.Pointer(&addr))
pa := (*[2]byte)(unsafe.Pointer(&pp.Port))
ret = &net.TCPAddr{Port: (int(pa[0]) << 8) + int(pa[1]),
IP: net.IPv4(pp.Addr[0], pp.Addr[1], pp.Addr[2], pp.Addr[3])}
default:
err = fmt.Errorf("Unknown address family: %d", addr.Addr.Family)
return
}
return
}
func copyTcp(w, r net.Conn, wg sync.WaitGroup) {
defer wg.Done()
defer w.(*net.TCPConn).CloseWrite()
io.Copy(w, r)
}
func handleClient(conn net.Conn, origDst *net.TCPAddr, dialer proxy.Dialer) {
defer conn.Close()
if remote, err := dialer.Dial("tcp", origDst.String()); err != nil {
log.Print("error dialing to remote: ", err)
} else {
defer remote.Close()
var wg sync.WaitGroup
wg.Add(2)
go copyTcp(remote, conn, wg)
go copyTcp(conn, remote, wg)
wg.Wait()
}
}
func main() {
listenAddr := flag.String("listen", "127.0.0.1:4567", "Listen ip:port")
socksAddr := flag.String("socks", "localhost:1080", "Socks5 server address")
flag.Parse()
dialer, err := proxy.SOCKS5("tcp", *socksAddr, nil, proxy.Direct)
if err != nil {
log.Fatal("creating dialer: ", err)
}
ls, err := net.Listen("tcp", *listenAddr)
if err != nil {
log.Fatal("Listening on socket: ", err)
}
defer ls.Close()
for {
cl, err := ls.Accept()
if err != nil {
log.Fatal("Accepting connection: ", err)
}
origDst, err := getOrigDest(cl)
if err != nil {
log.Printf("Cannot get original destination: %v", err)
cl.Close()
continue
}
log.Printf("Connecting %s -> [socks5:%s] -> %s\n", cl.RemoteAddr(), *socksAddr, origDst.String())
go handleClient(cl, origDst, dialer)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment