feat: TCP redirect mode

This commit is contained in:
Toby 2023-09-10 17:32:30 -07:00
parent 1736e6026e
commit 83a6e5f9a9
8 changed files with 299 additions and 55 deletions

View file

@ -0,0 +1,17 @@
//go:build !386
// +build !386
package redirect
import (
"syscall"
"unsafe"
)
func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
_, _, e := syscall.Syscall6(syscall.SYS_GETSOCKOPT, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
if e != 0 {
err = e
}
return
}

View file

@ -0,0 +1,23 @@
package redirect
import (
"syscall"
"unsafe"
)
const (
sysGetsockopt = 15
)
// On 386 we cannot call socketcall with syscall.Syscall6, as it always fails with EFAULT.
// Use our own syscall.socketcall hack instead.
func syscall_socketcall(call int, a0, a1, a2, a3, a4, a5 uintptr) (n int, err syscall.Errno)
func getsockopt(s, level, name uintptr, val unsafe.Pointer, vallen *uint32) (err error) {
_, e := syscall_socketcall(sysGetsockopt, s, level, name, uintptr(val), uintptr(unsafe.Pointer(vallen)), 0)
if e != 0 {
err = e
}
return
}

View file

@ -0,0 +1,7 @@
//go:build gc
// +build gc
#include "textflag.h"
TEXT ·syscall_socketcall(SB),NOSPLIT,$0-36
JMP syscall·socketcall(SB)

View file

@ -0,0 +1,126 @@
package redirect
import (
"encoding/binary"
"errors"
"io"
"net"
"syscall"
"unsafe"
"github.com/apernet/hysteria/core/client"
)
const (
soOriginalDst = 80
soOriginalDstV6 = 80
)
type TCPRedirect struct {
HyClient client.Client
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *TCPRedirect) ListenAndServe(laddr *net.TCPAddr) error {
listener, err := net.ListenTCP("tcp", laddr)
if err != nil {
return err
}
defer listener.Close()
for {
c, err := listener.AcceptTCP()
if err != nil {
return err
}
go r.handle(c)
}
}
func (r *TCPRedirect) handle(conn *net.TCPConn) {
defer conn.Close()
dstAddr, err := getDstAddr(conn)
if err != nil {
// Fail silently if we can't get the original destination.
// Maybe we should print something to the log?
return
}
if r.EventLogger != nil {
r.EventLogger.Connect(conn.RemoteAddr(), dstAddr)
}
var closeErr error
defer func() {
if r.EventLogger != nil {
r.EventLogger.Error(conn.RemoteAddr(), dstAddr, closeErr)
}
}()
rc, err := r.HyClient.TCP(dstAddr.String())
if err != nil {
closeErr = err
return
}
defer rc.Close()
// Start forwarding
copyErrChan := make(chan error, 2)
go func() {
_, copyErr := io.Copy(rc, conn)
copyErrChan <- copyErr
}()
go func() {
_, copyErr := io.Copy(conn, rc)
copyErrChan <- copyErr
}()
closeErr = <-copyErrChan
}
type sockAddr struct {
family uint16
port [2]byte // always big endian regardless of platform
data [24]byte // sockaddr_in or sockaddr_in6
}
func getOriginalDst(fd uintptr) (*sockAddr, error) {
var addr sockAddr
addrSize := uint32(unsafe.Sizeof(addr))
// Try IPv6 first
err := getsockopt(fd, syscall.SOL_IPV6, soOriginalDstV6, unsafe.Pointer(&addr), &addrSize)
if err == nil {
return &addr, nil
}
// Then IPv4
err = getsockopt(fd, syscall.SOL_IP, soOriginalDst, unsafe.Pointer(&addr), &addrSize)
return &addr, err
}
// getDstAddr returns the original destination of a redirected TCP connection.
func getDstAddr(conn *net.TCPConn) (*net.TCPAddr, error) {
rc, err := conn.SyscallConn()
if err != nil {
return nil, err
}
var addr *sockAddr
var err2 error
err = rc.Control(func(fd uintptr) {
addr, err2 = getOriginalDst(fd)
})
if err != nil {
return nil, err
}
if err2 != nil {
return nil, err2
}
switch addr.family {
case syscall.AF_INET:
return &net.TCPAddr{IP: addr.data[:4], Port: int(binary.BigEndian.Uint16(addr.port[:]))}, nil
case syscall.AF_INET6:
return &net.TCPAddr{IP: addr.data[4:20], Port: int(binary.BigEndian.Uint16(addr.port[:]))}, nil
default:
return nil, errors.New("address family not IPv4 or IPv6")
}
}

View file

@ -0,0 +1,24 @@
//go:build !linux
package redirect
import (
"errors"
"net"
"github.com/apernet/hysteria/core/client"
)
type TCPRedirect struct {
HyClient client.Client
EventLogger TCPEventLogger
}
type TCPEventLogger interface {
Connect(addr, reqAddr net.Addr)
Error(addr, reqAddr net.Addr, err error)
}
func (r *TCPRedirect) ListenAndServe(laddr *net.TCPAddr) error {
return errors.New("not supported on this platform")
}