diff --git a/chain.go b/chain.go index 966a53f..2aefd0d 100644 --- a/chain.go +++ b/chain.go @@ -20,6 +20,7 @@ type Chain struct { isRoute bool Retries int Mark int + Interface string nodeGroups []*NodeGroup route []Node // nodes in the selected route } @@ -36,9 +37,11 @@ func NewChain(nodes ...Node) *Chain { // newRoute creates a chain route. // a chain route is the final route after node selection. -func newRoute(nodes ...Node) *Chain { +func (c *Chain) newRoute(nodes ...Node) *Chain { chain := NewChain(nodes...) chain.isRoute = true + chain.Interface = c.Interface + chain.Mark = c.Mark return chain } @@ -166,6 +169,18 @@ func (c *Chain) dialWithOptions(ctx context.Context, network, address string, op } } + if c.Interface != "" { + controlFunction = func(_, _ string, cc syscall.RawConn) error { + return cc.Control(func(fd uintptr) { + err := setSocketInterface(int(fd), c.Interface) + + if err != nil { + log.Logf("net dialer set interface %s error: %s", c.Interface, err) + } + }) + } + } + if route.IsEmpty() { switch network { case "udp", "udp4", "udp6": @@ -303,13 +318,13 @@ func (c *Chain) selectRoute() (route *Chain, err error) { // selectRouteFor selects route with bypass testing. func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) { if c.IsEmpty() { - return newRoute(), nil + return c.newRoute(), nil } if c.isRoute { return c, nil } - route = newRoute() + route = c.newRoute() var nl []Node for _, group := range c.nodeGroups { @@ -327,7 +342,7 @@ func (c *Chain) selectRouteFor(addr string) (route *Chain, err error) { node.DialOptions = append(node.DialOptions, ChainDialOption(route), ) - route = newRoute() // cutoff the chain for multiplex node. + route = c.newRoute() // cutoff the chain for multiplex node. } route.AddNode(node) diff --git a/cmd/gost/main.go b/cmd/gost/main.go index 7aea15b..ce9184c 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -33,6 +33,7 @@ func init() { flag.Var(&baseCfg.route.ServeNodes, "L", "listen address, can listen on multiple ports (required)") flag.IntVar(&baseCfg.route.Mark, "M", 0, "Specify out connection mark") flag.StringVar(&configureFile, "C", "", "configure file") + flag.StringVar(&baseCfg.route.Interface, "I", "", "Interface to bind") flag.BoolVar(&baseCfg.Debug, "D", false, "enable debug log") flag.BoolVar(&printVersion, "V", false, "print version") if pprofEnabled { diff --git a/cmd/gost/route.go b/cmd/gost/route.go index ee1caa6..39c6929 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -31,12 +31,14 @@ type route struct { ChainNodes stringList Retries int Mark int + Interface string } func (r *route) parseChain() (*gost.Chain, error) { chain := gost.NewChain() chain.Retries = r.Retries chain.Mark = r.Mark + chain.Interface = r.Interface gid := 1 // group ID for _, ns := range r.ChainNodes { diff --git a/sockopts_linux.go b/sockopts_linux.go index f35423e..380f272 100644 --- a/sockopts_linux.go +++ b/sockopts_linux.go @@ -5,3 +5,7 @@ import "syscall" func setSocketMark(fd int, value int) (e error) { return syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, value) } + +func setSocketInterface(fd int, value string) (e error) { + return syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, value) +} \ No newline at end of file diff --git a/sockopts_other.go b/sockopts_other.go index c0dd1b8..d53e992 100644 --- a/sockopts_other.go +++ b/sockopts_other.go @@ -5,3 +5,7 @@ package gost func setSocketMark(fd int, value int) (e error) { return nil } + +func setSocketInterface(fd int, value string) (e error) { + return nil +} \ No newline at end of file