Skip to content

Commit

Permalink
Add support for multicast (#243)
Browse files Browse the repository at this point in the history
  • Loading branch information
Amaindex authored and xjasonlyu committed Aug 27, 2023
1 parent fffcbbe commit dbf8ba5
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 0 deletions.
61 changes: 61 additions & 0 deletions core/nic.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 multicastGroups == nil {
return nil
}
// The default NIC of tun2 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: "\x0A\x00\x00\x01",
PrefixLen: 8,
},
},
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
)
s.AddProtocolAddress(
nicID,
tcpip.ProtocolAddress{
Protocol: ipv6.ProtocolNumber,
AddressWithPrefix: tcpip.AddressWithPrefix{
Address: "\xfd\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
PrefixLen: 8,
},
},
stack.AddressProperties{PEB: stack.CanBePrimaryEndpoint},
)
for _, multicastGroup := range multicastGroups {
if tcpIpAddr := multicastGroup.To4(); tcpIpAddr != nil {
if err := s.JoinGroup(ipv4.ProtocolNumber, nicID, tcpip.Address(tcpIpAddr)); err != nil {
return fmt.Errorf("join multicast groups: %s", err)
}
} else {
tcpIpAddr := multicastGroup.To16()
if err := s.JoinGroup(ipv6.ProtocolNumber, nicID, tcpip.Address(tcpIpAddr)); err != nil {
return fmt.Errorf("join multicast groups: %s", err)
}
}
}
return nil
}
}
9 changes: 9 additions & 0 deletions core/stack.go
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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
Expand Down Expand Up @@ -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),

// Adds default NIC to the given multicast groups.
withMulticastGroups(nicID, cfg.MulticastGroups),
)

for _, opt := range opts {
Expand Down
6 changes: 6 additions & 0 deletions engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,9 +214,15 @@ func netstack(k *Key) (err error) {
opts = append(opts, option.WithTCPReceiveBufferSize(int(size)))
}

var multicastGroups []net.IP = nil
if multicastGroups, err = parseMulticastGroups(k.MulticastGroups); err != nil {
return err
}

if _defaultStack, err = core.CreateStack(&core.Config{
LinkEndpoint: _defaultDevice,
TransportHandler: &mirror.Tunnel{},
MulticastGroups: multicastGroups,
Options: opts,
}); err != nil {
return
Expand Down
1 change: 1 addition & 0 deletions engine/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down
19 changes: 19 additions & 0 deletions engine/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,22 @@ func parseShadowsocks(u *url.URL) (address, method, password, obfsMode, obfsHost

return
}

func parseMulticastGroups(multicastGroupsString string) ([]net.IP, error) {
if multicastGroupsString == "" {
return nil, nil
}
ipStrings := strings.Split(multicastGroupsString, ",")
multicastGroups := []net.IP{}
for _, ipString := range ipStrings {
ip := net.ParseIP(ipString)
if ip == nil {
return nil, fmt.Errorf("unsupported ip format: %s", ipString)
}
if !ip.IsMulticast() {
return nil, fmt.Errorf("invalid multicast address: %s", ipString)
}
multicastGroups = append(multicastGroups, ip)
}
return multicastGroups, nil
}
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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", "", "Join these multicast groups ip1,ip2,...")
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")
Expand Down

0 comments on commit dbf8ba5

Please sign in to comment.