Skip to content

Commit

Permalink
Remove splice, use naive way
Browse files Browse the repository at this point in the history
  • Loading branch information
Jipok committed Aug 10, 2024
1 parent 5e4a20d commit 4026e90
Show file tree
Hide file tree
Showing 2 changed files with 112 additions and 68 deletions.
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ A Go-based proxy tool that redirects blocked or throttled domains through a user

- Requires root privileges to set up nf/iptables rules
- Cleans up iptables rules on exit
- Data copying is done using linux syscall [splice](https://en.wikipedia.org/wiki/Splice_%28system_call%29).
- After start the tool sets GID to 2354
- `-proxyList` and `-blockList` accepts a semicolon-separated list of files. Like `proxy.lst; my.txt`

Expand Down
179 changes: 112 additions & 67 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import (
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"runtime"
"sync"
"syscall"

"golang.org/x/net/proxy"
Expand All @@ -19,14 +21,14 @@ import (
const GID = 2354

var (
mainPort = flag.String("mainPort", "21345", "Port to listen for iptables REDIRECT")
spliceBufferSize = flag.Int("spliceBufferSize", 131072, "Buffer size for linux splice(2)")
socksAddr = flag.String("socks5", "127.0.0.1:1080", "SOCKS5 proxy address")
interfaceName = flag.String("interface", "", "proxy through interface instead of socks5")
proxyListFile = flag.String("proxyList", "proxy.lst", "File with list of domains to proxy")
blockListFile = flag.String("blockList", "blocks.lst", "File with list of domains to BLOCK")
verbose = flag.Bool("v", false, "Print all dials")
socksDialer proxy.Dialer
mainPort = flag.String("mainPort", "21345", "Port to listen for iptables REDIRECT")
bufferSize = flag.Int("bufferSize", 262144, "Buffer size for copying pipe")
socksAddr = flag.String("socks5", "127.0.0.1:1080", "SOCKS5 proxy address")
interfaceName = flag.String("interface", "", "proxy through interface instead of socks5")
proxyListFile = flag.String("proxyList", "proxy.lst", "File with list of domains to proxy")
blockListFile = flag.String("blockList", "blocks.lst", "File with list of domains to BLOCK")
verbose = flag.Bool("v", false, "Print all dials")
socksDialer proxy.Dialer
)

func red(str string) string {
Expand Down Expand Up @@ -125,7 +127,7 @@ func main() {
continue
}

go handleConnection(tcpConn, *socksAddr)
go handleConnection(tcpConn)
}
}()

Expand All @@ -134,7 +136,7 @@ func main() {
log.Println("Shutting down...")
}

func handleConnection(conn *net.TCPConn, socks5Addr string) {
func handleConnection(conn *net.TCPConn) {
defer conn.Close()
peeked, serverName, _ := readServerName(conn)

Expand Down Expand Up @@ -366,71 +368,114 @@ func handleDirectly(incomingConn *net.TCPConn, originalDst net.Addr, peeked []by
pipe(incomingConn, upstreamConn.(*net.TCPConn))
}

// pipe sets up bidirectional data transfer between two TCP connections using splice.
func pipe(incomingConn, upstreamConn *net.TCPConn) {
// Create pipes for splice
var pipeFdsIncomingToUpstream [2]int
var pipeFdsUpstreamToIncoming [2]int
if err := unix.Pipe2(pipeFdsIncomingToUpstream[:], unix.O_NONBLOCK); err != nil {
log.Fatalf("failed to create pipe: %v", err)
}
if err := unix.Pipe2(pipeFdsUpstreamToIncoming[:], unix.O_NONBLOCK); err != nil {
log.Fatalf("failed to create pipe: %v", err)
}
var bufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, *bufferSize)
},
}

// Faster that linux splice. Idk why
func pipe(src, dst net.Conn) {
var wg sync.WaitGroup
wg.Add(2)

copyData := func(writer, reader net.Conn) {
defer wg.Done()
buffer := bufferPool.Get().([]byte)
defer bufferPool.Put(buffer)

transfer := func(srcFd, dstFd int, pipeFds [2]int) error {
for {
bytes, err := unix.Splice(srcFd, nil, pipeFds[1], nil, *spliceBufferSize, unix.SPLICE_F_MOVE|unix.SPLICE_F_NONBLOCK)
n, err := reader.Read(buffer)
if err != nil {
if err == unix.EAGAIN {
continue
if err == io.EOF || err == net.ErrClosed {
break
}
return err
}
if bytes == 0 {
return nil
log.Printf("Reading error : %v", err)
break
}

for bytes > 0 {
written, err := unix.Splice(pipeFds[0], nil, dstFd, nil, int(bytes), unix.SPLICE_F_MOVE|unix.SPLICE_F_NONBLOCK|unix.SPLICE_F_MORE)
if err != nil {
if err == unix.EAGAIN {
continue
}
// Hide "broken pipe" and "bad file descriptor" spam due to closed connection
if err == unix.EPIPE || err == unix.EBADFD {
return nil
}
return err
}
bytes -= written
// Записываем прочитанные данные в writer
_, err = writer.Write(buffer[:n])
if err == io.EOF || err == net.ErrClosed {
break
}
if err != nil {
log.Printf("Writing error: %v", err)
break
}
}
}

srcFdIncoming, err := incomingConn.File()
if err != nil {
log.Printf("failed to get file descriptor for incomingConn: %v", err)
}
defer srcFdIncoming.Close()
srcFdUpstream, err := upstreamConn.File()
if err != nil {
log.Printf("failed to get file descriptor for upstreamConn: %v", err)
}
defer srcFdUpstream.Close()

// Transfer data from incomingConn to upstreamConn
done := make(chan struct{})
go func() {
if err := transfer(int(srcFdIncoming.Fd()), int(srcFdUpstream.Fd()), pipeFdsIncomingToUpstream); err != nil {
log.Printf("error during transfer from incoming to upstream: %v", err)
}
close(done)
}()

// Transfer data from upstreamConn to incomingConn
if err := transfer(int(srcFdUpstream.Fd()), int(srcFdIncoming.Fd()), pipeFdsUpstreamToIncoming); err != nil {
log.Printf("error during transfer from upstream to incoming: %v", err)
}
<-done
go copyData(dst, src)
go copyData(src, dst)
wg.Wait()
}

// pipe sets up bidirectional data transfer between two TCP connections using splice.
// func pipe(incomingConn, upstreamConn *net.TCPConn) {
// // Create pipes for splice
// var pipeFdsIncomingToUpstream [2]int
// var pipeFdsUpstreamToIncoming [2]int
// if err := unix.Pipe2(pipeFdsIncomingToUpstream[:], unix.O_NONBLOCK); err != nil {
// log.Fatalf("failed to create pipe: %v", err)
// }
// if err := unix.Pipe2(pipeFdsUpstreamToIncoming[:], unix.O_NONBLOCK); err != nil {
// log.Fatalf("failed to create pipe: %v", err)
// }

// transfer := func(srcFd, dstFd int, pipeFds [2]int) error {
// for {
// bytes, err := unix.Splice(srcFd, nil, pipeFds[1], nil, *bufferSize, unix.SPLICE_F_MOVE|unix.SPLICE_F_NONBLOCK)
// if err != nil {
// if err == unix.EAGAIN {
// continue
// }
// return err
// }
// if bytes == 0 {
// return nil
// }

// for bytes > 0 {
// written, err := unix.Splice(pipeFds[0], nil, dstFd, nil, int(bytes), unix.SPLICE_F_MOVE|unix.SPLICE_F_NONBLOCK|unix.SPLICE_F_MORE)
// if err != nil {
// if err == unix.EAGAIN {
// continue
// }
// // Hide "broken pipe" and "bad file descriptor" spam due to closed connection
// if err == unix.EPIPE || err == unix.EBADFD {
// return nil
// }
// return err
// }
// bytes -= written
// }
// }
// }

// srcFdIncoming, err := incomingConn.File()
// if err != nil {
// log.Printf("failed to get file descriptor for incomingConn: %v", err)
// }
// defer srcFdIncoming.Close()
// srcFdUpstream, err := upstreamConn.File()
// if err != nil {
// log.Printf("failed to get file descriptor for upstreamConn: %v", err)
// }
// defer srcFdUpstream.Close()

// // Transfer data from incomingConn to upstreamConn
// done := make(chan struct{})
// go func() {
// if err := transfer(int(srcFdIncoming.Fd()), int(srcFdUpstream.Fd()), pipeFdsIncomingToUpstream); err != nil {
// log.Printf("error during transfer from incoming to upstream: %v", err)
// }
// close(done)
// }()

// // Transfer data from upstreamConn to incomingConn
// if err := transfer(int(srcFdUpstream.Fd()), int(srcFdIncoming.Fd()), pipeFdsUpstreamToIncoming); err != nil {
// log.Printf("error during transfer from upstream to incoming: %v", err)
// }
// <-done
// }

0 comments on commit 4026e90

Please sign in to comment.