Commit a33e90a7 authored by Mikio Hara's avatar Mikio Hara

x/net/ipv4: add support for source-specific multicast

This CL introduces methods for the manipulation of source-specifc
group into both PacketConn and RawConn as follows:

JoinSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
LeaveSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
ExcludeSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
IncludeSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error

Fixes golang/go#8266.

LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/174030043
parent e9210114
......@@ -93,7 +93,11 @@ func (c *dgramOpt) SetMulticastLoopback(on bool) error {
return setInt(fd, &sockOpts[ssoMulticastLoopback], boolint(on))
}
// JoinGroup joins the group address group on the interface ifi.
// JoinGroup joins the group address group on the interface ifi. By
// default all sources that can cast data to group are accepted. It's
// possible to mute and unmute data transmission from a specific
// source by using ExcludeSourceSpecificGroup and
// IncludeSourceSpecificGroup.
// It uses the system assigned multicast interface when ifi is nil,
// although this is not recommended because the assignment depends on
// platforms and sometimes it might require routing configuration.
......@@ -113,6 +117,8 @@ func (c *dgramOpt) JoinGroup(ifi *net.Interface, group net.Addr) error {
}
// LeaveGroup leaves the group address group on the interface ifi.
// It's allowed to leave the group which is formed by
// JoinSourceSpecificGroup for convenience.
func (c *dgramOpt) LeaveGroup(ifi *net.Interface, group net.Addr) error {
if !c.ok() {
return syscall.EINVAL
......@@ -127,3 +133,91 @@ func (c *dgramOpt) LeaveGroup(ifi *net.Interface, group net.Addr) error {
}
return setGroup(fd, &sockOpts[ssoLeaveGroup], ifi, grp)
}
// JoinSourceSpecificGroup joins the source-specific group consisting
// group and source on the interface ifi. It uses the system assigned
// multicast interface when ifi is nil, although this is not
// recommended because the assignment depends on platforms and
// sometimes it might require routing configuration.
func (c *dgramOpt) JoinSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
if !c.ok() {
return syscall.EINVAL
}
fd, err := c.sysfd()
if err != nil {
return err
}
grp := netAddrToIP4(group)
if grp == nil {
return errMissingAddress
}
src := netAddrToIP4(source)
if src == nil {
return errMissingAddress
}
return setSourceGroup(fd, &sockOpts[ssoJoinSourceGroup], ifi, grp, src)
}
// LeaveSourceSpecificGroup leaves the source-specific group on the
// interface ifi.
func (c *dgramOpt) LeaveSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
if !c.ok() {
return syscall.EINVAL
}
fd, err := c.sysfd()
if err != nil {
return err
}
grp := netAddrToIP4(group)
if grp == nil {
return errMissingAddress
}
src := netAddrToIP4(source)
if src == nil {
return errMissingAddress
}
return setSourceGroup(fd, &sockOpts[ssoLeaveSourceGroup], ifi, grp, src)
}
// ExcludeSourceSpecificGroup excludes the source-specific group from
// the already joined groups by either JoinGroup or
// JoinSourceSpecificGroup on the interface ifi.
func (c *dgramOpt) ExcludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
if !c.ok() {
return syscall.EINVAL
}
fd, err := c.sysfd()
if err != nil {
return err
}
grp := netAddrToIP4(group)
if grp == nil {
return errMissingAddress
}
src := netAddrToIP4(source)
if src == nil {
return errMissingAddress
}
return setSourceGroup(fd, &sockOpts[ssoBlockSourceGroup], ifi, grp, src)
}
// IncludeSourceSpecificGroup includes the excluded source-specific
// group by ExcludeSourceSpecificGroup again on the interface ifi.
func (c *dgramOpt) IncludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
if !c.ok() {
return syscall.EINVAL
}
fd, err := c.sysfd()
if err != nil {
return err
}
grp := netAddrToIP4(group)
if grp == nil {
return errMissingAddress
}
src := netAddrToIP4(source)
if src == nil {
return errMissingAddress
}
return setSourceGroup(fd, &sockOpts[ssoUnblockSourceGroup], ifi, grp, src)
}
......@@ -39,3 +39,19 @@ func (c *dgramOpt) JoinGroup(ifi *net.Interface, grp net.Addr) error {
func (c *dgramOpt) LeaveGroup(ifi *net.Interface, grp net.Addr) error {
return errOpNoSupport
}
func (c *dgramOpt) JoinSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
return errOpNoSupport
}
func (c *dgramOpt) LeaveSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
return errOpNoSupport
}
func (c *dgramOpt) ExcludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
return errOpNoSupport
}
func (c *dgramOpt) IncludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
return errOpNoSupport
}
......@@ -7,8 +7,8 @@
//
// The package provides IP-level socket options that allow
// manipulation of IPv4 facilities. The IPv4 and basic host
// requirements for IPv4 are defined in RFC 791, RFC 1112 and RFC
// 1122.
// requirements for IPv4 are defined in RFC 791, RFC 1112, RFC 1122,
// RFC 3678 and RFC 4607.
//
//
// Unicasting
......
......@@ -29,6 +29,10 @@ var (
// http://tools.ietf.org/html/rfc1112
// RFC 1122 Requirements for Internet Hosts
// http://tools.ietf.org/html/rfc1122
// RFC 3678 Socket Interface Extensions for Multicast Source Filters
// http://tools.ietf.org/html/rfc3678
// RFC 4607 Source-Specific Multicast for IP
// http://tools.ietf.org/html/rfc4607
const (
Version = 4 // protocol version
......
This diff is collapsed.
......@@ -16,10 +16,13 @@ import (
var packetConnMulticastSocketOptionTests = []struct {
net, proto, addr string
gaddr net.Addr
grp, src net.Addr
}{
{"udp4", "", "224.0.0.0:0", &net.UDPAddr{IP: net.IPv4(224, 0, 0, 249)}}, // see RFC 4727
{"ip4", ":icmp", "0.0.0.0", &net.IPAddr{IP: net.IPv4(224, 0, 0, 250)}}, // see RFC 4727
{"udp4", "", "224.0.0.0:0", &net.UDPAddr{IP: net.IPv4(224, 0, 0, 249)}, nil}, // see RFC 4727
{"ip4", ":icmp", "0.0.0.0", &net.IPAddr{IP: net.IPv4(224, 0, 0, 250)}, nil}, // see RFC 4727
{"udp4", "", "232.0.0.0:0", &net.UDPAddr{IP: net.IPv4(232, 0, 1, 249)}, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771
{"ip4", ":icmp", "0.0.0.0", &net.IPAddr{IP: net.IPv4(232, 0, 1, 250)}, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771
}
func TestPacketConnMulticastSocketOptions(t *testing.T) {
......@@ -34,18 +37,33 @@ func TestPacketConnMulticastSocketOptions(t *testing.T) {
for _, tt := range packetConnMulticastSocketOptionTests {
if tt.net == "ip4" && os.Getuid() != 0 {
t.Skip("must be root")
t.Log("must be root")
continue
}
c, err := net.ListenPacket(tt.net+tt.proto, tt.addr)
if err != nil {
t.Fatalf("net.ListenPacket failed: %v", err)
t.Fatal(err)
}
defer c.Close()
p := ipv4.NewPacketConn(c)
defer p.Close()
testMulticastSocketOptions(t, ipv4.NewPacketConn(c), ifi, tt.gaddr)
if tt.src == nil {
testMulticastSocketOptions(t, p, ifi, tt.grp)
} else {
testSourceSpecificMulticastSocketOptions(t, p, ifi, tt.grp, tt.src)
}
}
}
var rawConnMulticastSocketOptionTests = []struct {
grp, src net.Addr
}{
{&net.IPAddr{IP: net.IPv4(224, 0, 0, 250)}, nil}, // see RFC 4727
{&net.IPAddr{IP: net.IPv4(232, 0, 1, 250)}, &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771
}
func TestRawConnMulticastSocketOptions(t *testing.T) {
switch runtime.GOOS {
case "nacl", "plan9", "solaris":
......@@ -59,18 +77,24 @@ func TestRawConnMulticastSocketOptions(t *testing.T) {
t.Skipf("not available on %q", runtime.GOOS)
}
c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
t.Fatalf("net.ListenPacket failed: %v", err)
}
defer c.Close()
for _, tt := range rawConnMulticastSocketOptionTests {
c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
t.Fatal(err)
}
defer c.Close()
r, err := ipv4.NewRawConn(c)
if err != nil {
t.Fatal(err)
}
defer r.Close()
r, err := ipv4.NewRawConn(c)
if err != nil {
t.Fatalf("ipv4.NewRawConn failed: %v", err)
if tt.src == nil {
testMulticastSocketOptions(t, r, ifi, tt.grp)
} else {
testSourceSpecificMulticastSocketOptions(t, r, ifi, tt.grp, tt.src)
}
}
testMulticastSocketOptions(t, r, ifi, &net.IPAddr{IP: net.IPv4(224, 0, 0, 250)}) /// see RFC 4727
}
type testIPv4MulticastConn interface {
......@@ -80,34 +104,92 @@ type testIPv4MulticastConn interface {
SetMulticastLoopback(bool) error
JoinGroup(*net.Interface, net.Addr) error
LeaveGroup(*net.Interface, net.Addr) error
JoinSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
LeaveSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
ExcludeSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
IncludeSourceSpecificGroup(*net.Interface, net.Addr, net.Addr) error
}
func testMulticastSocketOptions(t *testing.T, c testIPv4MulticastConn, ifi *net.Interface, gaddr net.Addr) {
func testMulticastSocketOptions(t *testing.T, c testIPv4MulticastConn, ifi *net.Interface, grp net.Addr) {
const ttl = 255
if err := c.SetMulticastTTL(ttl); err != nil {
t.Fatalf("ipv4.PacketConn.SetMulticastTTL failed: %v", err)
t.Error(err)
return
}
if v, err := c.MulticastTTL(); err != nil {
t.Fatalf("ipv4.PacketConn.MulticastTTL failed: %v", err)
t.Error(err)
return
} else if v != ttl {
t.Fatalf("got unexpected multicast TTL value %v; expected %v", v, ttl)
t.Errorf("got unexpected multicast ttl %v; expected %v", v, ttl)
return
}
for _, toggle := range []bool{true, false} {
if err := c.SetMulticastLoopback(toggle); err != nil {
t.Fatalf("ipv4.PacketConn.SetMulticastLoopback failed: %v", err)
t.Error(err)
return
}
if v, err := c.MulticastLoopback(); err != nil {
t.Fatalf("ipv4.PacketConn.MulticastLoopback failed: %v", err)
t.Error(err)
return
} else if v != toggle {
t.Fatalf("got unexpected multicast loopback %v; expected %v", v, toggle)
t.Errorf("got unexpected multicast loopback %v; expected %v", v, toggle)
return
}
}
if err := c.JoinGroup(ifi, gaddr); err != nil {
t.Fatalf("ipv4.PacketConn.JoinGroup(%v, %v) failed: %v", ifi, gaddr, err)
if err := c.JoinGroup(ifi, grp); err != nil {
t.Error(err)
return
}
if err := c.LeaveGroup(ifi, grp); err != nil {
t.Error(err)
return
}
}
func testSourceSpecificMulticastSocketOptions(t *testing.T, c testIPv4MulticastConn, ifi *net.Interface, grp, src net.Addr) {
// MCAST_JOIN_GROUP -> MCAST_BLOCK_SOURCE -> MCAST_UNBLOCK_SOURCE -> MCAST_LEAVE_GROUP
if err := c.JoinGroup(ifi, grp); err != nil {
t.Error(err)
return
}
if err := c.ExcludeSourceSpecificGroup(ifi, grp, src); err != nil {
switch runtime.GOOS {
case "freebsd", "linux":
default: // platforms that don't support IGMPv2/3 fail here
t.Logf("not supported on %q", runtime.GOOS)
return
}
t.Error(err)
return
}
if err := c.IncludeSourceSpecificGroup(ifi, grp, src); err != nil {
t.Error(err)
return
}
if err := c.LeaveGroup(ifi, grp); err != nil {
t.Error(err)
return
}
// MCAST_JOIN_SOURCE_GROUP -> MCAST_LEAVE_SOURCE_GROUP
if err := c.JoinSourceSpecificGroup(ifi, grp, src); err != nil {
t.Error(err)
return
}
if err := c.LeaveSourceSpecificGroup(ifi, grp, src); err != nil {
t.Error(err)
return
}
// MCAST_JOIN_SOURCE_GROUP -> MCAST_LEAVE_GROUP
if err := c.JoinSourceSpecificGroup(ifi, grp, src); err != nil {
t.Error(err)
return
}
if err := c.LeaveGroup(ifi, gaddr); err != nil {
t.Fatalf("ipv4.PacketConn.LeaveGroup(%v, %v) failed: %v", ifi, gaddr, err)
if err := c.LeaveGroup(ifi, grp); err != nil {
t.Error(err)
return
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment