Files
trojan-go/tunnel/tproxy/tcp.go
T
LoyalsoldierandGitHub 2dc60f52e7 Chore: format code & update dependencies (#385)
* Chore: check Go modules before test & update dependencies
* Chore: run go fmt to format code
2021-09-14 15:41:24 +08:00

137 lines
3.4 KiB
Go

//go:build linux
// +build linux
package tproxy
import (
"fmt"
"net"
"os"
"syscall"
"unsafe"
)
// Listener describes a TCP Listener
// with the Linux IP_TRANSPARENT option defined
// on the listening socket
type Listener struct {
base net.Listener
}
// Accept waits for and returns
// the next connection to the listener.
//
// This command wraps the AcceptTProxy
// method of the Listener
func (listener *Listener) Accept() (net.Conn, error) {
tcpConn, err := listener.base.(*net.TCPListener).AcceptTCP()
if err != nil {
return nil, err
}
return tcpConn, nil
}
// Addr returns the network address
// the listener is accepting connections
// from
func (listener *Listener) Addr() net.Addr {
return listener.base.Addr()
}
// Close will close the listener from accepting
// any more connections. Any blocked connections
// will unblock and close
func (listener *Listener) Close() error {
return listener.base.Close()
}
// ListenTCP will construct a new TCP listener
// socket with the Linux IP_TRANSPARENT option
// set on the underlying socket
func ListenTCP(network string, laddr *net.TCPAddr) (net.Listener, error) {
listener, err := net.ListenTCP(network, laddr)
if err != nil {
return nil, err
}
fileDescriptorSource, err := listener.File()
if err != nil {
return nil, &net.OpError{Op: "listen", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf("get file descriptor: %s", err)}
}
defer fileDescriptorSource.Close()
if err = syscall.SetsockoptInt(int(fileDescriptorSource.Fd()), syscall.SOL_IP, syscall.IP_TRANSPARENT, 1); err != nil {
return nil, &net.OpError{Op: "listen", Net: network, Source: nil, Addr: laddr, Err: fmt.Errorf("set socket option: IP_TRANSPARENT: %s", err)}
}
return &Listener{listener}, nil
}
const (
IP6T_SO_ORIGINAL_DST = 80
SO_ORIGINAL_DST = 80
)
// getOriginalTCPDest retrieves the original destination address from
// NATed connection. Currently, only Linux iptables using DNAT/REDIRECT
// is supported. For other operating systems, this will just return
// conn.LocalAddr().
//
// Note that this function only works when nf_conntrack_ipv4 and/or
// nf_conntrack_ipv6 is loaded in the kernel.
func getOriginalTCPDest(conn *net.TCPConn) (*net.TCPAddr, error) {
f, err := conn.File()
if err != nil {
return nil, err
}
defer f.Close()
fd := int(f.Fd())
// revert to non-blocking mode.
// see http://stackoverflow.com/a/28968431/1493661
if err = syscall.SetNonblock(fd, true); err != nil {
return nil, os.NewSyscallError("setnonblock", err)
}
v6 := conn.LocalAddr().(*net.TCPAddr).IP.To4() == nil
if v6 {
var addr syscall.RawSockaddrInet6
var len uint32
len = uint32(unsafe.Sizeof(addr))
err = getsockopt(fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST,
unsafe.Pointer(&addr), &len)
if err != nil {
return nil, os.NewSyscallError("getsockopt", err)
}
ip := make([]byte, 16)
for i, b := range addr.Addr {
ip[i] = b
}
pb := *(*[2]byte)(unsafe.Pointer(&addr.Port))
return &net.TCPAddr{
IP: ip,
Port: int(pb[0])*256 + int(pb[1]),
}, nil
}
// IPv4
var addr syscall.RawSockaddrInet4
var len uint32
len = uint32(unsafe.Sizeof(addr))
err = getsockopt(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST,
unsafe.Pointer(&addr), &len)
if err != nil {
return nil, os.NewSyscallError("getsockopt", err)
}
ip := make([]byte, 4)
for i, b := range addr.Addr {
ip[i] = b
}
pb := *(*[2]byte)(unsafe.Pointer(&addr.Port))
return &net.TCPAddr{
IP: ip,
Port: int(pb[0])*256 + int(pb[1]),
}, nil
}