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
21func TestPacketConnReadWriteUnicastUDP(t *testing.T) {
22	switch runtime.GOOS {
23	case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
24		t.Skipf("not supported on %s", runtime.GOOS)
25	}
26	// Skip this check on z/OS since net.Interfaces() does not return loopback, however
27	// this does not affect the test and it will still pass.
28	if _, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagLoopback); err != nil && runtime.GOOS != "zos" {
29		t.Skipf("not available on %s", runtime.GOOS)
30	}
31
32	c, err := nettest.NewLocalPacketListener("udp4")
33	if err != nil {
34		t.Fatal(err)
35	}
36	defer c.Close()
37	p := ipv4.NewPacketConn(c)
38	defer p.Close()
39
40	dst := c.LocalAddr()
41	cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
42	wb := []byte("HELLO-R-U-THERE")
43
44	for i, toggle := range []bool{true, false, true} {
45		if err := p.SetControlMessage(cf, toggle); err != nil {
46			if protocolNotSupported(err) {
47				t.Logf("not supported on %s", runtime.GOOS)
48				continue
49			}
50			t.Fatal(err)
51		}
52		p.SetTTL(i + 1)
53		if err := p.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
54			t.Fatal(err)
55		}
56		if n, err := p.WriteTo(wb, nil, dst); err != nil {
57			t.Fatal(err)
58		} else if n != len(wb) {
59			t.Fatalf("got %v; want %v", n, len(wb))
60		}
61		rb := make([]byte, 128)
62		if err := p.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
63			t.Fatal(err)
64		}
65		if n, _, _, err := p.ReadFrom(rb); err != nil {
66			t.Fatal(err)
67		} else if !bytes.Equal(rb[:n], wb) {
68			t.Fatalf("got %v; want %v", rb[:n], wb)
69		}
70	}
71}
72
73func TestPacketConnReadWriteUnicastICMP(t *testing.T) {
74	switch runtime.GOOS {
75	case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
76		t.Skipf("not supported on %s", runtime.GOOS)
77	}
78	if !nettest.SupportsRawSocket() {
79		t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
80	}
81	// Skip this check on z/OS since net.Interfaces() does not return loopback, however
82	// this does not affect the test and it will still pass.
83	if _, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagLoopback); err != nil && runtime.GOOS != "zos" {
84		t.Skipf("not available on %s", runtime.GOOS)
85	}
86
87	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
88	if err != nil {
89		t.Fatal(err)
90	}
91	defer c.Close()
92
93	dst, err := net.ResolveIPAddr("ip4", "127.0.0.1")
94	if err != nil {
95		t.Fatal(err)
96	}
97	p := ipv4.NewPacketConn(c)
98	defer p.Close()
99	cf := ipv4.FlagDst | ipv4.FlagInterface
100	if runtime.GOOS != "illumos" && runtime.GOOS != "solaris" {
101		// Illumos and Solaris never allow modification of ICMP properties.
102		cf |= ipv4.FlagTTL
103	}
104
105	for i, toggle := range []bool{true, false, true} {
106		wb, err := (&icmp.Message{
107			Type: ipv4.ICMPTypeEcho, Code: 0,
108			Body: &icmp.Echo{
109				ID: os.Getpid() & 0xffff, Seq: i + 1,
110				Data: []byte("HELLO-R-U-THERE"),
111			},
112		}).Marshal(nil)
113		if err != nil {
114			t.Fatal(err)
115		}
116		if err := p.SetControlMessage(cf, toggle); err != nil {
117			if protocolNotSupported(err) {
118				t.Logf("not supported on %s", runtime.GOOS)
119				continue
120			}
121			t.Fatal(err)
122		}
123		p.SetTTL(i + 1)
124		if err := p.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
125			t.Fatal(err)
126		}
127		if n, err := p.WriteTo(wb, nil, dst); err != nil {
128			t.Fatal(err)
129		} else if n != len(wb) {
130			t.Fatalf("got %v; want %v", n, len(wb))
131		}
132		rb := make([]byte, 128)
133	loop:
134		if err := p.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
135			t.Fatal(err)
136		}
137		if n, _, _, err := p.ReadFrom(rb); err != nil {
138			switch runtime.GOOS {
139			case "darwin", "ios": // older darwin kernels have some limitation on receiving icmp packet through raw socket
140				t.Logf("not supported on %s", runtime.GOOS)
141				continue
142			}
143			t.Fatal(err)
144		} else {
145			m, err := icmp.ParseMessage(iana.ProtocolICMP, rb[:n])
146			if err != nil {
147				t.Fatal(err)
148			}
149			if runtime.GOOS == "linux" && m.Type == ipv4.ICMPTypeEcho {
150				// On Linux we must handle own sent packets.
151				goto loop
152			}
153			if m.Type != ipv4.ICMPTypeEchoReply || m.Code != 0 {
154				t.Fatalf("got type=%v, code=%v; want type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0)
155			}
156		}
157	}
158}
159
160func TestRawConnReadWriteUnicastICMP(t *testing.T) {
161	switch runtime.GOOS {
162	case "fuchsia", "hurd", "js", "nacl", "plan9", "windows":
163		t.Skipf("not supported on %s", runtime.GOOS)
164	}
165	if !nettest.SupportsRawSocket() {
166		t.Skipf("not supported on %s/%s", runtime.GOOS, runtime.GOARCH)
167	}
168	if _, err := nettest.RoutedInterface("ip4", net.FlagUp|net.FlagLoopback); err != nil {
169		t.Skipf("not available on %s", runtime.GOOS)
170	}
171
172	c, err := net.ListenPacket("ip4:icmp", "0.0.0.0")
173	if err != nil {
174		t.Fatal(err)
175	}
176	defer c.Close()
177
178	dst, err := net.ResolveIPAddr("ip4", "127.0.0.1")
179	if err != nil {
180		t.Fatal(err)
181	}
182	r, err := ipv4.NewRawConn(c)
183	if err != nil {
184		t.Fatal(err)
185	}
186	defer r.Close()
187	cf := ipv4.FlagTTL | ipv4.FlagDst | ipv4.FlagInterface
188
189	for i, toggle := range []bool{true, false, true} {
190		wb, err := (&icmp.Message{
191			Type: ipv4.ICMPTypeEcho, Code: 0,
192			Body: &icmp.Echo{
193				ID: os.Getpid() & 0xffff, Seq: i + 1,
194				Data: []byte("HELLO-R-U-THERE"),
195			},
196		}).Marshal(nil)
197		if err != nil {
198			t.Fatal(err)
199		}
200		wh := &ipv4.Header{
201			Version:  ipv4.Version,
202			Len:      ipv4.HeaderLen,
203			TOS:      i + 1,
204			TotalLen: ipv4.HeaderLen + len(wb),
205			TTL:      i + 1,
206			Protocol: 1,
207			Dst:      dst.IP,
208		}
209		if err := r.SetControlMessage(cf, toggle); err != nil {
210			if protocolNotSupported(err) {
211				t.Logf("not supported on %s", runtime.GOOS)
212				continue
213			}
214			t.Fatal(err)
215		}
216		if err := r.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
217			t.Fatal(err)
218		}
219		if err := r.WriteTo(wh, wb, nil); err != nil {
220			t.Fatal(err)
221		}
222		rb := make([]byte, ipv4.HeaderLen+128)
223	loop:
224		if err := r.SetReadDeadline(time.Now().Add(100 * time.Millisecond)); err != nil {
225			t.Fatal(err)
226		}
227		if _, b, _, err := r.ReadFrom(rb); err != nil {
228			switch runtime.GOOS {
229			case "darwin", "ios": // older darwin kernels have some limitation on receiving icmp packet through raw socket
230				t.Logf("not supported on %s", runtime.GOOS)
231				continue
232			}
233			t.Fatal(err)
234		} else {
235			m, err := icmp.ParseMessage(iana.ProtocolICMP, b)
236			if err != nil {
237				t.Fatal(err)
238			}
239			if runtime.GOOS == "linux" && m.Type == ipv4.ICMPTypeEcho {
240				// On Linux we must handle own sent packets.
241				goto loop
242			}
243			if m.Type != ipv4.ICMPTypeEchoReply || m.Code != 0 {
244				t.Fatalf("got type=%v, code=%v; want type=%v, code=%v", m.Type, m.Code, ipv4.ICMPTypeEchoReply, 0)
245			}
246		}
247	}
248}
249