breach/breacher/forward.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)
}
}
}()
}