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},