mirror of
https://github.com/apernet/hysteria.git
synced 2025-04-03 20:47:38 +03:00
feat: TCP redirect mode
This commit is contained in:
parent
1736e6026e
commit
83a6e5f9a9
8 changed files with 299 additions and 55 deletions
17
app/internal/redirect/getsockopt_linux.go
Normal file
17
app/internal/redirect/getsockopt_linux.go
Normal 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
|
||||
}
|
23
app/internal/redirect/getsockopt_linux_386.go
Normal file
23
app/internal/redirect/getsockopt_linux_386.go
Normal 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
|
||||
}
|
7
app/internal/redirect/syscall_socketcall_linux_386.s
Normal file
7
app/internal/redirect/syscall_socketcall_linux_386.s
Normal file
|
@ -0,0 +1,7 @@
|
|||
//go:build gc
|
||||
// +build gc
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
TEXT ·syscall_socketcall(SB),NOSPLIT,$0-36
|
||||
JMP syscall·socketcall(SB)
|
126
app/internal/redirect/tcp_linux.go
Normal file
126
app/internal/redirect/tcp_linux.go
Normal 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")
|
||||
}
|
||||
}
|
24
app/internal/redirect/tcp_others.go
Normal file
24
app/internal/redirect/tcp_others.go
Normal 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")
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue