237 lines
4.9 KiB
Go
237 lines
4.9 KiB
Go
|
package breacher
|
||
|
|
||
|
import (
|
||
|
"io"
|
||
|
"log"
|
||
|
"net"
|
||
|
"os"
|
||
|
"os/signal"
|
||
|
"sync"
|
||
|
"sync/atomic"
|
||
|
"syscall"
|
||
|
"time"
|
||
|
|
||
|
"github.com/spf13/cobra"
|
||
|
)
|
||
|
|
||
|
var forwardCmd = &cobra.Command{
|
||
|
Use: "forward [from address] [to address]",
|
||
|
Short: "port forwarding for UDP->UDP / TCP->TCP",
|
||
|
Long: `port forwarding, defaults to use tcp if no flags given
|
||
|
ex.
|
||
|
breacher forward :8080 kumoly.io:5080
|
||
|
breacher forward :8080 :8000
|
||
|
breacher forward --udp :8080 192.168.51.211:53
|
||
|
`,
|
||
|
Args: cobra.ExactArgs(2),
|
||
|
Run: func(cmd *cobra.Command, args []string) {
|
||
|
if ftcp {
|
||
|
ForwardTCP(args[0], args[1])
|
||
|
}
|
||
|
if fudp {
|
||
|
ForwardUDP(args[0], args[1])
|
||
|
}
|
||
|
if !(ftcp && fudp) {
|
||
|
ForwardTCP(args[0], args[1])
|
||
|
}
|
||
|
stop := make(chan os.Signal, 1)
|
||
|
signal.Notify(stop, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
|
||
|
<-stop
|
||
|
},
|
||
|
}
|
||
|
|
||
|
var (
|
||
|
ftcp bool
|
||
|
fudp bool
|
||
|
)
|
||
|
|
||
|
func init() {
|
||
|
forwardCmd.Flags().BoolVar(&ftcp, "tcp", false, "use tcp protocol (default if no flags given)")
|
||
|
forwardCmd.Flags().BoolVar(&fudp, "udp", false, "use udp protocol")
|
||
|
}
|
||
|
|
||
|
func ForwardTCP(from, to string) {
|
||
|
ln, err := net.Listen("tcp", from)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
go func() {
|
||
|
for {
|
||
|
conn, err := ln.Accept()
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
go func(con net.Conn) {
|
||
|
proxy, err := net.Dial("tcp", to)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
log.Println("forwarding tcp from", con.RemoteAddr())
|
||
|
go exchange(conn, proxy)
|
||
|
go exchange(proxy, conn)
|
||
|
}(conn)
|
||
|
}
|
||
|
}()
|
||
|
}
|
||
|
|
||
|
func exchange(src, dest net.Conn) {
|
||
|
defer src.Close()
|
||
|
defer dest.Close()
|
||
|
io.Copy(src, dest)
|
||
|
}
|
||
|
|
||
|
func ForwardUDP(from, to string) {
|
||
|
connIn, err := net.ListenPacket("udp", from)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return
|
||
|
}
|
||
|
go func() {
|
||
|
type pipeCache struct {
|
||
|
Pipe *io.PipeWriter
|
||
|
Ready *uintptr
|
||
|
TTL time.Time
|
||
|
}
|
||
|
type hashableAddr struct {
|
||
|
Network string
|
||
|
String string
|
||
|
}
|
||
|
pipes := make(map[hashableAddr]pipeCache)
|
||
|
pipesLock := new(sync.RWMutex)
|
||
|
go func() {
|
||
|
for {
|
||
|
time.Sleep(59 * time.Second)
|
||
|
now := time.Now()
|
||
|
for k, v := range pipes {
|
||
|
if v.TTL.Before(now) {
|
||
|
pipesLock.Lock()
|
||
|
delete(pipes, k)
|
||
|
pipesLock.Unlock()
|
||
|
v.Pipe.Close()
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
buffer := make([]byte, 65537)
|
||
|
for {
|
||
|
packetLen, addrIn, err := connIn.ReadFrom(buffer)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
if errNet, ok := err.(net.Error); ok {
|
||
|
if errNet.Temporary() {
|
||
|
log.Println(err)
|
||
|
continue
|
||
|
}
|
||
|
}
|
||
|
log.Fatalln(err)
|
||
|
}
|
||
|
log.Println("forwarding udp from", addrIn)
|
||
|
pipesLock.RLock()
|
||
|
if pipeOut, ok := pipes[hashableAddr{
|
||
|
Network: addrIn.Network(),
|
||
|
String: addrIn.String(),
|
||
|
}]; ok {
|
||
|
pipesLock.RUnlock()
|
||
|
pipeOut.TTL = time.Now().Add(180 * time.Second)
|
||
|
if atomic.LoadUintptr(pipeOut.Ready) != 0 {
|
||
|
pipeOut.Pipe.Write(buffer[:packetLen])
|
||
|
}
|
||
|
} else {
|
||
|
pipesLock.RUnlock()
|
||
|
firstPacket := make([]byte, packetLen)
|
||
|
copy(firstPacket, buffer)
|
||
|
go func(addrIn net.Addr, firstPacket []byte) {
|
||
|
connOut, err := net.Dial("udp", to)
|
||
|
var connWait sync.WaitGroup
|
||
|
connWait.Add(2)
|
||
|
if err != nil {
|
||
|
log.Println(err)
|
||
|
return
|
||
|
}
|
||
|
pipeIn, pipeOut := io.Pipe()
|
||
|
ready := new(uintptr)
|
||
|
pipe := pipeCache{
|
||
|
Pipe: pipeOut,
|
||
|
Ready: ready,
|
||
|
TTL: time.Now().Add(180 * time.Second),
|
||
|
}
|
||
|
pipesLock.Lock()
|
||
|
pipes[hashableAddr{
|
||
|
Network: addrIn.Network(),
|
||
|
String: addrIn.String(),
|
||
|
}] = pipe
|
||
|
pipesLock.Unlock()
|
||
|
go func() {
|
||
|
var err error
|
||
|
var packetLen int
|
||
|
buffer := make([]byte, 65537)
|
||
|
for {
|
||
|
atomic.StoreUintptr(ready, 1)
|
||
|
packetLen, err = pipeIn.Read(buffer)
|
||
|
atomic.StoreUintptr(ready, 0)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
_, err = connOut.Write(buffer[:packetLen])
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if err != io.EOF {
|
||
|
log.Println(err)
|
||
|
}
|
||
|
pipesLock.Lock()
|
||
|
delete(pipes, hashableAddr{
|
||
|
Network: addrIn.Network(),
|
||
|
String: addrIn.String(),
|
||
|
})
|
||
|
pipesLock.Unlock()
|
||
|
pipeIn.Close()
|
||
|
if connOutTCP, ok := connOut.(*net.TCPConn); ok {
|
||
|
connOutTCP.CloseWrite()
|
||
|
} else {
|
||
|
connOut.Close()
|
||
|
}
|
||
|
connWait.Done()
|
||
|
}()
|
||
|
go func() {
|
||
|
var err error
|
||
|
var packetLen int
|
||
|
buffer := make([]byte, 65537)
|
||
|
for {
|
||
|
connOut.SetReadDeadline(time.Now().Add(180 * time.Second))
|
||
|
packetLen, err = connOut.Read(buffer)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
_, err = connIn.WriteTo(buffer[:packetLen], addrIn)
|
||
|
if err != nil {
|
||
|
break
|
||
|
}
|
||
|
}
|
||
|
if err != io.EOF {
|
||
|
log.Println(err)
|
||
|
}
|
||
|
if connOutTCP, ok := connOut.(*net.TCPConn); ok {
|
||
|
connOutTCP.CloseRead()
|
||
|
}
|
||
|
connWait.Done()
|
||
|
}()
|
||
|
pipeOut.Write(firstPacket)
|
||
|
connWait.Wait()
|
||
|
if connOutTCP, ok := connOut.(*net.TCPConn); ok {
|
||
|
connOutTCP.Close()
|
||
|
}
|
||
|
}(addrIn, firstPacket)
|
||
|
}
|
||
|
}
|
||
|
}()
|
||
|
}
|