From 730e9c765e8e9e6308b2723b69f26c94fa98fac8 Mon Sep 17 00:00:00 2001 From: nadoo <287492+nadoo@users.noreply.github.com> Date: Tue, 5 Apr 2022 15:22:12 +0800 Subject: [PATCH] proxy: added vsock support (#295) --- README.md | 8 +- feature_linux.go | 1 + go.mod | 4 +- go.sum | 8 +- proxy/vsock/client.go | 27 ++++++ proxy/vsock/server.go | 90 ++++++++++++++++++ proxy/vsock/socket.go | 214 ++++++++++++++++++++++++++++++++++++++++++ proxy/vsock/vsock.go | 63 +++++++++++++ 8 files changed, 407 insertions(+), 8 deletions(-) create mode 100644 proxy/vsock/client.go create mode 100644 proxy/vsock/server.go create mode 100644 proxy/vsock/socket.go create mode 100644 proxy/vsock/vsock.go diff --git a/README.md b/README.md index de23eff..9541631 100644 --- a/README.md +++ b/README.md @@ -193,8 +193,8 @@ URL: -forward socks5://serverA:1080,socks5://serverB:1080 (proxy chain) SCHEME: - listen : http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless ws wss - forward: direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess ws wss + listen : http kcp mixed pxyproto redir redir6 smux sni socks5 ss tcp tls tproxy trojan trojanc udp unix vless vsock ws wss + forward: direct http kcp reject simple-obfs smux socks4 socks4a socks5 ss ssh ssr tcp tls trojan trojanc udp unix vless vmess vsock ws wss Note: use 'glider -scheme all' or 'glider -scheme SCHEME' to see help info for the scheme. @@ -348,6 +348,10 @@ TLS and Websocket with a specified proxy protocol: tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],http://[user:pass@] tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],socks5://[user:pass@] tls://host:port[?skipVerify=true],ws://[@/path[?host=HOST]],vmess://[security:]uuid@?alterID=num + +-- +VM socket scheme: + vsock://[contextID]:port ``` diff --git a/feature_linux.go b/feature_linux.go index cc254ae..bbe2eff 100644 --- a/feature_linux.go +++ b/feature_linux.go @@ -8,4 +8,5 @@ import ( _ "github.com/nadoo/glider/proxy/redir" _ "github.com/nadoo/glider/proxy/tproxy" _ "github.com/nadoo/glider/proxy/unix" + _ "github.com/nadoo/glider/proxy/vsock" ) diff --git a/go.mod b/go.mod index 9f4167b..20ca7c1 100644 --- a/go.mod +++ b/go.mod @@ -7,12 +7,12 @@ require ( github.com/dgryski/go-camellia v0.0.0-20191119043421-69a8a13fb23d github.com/dgryski/go-idea v0.0.0-20170306091226-d2fb45a411fb github.com/dgryski/go-rc2 v0.0.0-20150621095337-8a9021637152 - github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd + github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 github.com/nadoo/conflag v0.3.1 github.com/nadoo/ipset v0.5.0 github.com/xtaci/kcp-go/v5 v5.6.1 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 - golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb + golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 ) require ( diff --git a/go.sum b/go.sum index b459451..71381ab 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,8 @@ github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/hugelgupf/socketpair v0.0.0-20190730060125-05d35a94e714/go.mod h1:2Goc3h8EklBH5mspfHFxBnEoURQCGzQQH1ga9Myjvis= -github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd h1:efcJu2Vzz6DoSq245deWNzTz6l/gsqdphm3FjmI88/g= -github.com/insomniacslk/dhcp v0.0.0-20220119180841-3c283ff8b7dd/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= +github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41 h1:Yg3n3AI7GoHnWt7dyjsLPU+TEuZfPAg0OdiA3MJUV6I= +github.com/insomniacslk/dhcp v0.0.0-20220405050111-12fbdcb11b41/go.mod h1:h+MxyHxRg9NH3terB1nfRIUaQEcI0XOVkdR9LNBlp8E= github.com/josharian/native v1.0.0 h1:Ts/E8zCSEsG17dUqv7joXJFybuMLjQfWE04tsBODTxk= github.com/josharian/native v1.0.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w= github.com/jsimonetti/rtnetlink v0.0.0-20190606172950-9527aa82566a/go.mod h1:Oz+70psSo5OFh8DBl0Zv2ACw7Esh6pPUphlvZG9x7uw= @@ -164,8 +164,8 @@ golang.org/x/sys v0.0.0-20201101102859-da207088b7d1/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb h1:PVGECzEo9Y3uOidtkHGdd347NjLtITfJFO9BxFpmRoo= -golang.org/x/sys v0.0.0-20220403205710-6acee93ad0eb/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64 h1:D1v9ucDTYBtbz5vNuBbAhIMAGhQhJ6Ym5ah3maMVNX4= +golang.org/x/sys v0.0.0-20220405052023-b1e9470b6e64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= diff --git a/proxy/vsock/client.go b/proxy/vsock/client.go new file mode 100644 index 0000000..191a514 --- /dev/null +++ b/proxy/vsock/client.go @@ -0,0 +1,27 @@ +package vsock + +import ( + "net" + + "github.com/nadoo/glider/proxy" +) + +func init() { + proxy.RegisterDialer("vsock", NewVSockDialer) +} + +// NewVSockDialer returns a vm socket dialer. +func NewVSockDialer(s string, d proxy.Dialer) (proxy.Dialer, error) { + return NewVSock(s, d, nil) +} + +// Dial connects to the address addr on the network net via the proxy. +// NOTE: must be the first dialer in a chain +func (s *vsock) Dial(network, addr string) (net.Conn, error) { + return Dial(s.cid, s.port) +} + +// DialUDP connects to the given address via the proxy. +func (s *vsock) DialUDP(network, addr string) (net.PacketConn, error) { + return nil, proxy.ErrNotSupported +} diff --git a/proxy/vsock/server.go b/proxy/vsock/server.go new file mode 100644 index 0000000..bd608e3 --- /dev/null +++ b/proxy/vsock/server.go @@ -0,0 +1,90 @@ +package vsock + +import ( + "net" + "strings" + + "github.com/nadoo/glider/pkg/log" + "github.com/nadoo/glider/proxy" +) + +func init() { + proxy.RegisterServer("vsock", NewVSockServer) +} + +// NewVSockServer returns a vm socket server. +func NewVSockServer(s string, p proxy.Proxy) (proxy.Server, error) { + schemes := strings.SplitN(s, ",", 2) + vsock, err := NewVSock(schemes[0], nil, p) + if err != nil { + return nil, err + } + + if len(schemes) > 1 { + vsock.server, err = proxy.ServerFromURL(schemes[1], p) + if err != nil { + return nil, err + } + } + + if vsock.cid == 0 { + cid, err := ContextID() + if err != nil { + return nil, err + } + vsock.cid = cid + } + + return vsock, nil +} + +// ListenAndServe serves requests. +func (s *vsock) ListenAndServe() { + l, err := Listen(s.cid, s.port) + if err != nil { + log.Fatalf("[vsock] failed to listen: %v", err) + return + } + defer l.Close() + + log.F("[vsock] Listening on %s", l.Addr()) + + for { + c, err := l.Accept() + if err != nil { + log.F("[vsock] failed to accept: %v", err) + continue + } + + go s.Serve(c) + } +} + +// Serve serves requests. +func (s *vsock) Serve(c net.Conn) { + if s.server != nil { + s.server.Serve(c) + return + } + + defer c.Close() + + rc, dialer, err := s.proxy.Dial("tcp", "") + if err != nil { + log.F("[vsock] %s <-> %s via %s, error in dial: %v", c.RemoteAddr(), s.addr, dialer.Addr(), err) + s.proxy.Record(dialer, false) + return + } + defer rc.Close() + + log.F("[vsock] %s <-> %s", c.RemoteAddr(), dialer.Addr()) + + if err = proxy.Relay(c, rc); err != nil { + log.F("[vsock] %s <-> %s, relay error: %v", c.RemoteAddr(), dialer.Addr(), err) + // record remote conn failure only + if !strings.Contains(err.Error(), s.addr) { + s.proxy.Record(dialer, false) + } + } + +} diff --git a/proxy/vsock/socket.go b/proxy/vsock/socket.go new file mode 100644 index 0000000..addfc87 --- /dev/null +++ b/proxy/vsock/socket.go @@ -0,0 +1,214 @@ +package vsock + +// Source code from: +// https://github.com/linuxkit/virtsock/tree/master/pkg/vsock + +import ( + "fmt" + "net" + "os" + "syscall" + "time" + + "golang.org/x/sys/unix" +) + +// Addr represents the address of a vsock end point. +type Addr struct { + CID uint32 + Port uint32 +} + +// Network returns the network type for a Addr +func (a Addr) Network() string { + return "vsock" +} + +// String returns a string representation of a Addr +func (a Addr) String() string { + return fmt.Sprintf("%d:%d", a.CID, a.Port) +} + +// Conn is a vsock connection which supports half-close. +type Conn interface { + net.Conn + CloseRead() error + CloseWrite() error + File() (*os.File, error) +} + +// SocketMode is a NOOP on Linux. +func SocketMode(m string) {} + +// Convert a generic unix.Sockaddr to a Addr. +func sockaddrToVsock(sa unix.Sockaddr) *Addr { + switch sa := sa.(type) { + case *unix.SockaddrVM: + return &Addr{CID: sa.CID, Port: sa.Port} + } + return nil +} + +// Closes fd, retrying EINTR +func closeFD(fd int) error { + for { + if err := unix.Close(fd); err != nil { + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR { + continue + } + return fmt.Errorf("failed to close() fd %d: %w", fd, err) + } + break + } + return nil +} + +// Dial connects to the CID.Port via virtio sockets. +func Dial(cid, port uint32) (Conn, error) { + fd, err := syscall.Socket(unix.AF_VSOCK, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) + if err != nil { + return nil, fmt.Errorf("Failed to create AF_VSOCK socket: %w", err) + } + sa := &unix.SockaddrVM{CID: cid, Port: port} + // Retry connect in a loop if EINTR is encountered. + for { + if err := unix.Connect(fd, sa); err != nil { + if errno, ok := err.(syscall.Errno); ok && errno == syscall.EINTR { + continue + } + // Trying not to leak fd here + _ = closeFD(fd) + return nil, fmt.Errorf("failed connect() to %d:%d: %w", cid, port, err) + } + break + } + return newVsockConn(uintptr(fd), nil, &Addr{cid, port}), nil +} + +// Listen returns a net.Listener which can accept connections on the given cid and port. +func Listen(cid, port uint32) (net.Listener, error) { + fd, err := syscall.Socket(unix.AF_VSOCK, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0) + if err != nil { + return nil, err + } + + sa := &unix.SockaddrVM{CID: cid, Port: port} + if err = unix.Bind(fd, sa); err != nil { + return nil, fmt.Errorf("bind() to %d:%d failed: %w", cid, port, err) + } + + err = syscall.Listen(fd, syscall.SOMAXCONN) + if err != nil { + return nil, fmt.Errorf("listen() on %d:%d failed: %w", cid, port, err) + } + return &vsockListener{fd, Addr{cid, port}}, nil +} + +// ContextID retrieves the local context ID for this system. +func ContextID() (uint32, error) { + f, err := os.Open("/dev/vsock") + if err != nil { + return 0, err + } + defer f.Close() + + return unix.IoctlGetUint32(int(f.Fd()), unix.IOCTL_VM_SOCKETS_GET_LOCAL_CID) +} + +type vsockListener struct { + fd int + local Addr +} + +// Accept accepts an incoming call and returns the new connection. +func (v *vsockListener) Accept() (net.Conn, error) { + fd, sa, err := unix.Accept(v.fd) + if err != nil { + return nil, err + } + return newVsockConn(uintptr(fd), &v.local, sockaddrToVsock(sa)), nil +} + +// Close closes the listening connection +func (v *vsockListener) Close() error { + // Note this won't cause the Accept to unblock. + return unix.Close(v.fd) +} + +// Addr returns the address the Listener is listening on +func (v *vsockListener) Addr() net.Addr { + return v.local +} + +// a wrapper around FileConn which supports CloseRead and CloseWrite +type vsockConn struct { + vsock *os.File + fd uintptr + local *Addr + remote *Addr +} + +func newVsockConn(fd uintptr, local, remote *Addr) *vsockConn { + vsock := os.NewFile(fd, fmt.Sprintf("vsock:%d", fd)) + return &vsockConn{vsock: vsock, fd: fd, local: local, remote: remote} +} + +// LocalAddr returns the local address of a connection +func (v *vsockConn) LocalAddr() net.Addr { + return v.local +} + +// RemoteAddr returns the remote address of a connection +func (v *vsockConn) RemoteAddr() net.Addr { + return v.remote +} + +// Close closes the connection +func (v *vsockConn) Close() error { + return v.vsock.Close() +} + +// CloseRead shuts down the reading side of a vsock connection +func (v *vsockConn) CloseRead() error { + return syscall.Shutdown(int(v.fd), syscall.SHUT_RD) +} + +// CloseWrite shuts down the writing side of a vsock connection +func (v *vsockConn) CloseWrite() error { + return syscall.Shutdown(int(v.fd), syscall.SHUT_WR) +} + +// Read reads data from the connection +func (v *vsockConn) Read(buf []byte) (int, error) { + return v.vsock.Read(buf) +} + +// Write writes data over the connection +func (v *vsockConn) Write(buf []byte) (int, error) { + return v.vsock.Write(buf) +} + +// SetDeadline sets the read and write deadlines associated with the connection +func (v *vsockConn) SetDeadline(t time.Time) error { + return nil // FIXME +} + +// SetReadDeadline sets the deadline for future Read calls. +func (v *vsockConn) SetReadDeadline(t time.Time) error { + return nil // FIXME +} + +// SetWriteDeadline sets the deadline for future Write calls +func (v *vsockConn) SetWriteDeadline(t time.Time) error { + return nil // FIXME +} + +// File duplicates the underlying socket descriptor and returns it. +func (v *vsockConn) File() (*os.File, error) { + // This is equivalent to dup(2) but creates the new fd with CLOEXEC already set. + r0, _, e1 := syscall.Syscall(syscall.SYS_FCNTL, uintptr(v.vsock.Fd()), syscall.F_DUPFD_CLOEXEC, 0) + if e1 != 0 { + return nil, os.NewSyscallError("fcntl", e1) + } + return os.NewFile(r0, v.vsock.Name()), nil +} diff --git a/proxy/vsock/vsock.go b/proxy/vsock/vsock.go new file mode 100644 index 0000000..ea31525 --- /dev/null +++ b/proxy/vsock/vsock.go @@ -0,0 +1,63 @@ +package vsock + +import ( + "net" + "net/url" + "strconv" + + "github.com/nadoo/glider/pkg/log" + "github.com/nadoo/glider/proxy" +) + +type vsock struct { + dialer proxy.Dialer + proxy proxy.Proxy + server proxy.Server + addr string + cid, port uint32 +} + +// NewVSock returns vm socket proxy. +func NewVSock(s string, d proxy.Dialer, p proxy.Proxy) (*vsock, error) { + u, err := url.Parse(s) + if err != nil { + log.F("[vsock] parse url err: %s", err) + return nil, err + } + + v := &vsock{dialer: d, proxy: p, addr: u.Host} + if hostStr, portStr, _ := net.SplitHostPort(v.addr); portStr != "" { + if hostStr != "" { + host, err := strconv.ParseUint(hostStr, 10, 32) + if err != nil { + log.F("[vsock] parse cid err: %s", err) + return nil, err + } + v.cid = uint32(host) + } + + port, err := strconv.ParseUint(portStr, 10, 32) + if err != nil { + log.F("[vsock] parse port err: %s", err) + return nil, err + } + v.port = uint32(port) + } + + return v, nil +} + +// Addr returns forwarder's address. +func (s *vsock) Addr() string { + if s.addr == "" { + return s.dialer.Addr() + } + return s.addr +} + +func init() { + proxy.AddUsage("vsock", ` +VM socket scheme: + vsock://[contextID]:port +`) +}