add bind for relay

This commit is contained in:
ginuerzh
2021-11-25 17:29:54 +08:00
parent 98ef6c7492
commit 6daf0a4d0f
29 changed files with 600 additions and 352 deletions

View File

@ -4,16 +4,19 @@ import (
"bufio"
"context"
"net"
"time"
"github.com/go-gost/gosocks4"
"github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/handler"
http_handler "github.com/go-gost/gost/pkg/handler/http"
relay_handler "github.com/go-gost/gost/pkg/handler/relay"
socks4_handler "github.com/go-gost/gost/pkg/handler/socks/v4"
socks5_handler "github.com/go-gost/gost/pkg/handler/socks/v5"
"github.com/go-gost/gost/pkg/logger"
md "github.com/go-gost/gost/pkg/metadata"
"github.com/go-gost/gost/pkg/registry"
"github.com/go-gost/relay"
)
func init() {
@ -24,6 +27,7 @@ type autoHandler struct {
httpHandler handler.Handler
socks4Handler handler.Handler
socks5Handler handler.Handler
relayHandler handler.Handler
log logger.Logger
}
@ -53,6 +57,10 @@ func NewHandler(opts ...handler.Option) handler.Handler {
v = append(opts,
handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "socks5"})))
h.socks5Handler = socks5_handler.NewHandler(v...)
v = append(opts,
handler.LoggerOption(log.WithFields(map[string]interface{}{"type": "relay"})))
h.relayHandler = relay_handler.NewHandler(v...)
return h
}
@ -66,6 +74,9 @@ func (h *autoHandler) Init(md md.Metadata) error {
if err := h.socks5Handler.Init(md); err != nil {
return err
}
if err := h.relayHandler.Init(md); err != nil {
return err
}
return nil
}
@ -75,6 +86,14 @@ func (h *autoHandler) Handle(ctx context.Context, conn net.Conn) {
"local": conn.LocalAddr().String(),
})
start := time.Now()
h.log.Infof("%s <> %s", conn.RemoteAddr(), conn.LocalAddr())
defer func() {
h.log.WithFields(map[string]interface{}{
"duration": time.Since(start),
}).Infof("%s >< %s", conn.RemoteAddr(), conn.LocalAddr())
}()
br := bufio.NewReader(conn)
b, err := br.Peek(1)
if err != nil {
@ -89,6 +108,8 @@ func (h *autoHandler) Handle(ctx context.Context, conn net.Conn) {
h.socks4Handler.Handle(ctx, cc)
case gosocks5.Ver5: // socks5
h.socks5Handler.Handle(ctx, cc)
case relay.Version1: // relay
h.relayHandler.Handle(ctx, cc)
default: // http
h.httpHandler.Handle(ctx, cc)
}

View File

@ -17,6 +17,7 @@ import (
func init() {
registry.RegisterHandler("tcp", NewHandler)
registry.RegisterHandler("udp", NewHandler)
registry.RegisterHandler("forward", NewHandler)
}
type forwardHandler struct {
@ -40,7 +41,15 @@ func NewHandler(opts ...handler.Option) handler.Handler {
}
func (h *forwardHandler) Init(md md.Metadata) (err error) {
return h.parseMetadata(md)
if err = h.parseMetadata(md); err != nil {
return
}
if h.group == nil {
// dummy node used by relay connector.
h.group = chain.NewNodeGroup(chain.NewNode("dummy", ":0"))
}
return nil
}
// WithChain implements chain.Chainable interface

81
pkg/handler/relay/conn.go Normal file
View File

@ -0,0 +1,81 @@
package relay
import (
"bytes"
"encoding/binary"
"errors"
"io"
"math"
"net"
)
type tcpConn struct {
net.Conn
wbuf bytes.Buffer
}
func (c *tcpConn) Read(b []byte) (n int, err error) {
if err != nil {
return
}
return c.Conn.Read(b)
}
func (c *tcpConn) Write(b []byte) (n int, err error) {
n = len(b) // force byte length consistent
if c.wbuf.Len() > 0 {
c.wbuf.Write(b) // append the data to the cached header
_, err = c.wbuf.WriteTo(c.Conn)
return
}
_, err = c.Conn.Write(b)
return
}
type udpConn struct {
net.Conn
wbuf bytes.Buffer
}
func (c *udpConn) Read(b []byte) (n int, err error) {
var bb [2]byte
_, err = io.ReadFull(c.Conn, bb[:])
if err != nil {
return
}
dlen := int(binary.BigEndian.Uint16(bb[:]))
if len(b) >= dlen {
return io.ReadFull(c.Conn, b[:dlen])
}
buf := make([]byte, dlen)
_, err = io.ReadFull(c.Conn, buf)
n = copy(b, buf)
return
}
func (c *udpConn) Write(b []byte) (n int, err error) {
if len(b) > math.MaxUint16 {
err = errors.New("write: data maximum exceeded")
return
}
n = len(b)
if c.wbuf.Len() > 0 {
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
c.wbuf.Write(bb[:])
c.wbuf.Write(b) // append the data to the cached header
_, err = c.wbuf.WriteTo(c.Conn)
return
}
var bb [2]byte
binary.BigEndian.PutUint16(bb[:], uint16(len(b)))
_, err = c.Conn.Write(bb[:])
if err != nil {
return
}
return c.Conn.Write(b)
}

View File

@ -7,7 +7,6 @@ import (
"time"
"github.com/go-gost/gost/pkg/chain"
util_relay "github.com/go-gost/gost/pkg/common/util/relay"
"github.com/go-gost/gost/pkg/handler"
"github.com/go-gost/relay"
)
@ -51,12 +50,36 @@ func (h *relayHandler) handleConnect(ctx context.Context, conn net.Conn, network
}
defer cc.Close()
if _, err := resp.WriteTo(conn); err != nil {
h.logger.Error(err)
if h.md.noDelay {
if _, err := resp.WriteTo(conn); err != nil {
h.logger.Error(err)
return
}
}
if network == "udp" {
conn = util_relay.UDPTunConn(conn)
switch network {
case "udp", "udp4", "udp6":
rc := &udpConn{
Conn: conn,
}
if !h.md.noDelay {
// cache the header
if _, err := resp.WriteTo(&rc.wbuf); err != nil {
return
}
}
conn = rc
default:
rc := &tcpConn{
Conn: conn,
}
if !h.md.noDelay {
// cache the header
if _, err := resp.WriteTo(&rc.wbuf); err != nil {
return
}
}
conn = rc
}
t := time.Now()

View File

@ -8,11 +8,18 @@ import (
"github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/handler"
"github.com/go-gost/relay"
)
func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network string) {
resp := relay.Response{
Version: relay.Version1,
Status: relay.StatusOK,
}
target := h.group.Next()
if target == nil {
resp.Status = relay.StatusServiceUnavailable
resp.WriteTo(conn)
h.logger.Error("no target available")
return
}
@ -30,15 +37,51 @@ func (h *relayHandler) handleForward(ctx context.Context, conn net.Conn, network
cc, err := r.Dial(ctx, network, target.Addr())
if err != nil {
h.logger.Error(err)
// TODO: the router itself may be failed due to the failed node in the router,
// the dead marker may be a wrong operation.
target.Marker().Mark()
resp.Status = relay.StatusHostUnreachable
resp.WriteTo(conn)
h.logger.Error(err)
return
}
defer cc.Close()
target.Marker().Reset()
if h.md.noDelay {
if _, err := resp.WriteTo(conn); err != nil {
h.logger.Error(err)
return
}
}
switch network {
case "udp", "udp4", "udp6":
rc := &udpConn{
Conn: conn,
}
if !h.md.noDelay {
// cache the header
if _, err := resp.WriteTo(&rc.wbuf); err != nil {
return
}
}
conn = rc
default:
rc := &tcpConn{
Conn: conn,
}
if !h.md.noDelay {
// cache the header
if _, err := resp.WriteTo(&rc.wbuf); err != nil {
return
}
}
conn = rc
}
t := time.Now()
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), target.Addr())
handler.Transport(conn, cc)

View File

@ -123,7 +123,7 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn) {
if address != "" {
resp.Status = relay.StatusForbidden
resp.WriteTo(conn)
h.logger.Error("forbidden")
h.logger.Error("forward mode, connect is forbidden")
return
}
// forward mode
@ -132,7 +132,7 @@ func (h *relayHandler) Handle(ctx context.Context, conn net.Conn) {
}
switch req.Flags & relay.CmdMask {
case relay.CONNECT:
case 0, relay.CONNECT:
h.handleConnect(ctx, conn, network, address)
case relay.BIND:
h.handleBind(ctx, conn, network, address)

View File

@ -14,6 +14,7 @@ type metadata struct {
retryCount int
enableBind bool
udpBufferSize int
noDelay bool
}
func (h *relayHandler) parseMetadata(md md.Metadata) (err error) {
@ -23,6 +24,7 @@ func (h *relayHandler) parseMetadata(md md.Metadata) (err error) {
retryCount = "retry"
enableBind = "bind"
udpBufferSize = "udpBufferSize"
noDelay = "nodelay"
)
if v, _ := md.Get(users).([]interface{}); len(v) > 0 {
@ -42,6 +44,7 @@ func (h *relayHandler) parseMetadata(md md.Metadata) (err error) {
h.md.readTimeout = md.GetDuration(readTimeout)
h.md.retryCount = md.GetInt(retryCount)
h.md.enableBind = md.GetBool(enableBind)
h.md.noDelay = md.GetBool(noDelay)
h.md.udpBufferSize = md.GetInt(udpBufferSize)
if h.md.udpBufferSize > 0 {
if h.md.udpBufferSize < 512 {

View File

@ -2,7 +2,6 @@ package v5
import (
"context"
"errors"
"fmt"
"io"
"io/ioutil"
@ -10,7 +9,6 @@ import (
"time"
"github.com/go-gost/gosocks5"
"github.com/go-gost/gost/pkg/chain"
"github.com/go-gost/gost/pkg/common/bufpool"
"github.com/go-gost/gost/pkg/common/util/socks"
)
@ -54,153 +52,24 @@ func (h *socks5Handler) handleUDP(ctx context.Context, conn net.Conn) {
})
h.logger.Debugf("bind on %s OK", relay.LocalAddr())
if h.chain.IsEmpty() {
// serve as standard socks5 udp relay.
peer, err := net.ListenUDP("udp", nil)
if err != nil {
h.logger.Error(err)
return
}
defer peer.Close()
go h.relayUDP(
socks.UDPConn(relay, h.md.udpBufferSize),
peer,
)
} else {
tun, err := h.getUDPTun(ctx)
if err != nil {
h.logger.Error(err)
return
}
defer tun.Close()
go h.tunnelClientUDP(
socks.UDPConn(relay, h.md.udpBufferSize),
socks.UDPTunClientPacketConn(tun),
)
peer, err := net.ListenUDP("udp", nil)
if err != nil {
h.logger.Error(err)
return
}
defer peer.Close()
go h.relayUDP(
socks.UDPConn(relay, h.md.udpBufferSize),
peer,
)
t := time.Now()
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), &saddr)
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), relay.LocalAddr())
io.Copy(ioutil.Discard, conn)
h.logger.
WithFields(map[string]interface{}{"duration": time.Since(t)}).
Infof("%s >-< %s", conn.RemoteAddr(), &saddr)
}
func (h *socks5Handler) getUDPTun(ctx context.Context) (conn net.Conn, err error) {
r := (&chain.Router{}).
WithChain(h.chain).
WithRetry(h.md.retryCount).
WithLogger(h.logger)
conn, err = r.Connect(ctx)
if err != nil {
return nil, err
}
defer func() {
if err != nil {
conn.Close()
conn = nil
}
}()
if h.md.timeout > 0 {
conn.SetDeadline(time.Now().Add(h.md.timeout))
defer conn.SetDeadline(time.Time{})
}
req := gosocks5.NewRequest(socks.CmdUDPTun, nil)
if err = req.Write(conn); err != nil {
return
}
h.logger.Debug(req)
reply, err := gosocks5.ReadReply(conn)
if err != nil {
return
}
h.logger.Debug(reply)
if reply.Rep != gosocks5.Succeeded {
err = errors.New("UDP associate failed")
return
}
return
}
func (h *socks5Handler) tunnelClientUDP(c, tun net.PacketConn) (err error) {
bufSize := h.md.udpBufferSize
errc := make(chan error, 2)
go func() {
for {
err := func() error {
b := bufpool.Get(bufSize)
defer bufpool.Put(b)
n, raddr, err := c.ReadFrom(b)
if err != nil {
return err
}
if h.bypass != nil && h.bypass.Contains(raddr.String()) {
h.logger.Warn("bypass: ", raddr)
return nil
}
if _, err := tun.WriteTo(b[:n], raddr); err != nil {
return err
}
h.logger.Debugf("%s >>> %s data: %d",
tun.LocalAddr(), raddr, n)
return nil
}()
if err != nil {
errc <- err
return
}
}
}()
go func() {
for {
err := func() error {
b := bufpool.Get(bufSize)
defer bufpool.Put(b)
n, raddr, err := tun.ReadFrom(b)
if err != nil {
return err
}
if h.bypass != nil && h.bypass.Contains(raddr.String()) {
h.logger.Warn("bypass: ", raddr)
return nil
}
if _, err := c.WriteTo(b[:n], raddr); err != nil {
return err
}
h.logger.Debugf("%s <<< %s data: %d",
tun.LocalAddr(), raddr, n)
return nil
}()
if err != nil {
errc <- err
return
}
}
}()
return <-errc
Infof("%s >-< %s", conn.RemoteAddr(), relay.LocalAddr())
}
func (h *socks5Handler) relayUDP(c, peer net.PacketConn) (err error) {

View File

@ -84,18 +84,18 @@ func (h *ssuHandler) Handle(ctx context.Context, conn net.Conn) {
WithChain(h.chain).
WithRetry(h.md.retryCount).
WithLogger(h.logger)
c, err := r.Dial(ctx, "udp", "")
c, err := r.Dial(ctx, "udp", "") // UDP association
if err != nil {
h.logger.Error(err)
return
}
defer c.Close()
cc, ok := c.(net.PacketConn)
if !ok {
h.logger.Errorf("%s: not a packet connection")
h.logger.Errorf("wrong connection type")
return
}
defer cc.Close()
t := time.Now()
h.logger.Infof("%s <-> %s", conn.RemoteAddr(), cc.LocalAddr())