1// Copyright 2012 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package ipv4_test
6
7import (
8	"bytes"
9	"net"
10	"os"
11	"runtime"
12	"testing"
13	"time"
14
15	"golang.org/x/net/icmp"
16	"golang.org/x/net/internal/iana"
17	"golang.org/x/net/ipv4"
18	"golang.org/x/net/nettest"
19)
20
21var packetConnReadWriteMulticastUDPTests = []struct {
22	addr     string
23	grp, src *net.UDPAddr
24}{
25	{"224.0.0.0:0", &net.UDPAddr{IP: net.IPv4(224, 0, 0, 254)}, nil}, // see RFC 4727
26
27	{"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
28}
29
30func TestPacketConnReadWriteMulticastUDP(t *testing.T) {
31	switch runtime.GOOS {
32	case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "windows":
33		t.Skipf("not supported on %s", runtime.GOOS)
34	}
35	ifi, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagMulticast|net.FlagLoopback)
36	if err != nil {
37		t.Skipf("not available on %s", runtime.GOOS)
38	}
39
40	for _, tt := range packetConnReadWriteMulticastUDPTests {
41		c, err := net.ListenPacket("udp4", tt.addr)
42		if err != nil {
43			t.Fatal(err)
44		}
45		defer c.Close()
46
47		grp := *tt.grp
48		grp.Port = c.LocalAddr().(*net.UDPAddr).Port
49		p := ipv4.NewPacketConn(c)
50		defer p.Close()
51		if tt.src == nil {
52			if err := p.JoinGroup(ifi, &grp); err != nil {
53				t.Fatal(err)
54			}
55			defer p.LeaveGroup(ifi, &grp)
56		} else {
57			if err := p.JoinSourceSpecificGroup(ifi, &grp, tt.src); err != nil {
58				switch runtime.GOOS {
59				case "freebsd", "linux":
60				default: // platforms that don't support IGMPv2/3 fail here
61					t.Logf("not supported on %s", runtime.GOOS)
62					continue
63				}
64				t.Fatal(err)
65			}
66			defer p.LeaveSourceSpecificGroup(ifi, &grp, tt.src)
67		}
68		if err := p.SetMulticastInterface(ifi); err != nil {
69			t.Fatal(err)
70		}
71		if _, err := p.MulticastInterface(); err != nil {
72			t.Fatal(err)
73		}
74		if err := p.SetMulticastLoopback(true); err != nil {
75			t.Fatal(err)
76		}
77		if _, err := p.MulticastLoopback(); err != nil {
78			t.Fatal(err)
79		}
80		cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
81		wb := []byte("HELLO-R-U-THERE")
82
83		for i, toggle := range []bool{true, false, true} {
84			if err := p.SetControlMessage(cf, toggle); err != nil {
85				if protocolNotSupported(err) {
86					t.Logf("not supported on %s", runtime.GOOS)
87					continue
88				}
89				t.Fatal(err)
90			}
91			if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil {
92				t.Fatal(err)
93			}
94			p.SetMulticastTTL(i + 1)
95			if n, err := p.WriteTo(wb, nil, &grp); err != nil {
96				t.Fatal(err)
97			} else if n != len(wb) {
98				t.Fatalf("got %v; want %v", n, len(wb))
99			}
100			rb := make([]byte, 128)
101			if n, _, _, err := p.ReadFrom(rb); err != nil {
102				t.Fatal(err)
103			} else if !bytes.Equal(rb[:n], wb) {
104				t.Fatalf("got %v; want %v", rb[:n], wb)
105			}
106		}
107	}
108}
109
110var packetConnReadWriteMulticastICMPTests = []struct {
111	grp, src *net.IPAddr
112}{
113	{&net.IPAddr{IP: net.IPv4(224, 0, 0, 254)}, nil}, // see RFC 4727
114
115	{&net.IPAddr{IP: net.IPv4(232, 0, 1, 254)}, &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771
116}
117
118func TestPacketConnReadWriteMulticastICMP(t *testing.T) {
119	switch runtime.GOOS {
120	case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "windows":
121		t.Skipf("not supported on %s", runtime.GOOS)
122	}
123	if !nettest.SupportsRawSocket() {
124		t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
125	}
126	ifi, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagMulticast|net.FlagLoopback)
127	if err != nil {
128		t.Skipf("not available on %s", runtime.GOOS)
129	}
130
131	for _, tt := range packetConnReadWriteMulticastICMPTests {
132		c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
133		if err != nil {
134			t.Fatal(err)
135		}
136		defer c.Close()
137
138		p := ipv4.NewPacketConn(c)
139		defer p.Close()
140		if tt.src == nil {
141			if err := p.JoinGroup(ifi, tt.grp); err != nil {
142				t.Fatal(err)
143			}
144			defer p.LeaveGroup(ifi, tt.grp)
145		} else {
146			if err := p.JoinSourceSpecificGroup(ifi, tt.grp, tt.src); err != nil {
147				switch runtime.GOOS {
148				case "freebsd", "linux":
149				default: // platforms that don't support IGMPv2/3 fail here
150					t.Logf("not supported on %s", runtime.GOOS)
151					continue
152				}
153				t.Fatal(err)
154			}
155			defer p.LeaveSourceSpecificGroup(ifi, tt.grp, tt.src)
156		}
157		if err := p.SetMulticastInterface(ifi); err != nil {
158			t.Fatal(err)
159		}
160		if _, err := p.MulticastInterface(); err != nil {
161			t.Fatal(err)
162		}
163		if err := p.SetMulticastLoopback(true); err != nil {
164			t.Fatal(err)
165		}
166		if _, err := p.MulticastLoopback(); err != nil {
167			t.Fatal(err)
168		}
169		cf := ipv4.FlagDst | ipv4.FlagInterface
170		if runtime.GOOS != "illumos" && runtime.GOOS != "solaris" {
171			// Illumos and Solaris never allow modification of ICMP properties.
172			cf |= ipv4.FlagTTL
173		}
174
175		for i, toggle := range []bool{true, false, true} {
176			wb, err := (&icmp.Message{
177				Type: ipv4.ICMPTypeEcho, Code: 0,
178				Body: &icmp.Echo{
179					ID: os.Getpid() & 0xffff, Seq: i + 1,
180					Data: []byte("HELLO-R-U-THERE"),
181				},
182			}).Marshal(nil)
183			if err != nil {
184				t.Fatal(err)
185			}
186			if err := p.SetControlMessage(cf, toggle); err != nil {
187				if protocolNotSupported(err) {
188					t.Logf("not supported on %s", runtime.GOOS)
189					continue
190				}
191				t.Fatal(err)
192			}
193			if err := p.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil {
194				t.Fatal(err)
195			}
196			p.SetMulticastTTL(i + 1)
197			if n, err := p.WriteTo(wb, nil, tt.grp); err != nil {
198				t.Fatal(err)
199			} else if n != len(wb) {
200				t.Fatalf("got %v; want %v", n, len(wb))
201			}
202			rb := make([]byte, 128)
203			if n, _, _, err := p.ReadFrom(rb); err != nil {
204				t.Fatal(err)
205			} else {
206				m, err := icmp.ParseMessage(iana.ProtocolICMP, rb[:n])
207				if err != nil {
208					t.Fatal(err)
209				}
210				switch {
211				case m.Type == ipv4.ICMPTypeEchoReply && m.Code == 0: // net.inet.icmp.bmcastecho=1
212				case m.Type == ipv4.ICMPTypeEcho && m.Code == 0: // net.inet.icmp.bmcastecho=0
213				default:
214					t.Fatalf("got type=%v, code=%v; want type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0)
215				}
216			}
217		}
218	}
219}
220
221var rawConnReadWriteMulticastICMPTests = []struct {
222	grp, src *net.IPAddr
223}{
224	{&net.IPAddr{IP: net.IPv4(224, 0, 0, 254)}, nil}, // see RFC 4727
225
226	{&net.IPAddr{IP: net.IPv4(232, 0, 1, 254)}, &net.IPAddr{IP: net.IPv4(127, 0, 0, 1)}}, // see RFC 5771
227}
228
229func TestRawConnReadWriteMulticastICMP(t *testing.T) {
230	switch runtime.GOOS {
231	case "fuchsia", "hurd", "illumos", "js", "nacl", "plan9", "solaris", "windows":
232		t.Skipf("not supported on %s", runtime.GOOS)
233	}
234	if testing.Short() {
235		t.Skip("to avoid external network")
236	}
237	if !nettest.SupportsRawSocket() {
238		t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
239	}
240	ifi, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagMulticast|net.FlagLoopback)
241	if err != nil {
242		t.Skipf("not available on %s", runtime.GOOS)
243	}
244
245	for _, tt := range rawConnReadWriteMulticastICMPTests {
246		c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
247		if err != nil {
248			t.Fatal(err)
249		}
250		defer c.Close()
251
252		r, err := ipv4.NewRawConn(c)
253		if err != nil {
254			t.Fatal(err)
255		}
256		defer r.Close()
257		if tt.src == nil {
258			if err := r.JoinGroup(ifi, tt.grp); err != nil {
259				t.Fatal(err)
260			}
261			defer r.LeaveGroup(ifi, tt.grp)
262		} else {
263			if err := r.JoinSourceSpecificGroup(ifi, tt.grp, tt.src); err != nil {
264				switch runtime.GOOS {
265				case "freebsd", "linux":
266				default: // platforms that don't support IGMPv2/3 fail here
267					t.Logf("not supported on %s", runtime.GOOS)
268					continue
269				}
270				t.Fatal(err)
271			}
272			defer r.LeaveSourceSpecificGroup(ifi, tt.grp, tt.src)
273		}
274		if err := r.SetMulticastInterface(ifi); err != nil {
275			t.Fatal(err)
276		}
277		if _, err := r.MulticastInterface(); err != nil {
278			t.Fatal(err)
279		}
280		if err := r.SetMulticastLoopback(true); err != nil {
281			t.Fatal(err)
282		}
283		if _, err := r.MulticastLoopback(); err != nil {
284			t.Fatal(err)
285		}
286		cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
287
288		for i, toggle := range []bool{true, false, true} {
289			wb, err := (&icmp.Message{
290				Type: ipv4.ICMPTypeEcho, Code: 0,
291				Body: &icmp.Echo{
292					ID: os.Getpid() & 0xffff, Seq: i + 1,
293					Data: []byte("HELLO-R-U-THERE"),
294				},
295			}).Marshal(nil)
296			if err != nil {
297				t.Fatal(err)
298			}
299			wh := &ipv4.Header{
300				Version:  ipv4.Version,
301				Len:      ipv4.HeaderLen,
302				TOS:      i + 1,
303				TotalLen: ipv4.HeaderLen + len(wb),
304				Protocol: 1,
305				Dst:      tt.grp.IP,
306			}
307			if err := r.SetControlMessage(cf, toggle); err != nil {
308				if protocolNotSupported(err) {
309					t.Logf("not supported on %s", runtime.GOOS)
310					continue
311				}
312				t.Fatal(err)
313			}
314			if err := r.SetDeadline(time.Now().Add(200 * time.Millisecond)); err != nil {
315				t.Fatal(err)
316			}
317			r.SetMulticastTTL(i + 1)
318			if err := r.WriteTo(wh, wb, nil); err != nil {
319				t.Fatal(err)
320			}
321			rb := make([]byte, ipv4.HeaderLen+128)
322			if rh, b, _, err := r.ReadFrom(rb); err != nil {
323				t.Fatal(err)
324			} else {
325				m, err := icmp.ParseMessage(iana.ProtocolICMP, b)
326				if err != nil {
327					t.Fatal(err)
328				}
329				switch {
330				case (rh.Dst.IsLoopback() || rh.Dst.IsLinkLocalUnicast() || rh.Dst.IsGlobalUnicast()) && m.Type == ipv4.ICMPTypeEchoReply && m.Code == 0: // net.inet.icmp.bmcastecho=1
331				case rh.Dst.IsMulticast() && m.Type == ipv4.ICMPTypeEcho && m.Code == 0: // net.inet.icmp.bmcastecho=0
332				default:
333					t.Fatalf("got type=%v, code=%v; want type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0)
334				}
335			}
336		}
337	}
338}
339