diff --git a/core/nic.go b/core/nic.go index ab537e7..c81dfc7 100644 --- a/core/nic.go +++ b/core/nic.go @@ -2,8 +2,11 @@ package core import ( "fmt" + "net" "gvisor.dev/gvisor/pkg/tcpip" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" + "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" "gvisor.dev/gvisor/pkg/tcpip/stack" "github.com/xjasonlyu/tun2socks/v2/core/option" @@ -56,3 +59,61 @@ func withSpoofing(nicID tcpip.NICID, v bool) option.Option { return nil } } + +// withMulticastGroups adds a NIC to the given multicast groups. +func withMulticastGroups(nicID tcpip.NICID, multicastGroups []net.IP) option.Option { + return func(s *stack.Stack) error { + if len(multicastGroups) == 0 { + return nil + } + // The default NIC of tun2socks is working on Spoofing mode. When the UDP Endpoint + // tries to use a non-local address to connect, the network stack will + // generate a temporary addressState to build the route, which can be primary + // but is ephemeral. Nevertheless, when the UDP Endpoint tries to use a + // multicast address to connect, the network stack will select an available + // primary addressState to build the route. However, when tun2socks is in the + // just-initialized or idle state, there will be no available primary addressState, + // and the connect operation will fail. Therefore, we need to add permanent addresses, + // e.g. 10.0.0.1/8 and fd00:1/8, to the default NIC, which are only used to build + // routes for multicast response and do not affect other connections. + // + // In fact, for multicast, the sender normally does not expect a response. + // So, the ep.net.Connect is unnecessary. If we implement a custom UDP Forwarder + // and ForwarderRequest in the future, we can remove these code. + s.AddProtocolAddress( + nicID, + tcpip.ProtocolAddress{ + Protocol: ipv4.ProtocolNumber, + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: tcpip.AddrFrom4([4]byte{0x0a, 0, 0, 0x01}), + PrefixLen: 8, + }, + }, + stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint}, + ) + s.AddProtocolAddress( + nicID, + tcpip.ProtocolAddress{ + Protocol: ipv6.ProtocolNumber, + AddressWithPrefix: tcpip.AddressWithPrefix{ + Address: tcpip.AddrFrom16([16]byte{0xfd, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x01}), + PrefixLen: 8, + }, + }, + stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint}, + ) + for _, multicastGroup := range multicastGroups { + if ip := multicastGroup.To4(); ip != nil { + if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.AddrFrom4Slice(ip)); err != nil { + return fmt.Errorf("join multicast group: %s", err) + } + } else { + ip := multicastGroup.To16() + if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.AddrFrom16Slice(ip)); err != nil { + return fmt.Errorf("join multicast group: %s", err) + } + } + } + return nil + } +} diff --git a/core/stack.go b/core/stack.go index 8f2b9f6..64e8b79 100644 --- a/core/stack.go +++ b/core/stack.go @@ -1,6 +1,8 @@ package core import ( + "net" + "gvisor.dev/gvisor/pkg/tcpip" "gvisor.dev/gvisor/pkg/tcpip/network/ipv4" "gvisor.dev/gvisor/pkg/tcpip/network/ipv6" @@ -23,6 +25,10 @@ type Config struct { // stack to set transport handlers. TransportHandler adapter.TransportHandler + // MulticastGroups is used by internal stack to add + // nic to given groups. + MulticastGroups []net.IP + // Options are supplement options to apply settings // for the internal stack. Options []option.Option @@ -88,6 +94,9 @@ func CreateStack(cfg *Config) (*stack.Stack, error) { // Add default route table for IPv4 and IPv6. This will handle // all incoming ICMP packets. withRouteTable(nicID), + + // Add default NIC to the given multicast groups. + withMulticastGroups(nicID, cfg.MulticastGroups), ) for _, opt := range opts { diff --git a/engine/engine.go b/engine/engine.go index 35f79c4..b7ae8e9 100644 --- a/engine/engine.go +++ b/engine/engine.go @@ -193,6 +193,11 @@ func netstack(k *Key) (err error) { return } + var multicastGroups []net.IP + if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil { + return err + } + var opts []option.Option if k.TCPModerateReceiveBuffer { opts = append(opts, option.WithTCPModerateReceiveBuffer(true)) @@ -217,6 +222,7 @@ func netstack(k *Key) (err error) { if _defaultStack, err = core.CreateStack(&core.Config{ LinkEndpoint: _defaultDevice, TransportHandler: &mirror.Tunnel{}, + MulticastGroups: multicastGroups, Options: opts, }); err != nil { return diff --git a/engine/key.go b/engine/key.go index 32e39dc..5a86d53 100644 --- a/engine/key.go +++ b/engine/key.go @@ -13,6 +13,7 @@ type Key struct { TCPModerateReceiveBuffer bool `yaml:"tcp-moderate-receive-buffer"` TCPSendBufferSize string `yaml:"tcp-send-buffer-size"` TCPReceiveBufferSize string `yaml:"tcp-receive-buffer-size"` + MulticastGroups string `yaml:"multicast-groups"` TUNPreUp string `yaml:"tun-pre-up"` TUNPostUp string `yaml:"tun-post-up"` UDPTimeout time.Duration `yaml:"udp-timeout"` diff --git a/engine/parse.go b/engine/parse.go index bb3adc9..c413cc7 100644 --- a/engine/parse.go +++ b/engine/parse.go @@ -150,3 +150,21 @@ func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost return } + +func parseMulticastGroups(s string) (multicastGroups []net.IP, _ error) { + ipStrings := strings.Split(s, ",") + for _, ipString := range ipStrings { + if strings.TrimSpace(ipString) == "" { + continue + } + ip := net.ParseIP(ipString) + if ip == nil { + return nil, fmt.Errorf("invalid IP format: %s", ipString) + } + if !ip.IsMulticast() { + return nil, fmt.Errorf("invalid multicast IP address: %s", ipString) + } + multicastGroups = append(multicastGroups, ip) + } + return +} diff --git a/main.go b/main.go index 7cc4b1f..a7c08e4 100644 --- a/main.go +++ b/main.go @@ -36,6 +36,7 @@ func init() { flag.StringVar(&key.TCPSendBufferSize, "tcp-sndbuf", "", "Set TCP send buffer size for netstack") flag.StringVar(&key.TCPReceiveBufferSize, "tcp-rcvbuf", "", "Set TCP receive buffer size for netstack") flag.BoolVar(&key.TCPModerateReceiveBuffer, "tcp-auto-tuning", false, "Enable TCP receive buffer auto-tuning") + flag.StringVar(&key.MulticastGroups, "multicast-groups", "", "Set multicast groups, separated by commas") flag.StringVar(&key.TUNPreUp, "tun-pre-up", "", "Execute a command before TUN device setup") flag.StringVar(&key.TUNPostUp, "tun-post-up", "", "Execute a command after TUN device setup") flag.BoolVar(&versionFlag, "version", false, "Show version and then quit")