Skip to content

Commit

Permalink
Feature: add support for multicast (#245)
Browse files Browse the repository at this point in the history
* add support for multicast (#243)

* adjust setup

---------

Co-authored-by: xjasonlyu <xjasonlyu@gmail.com>
  • Loading branch information
Amaindex and xjasonlyu authored Aug 27, 2023
1 parent fffcbbe commit 90f7754
Show file tree
Hide file tree
Showing 6 changed files with 96 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 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
}
}
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),

// Add 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 @@ -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))
Expand All @@ -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
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
18 changes: 18 additions & 0 deletions engine/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
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", "", "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")
Expand Down

0 comments on commit 90f7754

Please sign in to comment.