diff --git a/ipv4/dgramopt_posix.go b/ipv4/dgramopt_posix.go index fce881a8..68e25e20 100644 --- a/ipv4/dgramopt_posix.go +++ b/ipv4/dgramopt_posix.go @@ -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) +} diff --git a/ipv4/dgramopt_stub.go b/ipv4/dgramopt_stub.go index a4688d92..c3aa2a49 100644 --- a/ipv4/dgramopt_stub.go +++ b/ipv4/dgramopt_stub.go @@ -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 +} diff --git a/ipv4/doc.go b/ipv4/doc.go index 576cd83e..94095f82 100644 --- a/ipv4/doc.go +++ b/ipv4/doc.go @@ -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 diff --git a/ipv4/header.go b/ipv4/header.go index 2919607c..ba09d758 100644 --- a/ipv4/header.go +++ b/ipv4/header.go @@ -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 diff --git a/ipv4/multicast_test.go b/ipv4/multicast_test.go index 2ece7f03..da6773c9 100644 --- a/ipv4/multicast_test.go +++ b/ipv4/multicast_test.go @@ -5,6 +5,7 @@ package ipv4_test import ( + "bytes" "net" "os" "runtime" @@ -17,6 +18,15 @@ import ( "golang.org/x/net/ipv4" ) +var packetConnReadWriteMulticastUDPTests = []struct { + addr string + grp, src *net.UDPAddr +}{ + {"224.0.0.0:0", &net.UDPAddr{IP: net.IPv4(224, 0, 0, 254)}, nil}, // see RFC 4727 + + {"232.0.1.0:0", &net.UDPAddr{IP: net.IPv4(232, 0, 1, 254)}, &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771 +} + func TestPacketConnReadWriteMulticastUDP(t *testing.T) { switch runtime.GOOS { case "nacl", "plan9", "solaris", "windows": @@ -27,63 +37,86 @@ func TestPacketConnReadWriteMulticastUDP(t *testing.T) { t.Skipf("not available on %q", runtime.GOOS) } - c, err := net.ListenPacket("udp4", "224.0.0.0:0") // see RFC 4727 - if err != nil { - t.Fatalf("net.ListenPacket failed: %v", err) - } - defer c.Close() + for _, tt := range packetConnReadWriteMulticastUDPTests { + c, err := net.ListenPacket("udp4", tt.addr) + if err != nil { + t.Fatal(err) + } + defer c.Close() - _, port, err := net.SplitHostPort(c.LocalAddr().String()) - if err != nil { - t.Fatalf("net.SplitHostPort failed: %v", err) - } - dst, err := net.ResolveUDPAddr("udp4", "224.0.0.254:"+port) // see RFC 4727 - if err != nil { - t.Fatalf("net.ResolveUDPAddr failed: %v", err) - } - - p := ipv4.NewPacketConn(c) - defer p.Close() - if err := p.JoinGroup(ifi, dst); err != nil { - t.Fatalf("ipv4.PacketConn.JoinGroup on %v failed: %v", ifi, err) - } - if err := p.SetMulticastInterface(ifi); err != nil { - t.Fatalf("ipv4.PacketConn.SetMulticastInterface failed: %v", err) - } - if _, err := p.MulticastInterface(); err != nil { - t.Fatalf("ipv4.PacketConn.MulticastInterface failed: %v", err) - } - if err := p.SetMulticastLoopback(true); err != nil { - t.Fatalf("ipv4.PacketConn.SetMulticastLoopback failed: %v", err) - } - if _, err := p.MulticastLoopback(); err != nil { - t.Fatalf("ipv4.PacketConn.MulticastLoopback failed: %v", err) - } - cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface - - for i, toggle := range []bool{true, false, true} { - if err := p.SetControlMessage(cf, toggle); err != nil { - if nettest.ProtocolNotSupported(err) { - t.Skipf("not supported on %q", runtime.GOOS) + grp := *tt.grp + grp.Port = c.LocalAddr().(*net.UDPAddr).Port + p := ipv4.NewPacketConn(c) + defer p.Close() + if tt.src == nil { + if err := p.JoinGroup(ifi, &grp); err != nil { + t.Fatal(err) } - t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err) - } - if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { - t.Fatalf("ipv4.PacketConn.SetDeadline failed: %v", err) - } - p.SetMulticastTTL(i + 1) - if _, err := p.WriteTo([]byte("HELLO-R-U-THERE"), nil, dst); err != nil { - t.Fatalf("ipv4.PacketConn.WriteTo failed: %v", err) - } - b := make([]byte, 128) - if _, cm, _, err := p.ReadFrom(b); err != nil { - t.Fatalf("ipv4.PacketConn.ReadFrom failed: %v", err) + defer p.LeaveGroup(ifi, &grp) } else { - t.Logf("rcvd cmsg: %v", cm) + if err := p.JoinSourceSpecificGroup(ifi, &grp, tt.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) + continue + } + t.Fatal(err) + } + defer p.LeaveSourceSpecificGroup(ifi, &grp, tt.src) + } + if err := p.SetMulticastInterface(ifi); err != nil { + t.Fatal(err) + } + if _, err := p.MulticastInterface(); err != nil { + t.Fatal(err) + } + if err := p.SetMulticastLoopback(true); err != nil { + t.Fatal(err) + } + if _, err := p.MulticastLoopback(); err != nil { + t.Fatal(err) + } + cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface + wb := []byte("HELLO-R-U-THERE") + + for i, toggle := range []bool{true, false, true} { + if err := p.SetControlMessage(cf, toggle); err != nil { + if nettest.ProtocolNotSupported(err) { + t.Logf("not supported on %q", runtime.GOOS) + continue + } + t.Fatal(err) + } + if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { + t.Fatal(err) + } + p.SetMulticastTTL(i + 1) + if n, err := p.WriteTo(wb, nil, &grp); err != nil { + t.Fatal(err) + } else if n != len(wb) { + t.Fatalf("got %v; expected %v", n, len(wb)) + } + rb := make([]byte, 128) + if n, cm, _, err := p.ReadFrom(rb); err != nil { + t.Fatal(err) + } else if !bytes.Equal(rb[:n], wb) { + t.Fatalf("got %v; expected %v", rb[:n], wb) + } else { + t.Logf("rcvd cmsg: %v", cm) + } } } } +var packetConnReadWriteMulticastICMPTests = []struct { + grp, src *net.IPAddr +}{ + {&net.IPAddr{IP: net.IPv4(224, 0, 0, 254)}, nil}, // see RFC 4727 + + {&net.IPAddr{IP: net.IPv4(232, 0, 1, 254)}, &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771 +} + func TestPacketConnReadWriteMulticastICMP(t *testing.T) { switch runtime.GOOS { case "nacl", "plan9", "solaris", "windows": @@ -97,79 +130,101 @@ func TestPacketConnReadWriteMulticastICMP(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() - - dst, err := net.ResolveIPAddr("ip4", "224.0.0.254") // see RFC 4727 - if err != nil { - t.Fatalf("net.ResolveIPAddr failed: %v", err) - } - - p := ipv4.NewPacketConn(c) - defer p.Close() - if err := p.JoinGroup(ifi, dst); err != nil { - t.Fatalf("ipv4.PacketConn.JoinGroup on %v failed: %v", ifi, err) - } - if err := p.SetMulticastInterface(ifi); err != nil { - t.Fatalf("ipv4.PacketConn.SetMulticastInterface failed: %v", err) - } - if _, err := p.MulticastInterface(); err != nil { - t.Fatalf("ipv4.PacketConn.MulticastInterface failed: %v", err) - } - if err := p.SetMulticastLoopback(true); err != nil { - t.Fatalf("ipv4.PacketConn.SetMulticastLoopback failed: %v", err) - } - if _, err := p.MulticastLoopback(); err != nil { - t.Fatalf("ipv4.PacketConn.MulticastLoopback failed: %v", err) - } - cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface - - for i, toggle := range []bool{true, false, true} { - wb, err := (&icmp.Message{ - Type: ipv4.ICMPTypeEcho, Code: 0, - Body: &icmp.Echo{ - ID: os.Getpid() & 0xffff, Seq: i + 1, - Data: []byte("HELLO-R-U-THERE"), - }, - }).Marshal(nil) + for _, tt := range packetConnReadWriteMulticastICMPTests { + c, err := net.ListenPacket("ip4:icmp", "0.0.0.0") if err != nil { - t.Fatalf("icmp.Message.Marshal failed: %v", err) + t.Fatal(err) } - if err := p.SetControlMessage(cf, toggle); err != nil { - if nettest.ProtocolNotSupported(err) { - t.Skipf("not supported on %q", runtime.GOOS) + defer c.Close() + + p := ipv4.NewPacketConn(c) + defer p.Close() + if tt.src == nil { + if err := p.JoinGroup(ifi, tt.grp); err != nil { + t.Fatal(err) } - t.Fatalf("ipv4.PacketConn.SetControlMessage failed: %v", err) - } - if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { - t.Fatalf("ipv4.PacketConn.SetDeadline failed: %v", err) - } - p.SetMulticastTTL(i + 1) - if _, err := p.WriteTo(wb, nil, dst); err != nil { - t.Fatalf("ipv4.PacketConn.WriteTo failed: %v", err) - } - b := make([]byte, 128) - if n, cm, _, err := p.ReadFrom(b); err != nil { - t.Fatalf("ipv4.PacketConn.ReadFrom failed: %v", err) + defer p.LeaveGroup(ifi, tt.grp) } else { - t.Logf("rcvd cmsg: %v", cm) - m, err := icmp.ParseMessage(iana.ProtocolICMP, b[:n]) - if err != nil { - t.Fatalf("icmp.ParseMessage failed: %v", err) + if err := p.JoinSourceSpecificGroup(ifi, tt.grp, tt.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) + continue + } + t.Fatal(err) } - switch { - case m.Type == ipv4.ICMPTypeEchoReply && m.Code == 0: // net.inet.icmp.bmcastecho=1 - case m.Type == ipv4.ICMPTypeEcho && m.Code == 0: // net.inet.icmp.bmcastecho=0 - default: - t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0) + defer p.LeaveSourceSpecificGroup(ifi, tt.grp, tt.src) + } + if err := p.SetMulticastInterface(ifi); err != nil { + t.Fatal(err) + } + if _, err := p.MulticastInterface(); err != nil { + t.Fatal(err) + } + if err := p.SetMulticastLoopback(true); err != nil { + t.Fatal(err) + } + if _, err := p.MulticastLoopback(); err != nil { + t.Fatal(err) + } + cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface + + for i, toggle := range []bool{true, false, true} { + wb, err := (&icmp.Message{ + Type: ipv4.ICMPTypeEcho, Code: 0, + Body: &icmp.Echo{ + ID: os.Getpid() & 0xffff, Seq: i + 1, + Data: []byte("HELLO-R-U-THERE"), + }, + }).Marshal(nil) + if err != nil { + t.Fatal(err) + } + if err := p.SetControlMessage(cf, toggle); err != nil { + if nettest.ProtocolNotSupported(err) { + t.Logf("not supported on %q", runtime.GOOS) + continue + } + t.Fatal(err) + } + if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { + t.Fatal(err) + } + p.SetMulticastTTL(i + 1) + if n, err := p.WriteTo(wb, nil, tt.grp); err != nil { + t.Fatal(err) + } else if n != len(wb) { + t.Fatalf("got %v; expected %v", n, len(wb)) + } + rb := make([]byte, 128) + if n, cm, _, err := p.ReadFrom(rb); err != nil { + t.Fatal(err) + } else { + t.Logf("rcvd cmsg: %v", cm) + m, err := icmp.ParseMessage(iana.ProtocolICMP, rb[:n]) + if err != nil { + t.Fatal(err) + } + switch { + case m.Type == ipv4.ICMPTypeEchoReply && m.Code == 0: // net.inet.icmp.bmcastecho=1 + case m.Type == ipv4.ICMPTypeEcho && m.Code == 0: // net.inet.icmp.bmcastecho=0 + default: + t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0) + } } } } } +var rawConnReadWriteMulticastICMPTests = []struct { + grp, src *net.IPAddr +}{ + {&net.IPAddr{IP: net.IPv4(224, 0, 0, 254)}, nil}, // see RFC 4727 + + {&net.IPAddr{IP: net.IPv4(232, 0, 1, 254)}, &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771 +} + func TestRawConnReadWriteMulticastICMP(t *testing.T) { if testing.Short() { t.Skip("to avoid external network") @@ -182,85 +237,97 @@ func TestRawConnReadWriteMulticastICMP(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() - - dst, err := net.ResolveIPAddr("ip4", "224.0.0.254") // see RFC 4727 - if err != nil { - t.Fatalf("ResolveIPAddr failed: %v", err) - } - - r, err := ipv4.NewRawConn(c) - if err != nil { - t.Fatalf("ipv4.NewRawConn failed: %v", err) - } - defer r.Close() - if err := r.JoinGroup(ifi, dst); err != nil { - t.Fatalf("ipv4.RawConn.JoinGroup on %v failed: %v", ifi, err) - } - if err := r.SetMulticastInterface(ifi); err != nil { - t.Fatalf("ipv4.RawConn.SetMulticastInterface failed: %v", err) - } - if _, err := r.MulticastInterface(); err != nil { - t.Fatalf("ipv4.RawConn.MulticastInterface failed: %v", err) - } - if err := r.SetMulticastLoopback(true); err != nil { - t.Fatalf("ipv4.RawConn.SetMulticastLoopback failed: %v", err) - } - if _, err := r.MulticastLoopback(); err != nil { - t.Fatalf("ipv4.RawConn.MulticastLoopback failed: %v", err) - } - cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface - - for i, toggle := range []bool{true, false, true} { - wb, err := (&icmp.Message{ - Type: ipv4.ICMPTypeEcho, Code: 0, - Body: &icmp.Echo{ - ID: os.Getpid() & 0xffff, Seq: i + 1, - Data: []byte("HELLO-R-U-THERE"), - }, - }).Marshal(nil) + for _, tt := range rawConnReadWriteMulticastICMPTests { + c, err := net.ListenPacket("ip4:icmp", "0.0.0.0") if err != nil { - t.Fatalf("icmp.Message.Marshal failed: %v", err) + t.Fatal(err) } - wh := &ipv4.Header{ - Version: ipv4.Version, - Len: ipv4.HeaderLen, - TOS: i + 1, - TotalLen: ipv4.HeaderLen + len(wb), - Protocol: 1, - Dst: dst.IP, + defer c.Close() + + r, err := ipv4.NewRawConn(c) + if err != nil { + t.Fatal(err) } - if err := r.SetControlMessage(cf, toggle); err != nil { - if nettest.ProtocolNotSupported(err) { - t.Skipf("not supported on %q", runtime.GOOS) + defer r.Close() + if tt.src == nil { + if err := r.JoinGroup(ifi, tt.grp); err != nil { + t.Fatal(err) } - t.Fatalf("ipv4.RawConn.SetControlMessage failed: %v", err) - } - if err := r.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { - t.Fatalf("ipv4.RawConn.SetDeadline failed: %v", err) - } - r.SetMulticastTTL(i + 1) - if err := r.WriteTo(wh, wb, nil); err != nil { - t.Fatalf("ipv4.RawConn.WriteTo failed: %v", err) - } - rb := make([]byte, ipv4.HeaderLen+128) - if rh, b, cm, err := r.ReadFrom(rb); err != nil { - t.Fatalf("ipv4.RawConn.ReadFrom failed: %v", err) + defer r.LeaveGroup(ifi, tt.grp) } else { - t.Logf("rcvd cmsg: %v", cm) - m, err := icmp.ParseMessage(iana.ProtocolICMP, b) - if err != nil { - t.Fatalf("icmp.ParseMessage failed: %v", err) + if err := r.JoinSourceSpecificGroup(ifi, tt.grp, tt.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) + continue + } + t.Fatal(err) } - switch { - case (rh.Dst.IsLoopback() || rh.Dst.IsLinkLocalUnicast() || rh.Dst.IsGlobalUnicast()) && m.Type == ipv4.ICMPTypeEchoReply && m.Code == 0: // net.inet.icmp.bmcastecho=1 - case rh.Dst.IsMulticast() && m.Type == ipv4.ICMPTypeEcho && m.Code == 0: // net.inet.icmp.bmcastecho=0 - default: - t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0) + defer r.LeaveSourceSpecificGroup(ifi, tt.grp, tt.src) + } + if err := r.SetMulticastInterface(ifi); err != nil { + t.Fatal(err) + } + if _, err := r.MulticastInterface(); err != nil { + t.Fatal(err) + } + if err := r.SetMulticastLoopback(true); err != nil { + t.Fatal(err) + } + if _, err := r.MulticastLoopback(); err != nil { + t.Fatal(err) + } + cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface + + for i, toggle := range []bool{true, false, true} { + wb, err := (&icmp.Message{ + Type: ipv4.ICMPTypeEcho, Code: 0, + Body: &icmp.Echo{ + ID: os.Getpid() & 0xffff, Seq: i + 1, + Data: []byte("HELLO-R-U-THERE"), + }, + }).Marshal(nil) + if err != nil { + t.Fatal(err) + } + wh := &ipv4.Header{ + Version: ipv4.Version, + Len: ipv4.HeaderLen, + TOS: i + 1, + TotalLen: ipv4.HeaderLen + len(wb), + Protocol: 1, + Dst: tt.grp.IP, + } + if err := r.SetControlMessage(cf, toggle); err != nil { + if nettest.ProtocolNotSupported(err) { + t.Logf("not supported on %q", runtime.GOOS) + continue + } + t.Fatal(err) + } + if err := r.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil { + t.Fatal(err) + } + r.SetMulticastTTL(i + 1) + if err := r.WriteTo(wh, wb, nil); err != nil { + t.Fatal(err) + } + rb := make([]byte, ipv4.HeaderLen+128) + if rh, b, cm, err := r.ReadFrom(rb); err != nil { + t.Fatal(err) + } else { + t.Logf("rcvd cmsg: %v", cm) + m, err := icmp.ParseMessage(iana.ProtocolICMP, b) + if err != nil { + t.Fatal(err) + } + switch { + case (rh.Dst.IsLoopback() || rh.Dst.IsLinkLocalUnicast() || rh.Dst.IsGlobalUnicast()) && m.Type == ipv4.ICMPTypeEchoReply && m.Code == 0: // net.inet.icmp.bmcastecho=1 + case rh.Dst.IsMulticast() && m.Type == ipv4.ICMPTypeEcho && m.Code == 0: // net.inet.icmp.bmcastecho=0 + default: + t.Fatalf("got type=%v, code=%v; expected type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0) + } } } } diff --git a/ipv4/multicastsockopt_test.go b/ipv4/multicastsockopt_test.go index cda97bdd..56df5be6 100644 --- a/ipv4/multicastsockopt_test.go +++ b/ipv4/multicastsockopt_test.go @@ -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, 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 + } +} + +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, grp); err != nil { + t.Error(err) + return } }