x/net/ipv4: add support for icmp filter on linux

LGTM=iant
R=iant
CC=golang-codereviews
https://golang.org/cl/179510043
diff --git a/ipv4/dgramopt_posix.go b/ipv4/dgramopt_posix.go
index aed1ebc..103c4f6 100644
--- a/ipv4/dgramopt_posix.go
+++ b/ipv4/dgramopt_posix.go
@@ -223,3 +223,29 @@
 	}
 	return setSourceGroup(fd, &sockOpts[ssoUnblockSourceGroup], ifi, grp, src)
 }
+
+// ICMPFilter returns an ICMP filter.
+// Currently only Linux supports this.
+func (c *dgramOpt) ICMPFilter() (*ICMPFilter, error) {
+	if !c.ok() {
+		return nil, syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return nil, err
+	}
+	return getICMPFilter(fd, &sockOpts[ssoICMPFilter])
+}
+
+// SetICMPFilter deploys the ICMP filter.
+// Currently only Linux supports this.
+func (c *dgramOpt) SetICMPFilter(f *ICMPFilter) error {
+	if !c.ok() {
+		return syscall.EINVAL
+	}
+	fd, err := c.sysfd()
+	if err != nil {
+		return err
+	}
+	return setICMPFilter(fd, &sockOpts[ssoICMPFilter], f)
+}
diff --git a/ipv4/dgramopt_stub.go b/ipv4/dgramopt_stub.go
index 5c3d48f..b74df69 100644
--- a/ipv4/dgramopt_stub.go
+++ b/ipv4/dgramopt_stub.go
@@ -92,3 +92,15 @@
 func (c *dgramOpt) IncludeSourceSpecificGroup(ifi *net.Interface, group, source net.Addr) error {
 	return errOpNoSupport
 }
+
+// ICMPFilter returns an ICMP filter.
+// Currently only Linux supports this.
+func (c *dgramOpt) ICMPFilter() (*ICMPFilter, error) {
+	return nil, errOpNoSupport
+}
+
+// SetICMPFilter deploys the ICMP filter.
+// Currently only Linux supports this.
+func (c *dgramOpt) SetICMPFilter(f *ICMPFilter) error {
+	return errOpNoSupport
+}
diff --git a/ipv4/icmp.go b/ipv4/icmp.go
index 4682e8c..dbd05cf 100644
--- a/ipv4/icmp.go
+++ b/ipv4/icmp.go
@@ -21,3 +21,37 @@
 func (typ ICMPType) Protocol() int {
 	return iana.ProtocolICMP
 }
+
+// An ICMPFilter represents an ICMP message filter for incoming
+// packets. The filter belongs to a packet delivery path on a host and
+// it cannot interact with forwarding packets or tunnel-outer packets.
+//
+// Note: RFC 2460 defines a reasonable role model and it works not
+// only for IPv6 but IPv4. A node means a device that implements IP.
+// A router means a node that forwards IP packets not explicitly
+// addressed to itself, and a host means a node that is not a router.
+type ICMPFilter struct {
+	sysICMPFilter
+}
+
+// Accept accepts incoming ICMP packets including the type field value
+// typ.
+func (f *ICMPFilter) Accept(typ ICMPType) {
+	f.accept(typ)
+}
+
+// Block blocks incoming ICMP packets including the type field value
+// typ.
+func (f *ICMPFilter) Block(typ ICMPType) {
+	f.block(typ)
+}
+
+// SetAll sets the filter action to the filter.
+func (f *ICMPFilter) SetAll(block bool) {
+	f.setAll(block)
+}
+
+// WillBlock reports whether the ICMP type will be blocked.
+func (f *ICMPFilter) WillBlock(typ ICMPType) bool {
+	return f.willBlock(typ)
+}
diff --git a/ipv4/icmp_linux.go b/ipv4/icmp_linux.go
new file mode 100644
index 0000000..c912253
--- /dev/null
+++ b/ipv4/icmp_linux.go
@@ -0,0 +1,25 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ipv4
+
+func (f *sysICMPFilter) accept(typ ICMPType) {
+	f.Data &^= 1 << (uint32(typ) & 31)
+}
+
+func (f *sysICMPFilter) block(typ ICMPType) {
+	f.Data |= 1 << (uint32(typ) & 31)
+}
+
+func (f *sysICMPFilter) setAll(block bool) {
+	if block {
+		f.Data = 1<<32 - 1
+	} else {
+		f.Data = 0
+	}
+}
+
+func (f *sysICMPFilter) willBlock(typ ICMPType) bool {
+	return f.Data&(1<<(uint32(typ)&31)) != 0
+}
diff --git a/ipv4/icmp_stub.go b/ipv4/icmp_stub.go
new file mode 100644
index 0000000..9ee9b6a
--- /dev/null
+++ b/ipv4/icmp_stub.go
@@ -0,0 +1,25 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !linux
+
+package ipv4
+
+const sysSizeofICMPFilter = 0x0
+
+type sysICMPFilter struct {
+}
+
+func (f *sysICMPFilter) accept(typ ICMPType) {
+}
+
+func (f *sysICMPFilter) block(typ ICMPType) {
+}
+
+func (f *sysICMPFilter) setAll(block bool) {
+}
+
+func (f *sysICMPFilter) willBlock(typ ICMPType) bool {
+	return false
+}
diff --git a/ipv4/icmp_test.go b/ipv4/icmp_test.go
new file mode 100644
index 0000000..d398f5b
--- /dev/null
+++ b/ipv4/icmp_test.go
@@ -0,0 +1,95 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package ipv4_test
+
+import (
+	"net"
+	"os"
+	"reflect"
+	"runtime"
+	"testing"
+
+	"golang.org/x/net/ipv4"
+)
+
+var icmpStringTests = []struct {
+	in  ipv4.ICMPType
+	out string
+}{
+	{ipv4.ICMPTypeDestinationUnreachable, "destination unreachable"},
+
+	{256, "<nil>"},
+}
+
+func TestICMPString(t *testing.T) {
+	for _, tt := range icmpStringTests {
+		s := tt.in.String()
+		if s != tt.out {
+			t.Errorf("got %s; want %s", s, tt.out)
+		}
+	}
+}
+
+func TestICMPFilter(t *testing.T) {
+	switch runtime.GOOS {
+	case "linux":
+	default:
+		t.Skipf("not supported on %q", runtime.GOOS)
+	}
+
+	var f ipv4.ICMPFilter
+	for _, toggle := range []bool{false, true} {
+		f.SetAll(toggle)
+		for _, typ := range []ipv4.ICMPType{
+			ipv4.ICMPTypeDestinationUnreachable,
+			ipv4.ICMPTypeEchoReply,
+			ipv4.ICMPTypeTimeExceeded,
+			ipv4.ICMPTypeParameterProblem,
+		} {
+			f.Accept(typ)
+			if f.WillBlock(typ) {
+				t.Errorf("ipv4.ICMPFilter.Set(%v, false) failed", typ)
+			}
+			f.Block(typ)
+			if !f.WillBlock(typ) {
+				t.Errorf("ipv4.ICMPFilter.Set(%v, true) failed", typ)
+			}
+		}
+	}
+}
+
+func TestSetICMPFilter(t *testing.T) {
+	switch runtime.GOOS {
+	case "linux":
+	default:
+		t.Skipf("not supported on %q", runtime.GOOS)
+	}
+	if os.Getuid() != 0 {
+		t.Skip("must be root")
+	}
+
+	c, err := net.ListenPacket("ip4:icmp", "127.0.0.1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer c.Close()
+
+	p := ipv4.NewPacketConn(c)
+
+	var f ipv4.ICMPFilter
+	f.SetAll(true)
+	f.Accept(ipv4.ICMPTypeEcho)
+	f.Accept(ipv4.ICMPTypeEchoReply)
+	if err := p.SetICMPFilter(&f); err != nil {
+		t.Fatal(err)
+	}
+	kf, err := p.ICMPFilter()
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !reflect.DeepEqual(kf, &f) {
+		t.Fatalf("got %#v; want %#v", kf, f)
+	}
+}
diff --git a/ipv4/sockopt.go b/ipv4/sockopt.go
index 1c85553..ace37d3 100644
--- a/ipv4/sockopt.go
+++ b/ipv4/sockopt.go
@@ -17,6 +17,7 @@
 	ssoPacketInfo                // incbound or outbound packet path
 	ssoHeaderPrepend             // ipv4 header prepend
 	ssoStripHeader               // strip ipv4 header
+	ssoICMPFilter                // icmp filter
 	ssoJoinGroup                 // any-source multicast
 	ssoLeaveGroup                // any-source multicast
 	ssoJoinSourceGroup           // source-specific multicast
@@ -31,6 +32,7 @@
 	ssoTypeByte = iota + 1
 	ssoTypeInt
 	ssoTypeInterface
+	ssoTypeICMPFilter
 	ssoTypeIPMreq
 	ssoTypeIPMreqn
 	ssoTypeGroupReq
diff --git a/ipv4/sockopt_unix.go b/ipv4/sockopt_unix.go
index 2a492f9..50cdbd8 100644
--- a/ipv4/sockopt_unix.go
+++ b/ipv4/sockopt_unix.go
@@ -79,6 +79,25 @@
 	}
 }
 
+func getICMPFilter(fd int, opt *sockOpt) (*ICMPFilter, error) {
+	if opt.name < 1 || opt.typ != ssoTypeICMPFilter {
+		return nil, errOpNoSupport
+	}
+	var f ICMPFilter
+	l := sysSockoptLen(sysSizeofICMPFilter)
+	if err := getsockopt(fd, iana.ProtocolReserved, opt.name, unsafe.Pointer(&f.sysICMPFilter), &l); err != nil {
+		return nil, os.NewSyscallError("getsockopt", err)
+	}
+	return &f, nil
+}
+
+func setICMPFilter(fd int, opt *sockOpt, f *ICMPFilter) error {
+	if opt.name < 1 || opt.typ != ssoTypeICMPFilter {
+		return errOpNoSupport
+	}
+	return os.NewSyscallError("setsockopt", setsockopt(fd, iana.ProtocolReserved, opt.name, unsafe.Pointer(&f.sysICMPFilter), sysSizeofICMPFilter))
+}
+
 func setGroup(fd int, opt *sockOpt, ifi *net.Interface, grp net.IP) error {
 	if opt.name < 1 {
 		return errOpNoSupport
diff --git a/ipv4/sockopt_windows.go b/ipv4/sockopt_windows.go
index b443021..c4c2441 100644
--- a/ipv4/sockopt_windows.go
+++ b/ipv4/sockopt_windows.go
@@ -47,6 +47,14 @@
 	return setsockoptInterface(fd, opt.name, ifi)
 }
 
+func getICMPFilter(fd syscall.Handle, opt *sockOpt) (*ICMPFilter, error) {
+	return nil, errOpNoSupport
+}
+
+func setICMPFilter(fd syscall.Handle, opt *sockOpt, f *ICMPFilter) error {
+	return errOpNoSupport
+}
+
 func setGroup(fd syscall.Handle, opt *sockOpt, ifi *net.Interface, grp net.IP) error {
 	if opt.name < 1 || opt.typ != ssoTypeIPMreq {
 		return errOpNoSupport
diff --git a/ipv4/sys_linux.go b/ipv4/sys_linux.go
index ab98215..b1f3878 100644
--- a/ipv4/sys_linux.go
+++ b/ipv4/sys_linux.go
@@ -27,6 +27,7 @@
 		ssoReceiveTTL:         {sysIP_RECVTTL, ssoTypeInt},
 		ssoPacketInfo:         {sysIP_PKTINFO, ssoTypeInt},
 		ssoHeaderPrepend:      {sysIP_HDRINCL, ssoTypeInt},
+		ssoICMPFilter:         {sysICMP_FILTER, ssoTypeICMPFilter},
 		ssoJoinGroup:          {sysMCAST_JOIN_GROUP, ssoTypeGroupReq},
 		ssoLeaveGroup:         {sysMCAST_LEAVE_GROUP, ssoTypeGroupReq},
 		ssoJoinSourceGroup:    {sysMCAST_JOIN_SOURCE_GROUP, ssoTypeGroupSourceReq},