Add ICMP forwarding support

The current implementation supports forwarding only ICMP Echo(ping) packets, as OS not allow to send arbitrary packets without escalating privileges.  All other ICMP packets are dropped(ignored)

Changes in 'icmp_packet.go' and 'icmp.go' was mostly maded by cursor.

Signed-off-by: Yevhen Vydolob <yvydolob@redhat.com>
This commit is contained in:
Yevhen Vydolob
2026-01-15 09:45:30 +02:00
committed by Christophe Fergeau
parent 6f646c44e5
commit cf0e470794
27 changed files with 2226 additions and 1 deletions
+1 -1
View File
@@ -25,6 +25,7 @@ require (
github.com/vishvananda/netlink v1.3.1
golang.org/x/crypto v0.50.0
golang.org/x/mod v0.35.0
golang.org/x/net v0.52.0
golang.org/x/sync v0.20.0
golang.org/x/sys v0.43.0
gopkg.in/yaml.v3 v3.0.1
@@ -45,7 +46,6 @@ require (
github.com/vishvananda/netns v0.0.5 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.52.0 // indirect
golang.org/x/text v0.36.0 // indirect
golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.43.0 // indirect
+73
View File
@@ -0,0 +1,73 @@
package forwarder
import (
"sync"
log "github.com/sirupsen/logrus"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
type Forwarder struct {
handler func(request *ICMPForwarderRequest)
stack *stack.Stack
}
func ICMP(s *stack.Stack, nat map[tcpip.Address]tcpip.Address, natLock *sync.Mutex) *Forwarder {
return NewForwarder(s, func(r *ICMPForwarderRequest) {
localAddress := r.ID().LocalAddress
// Skip forwarding for addresses that should be handled locally
if header.IsV4LoopbackAddress(localAddress) || localAddress == header.IPv4Broadcast {
return
}
// Apply NAT translation if needed
natLock.Lock()
if replaced, ok := nat[localAddress]; ok {
localAddress = replaced
}
natLock.Unlock()
pkt := r.Packet()
if pkt == nil {
log.Warningf("Dropping ICMP packet from VM (no packet data)")
return
}
// Check if this is an ICMP Echo Request (PING)
transportHeader := pkt.TransportHeader().Slice()
if len(transportHeader) < header.ICMPv4MinimumSize {
log.Warningf("Dropping ICMP packet from VM (packet too short)")
return
}
icmpHeader := header.ICMPv4(transportHeader)
if icmpHeader.Type() != header.ICMPv4Echo {
// Not a PING, drop it
log.Warningf("Dropping ICMP packet from VM (type %d, not Echo Request)", icmpHeader.Type())
return
}
// This is a PING request - forward it using unprivileged ICMP sockets
go handlePingRequest(s, r, localAddress, icmpHeader, pkt)
})
}
// HandlePacket handles all packets.
//
// This function is expected to be passed as an argument to the
// stack.SetTransportProtocolHandler function.
func (f *Forwarder) HandlePacket(id stack.TransportEndpointID, pkt *stack.PacketBuffer) bool {
f.handler(NewICMPForwarderRequest(f.stack, id, pkt.IncRef()))
return true
}
// NewForwarder allocates and initializes a new forwarder.
func NewForwarder(s *stack.Stack, handler func(*ICMPForwarderRequest)) *Forwarder {
return &Forwarder{
stack: s,
handler: handler,
}
}
+40
View File
@@ -0,0 +1,40 @@
package forwarder
import (
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/waiter"
)
// ICMPForwarderRequest represents a request to forward an ICMP packet.
type ICMPForwarderRequest struct {
stack *stack.Stack
id stack.TransportEndpointID
pkt *stack.PacketBuffer
}
// NewICMPForwarderRequest creates a new ICMP forwarder request.
func NewICMPForwarderRequest(s *stack.Stack, id stack.TransportEndpointID, pkt *stack.PacketBuffer) *ICMPForwarderRequest {
return &ICMPForwarderRequest{
stack: s,
id: id,
pkt: pkt,
}
}
// ID returns the 4-tuple (src address, src port, dst address, dst port) that
// represents the connection request.
func (f *ICMPForwarderRequest) ID() stack.TransportEndpointID {
return f.id
}
// Packet returns the packet buffer associated with this forwarder request.
func (f *ICMPForwarderRequest) Packet() *stack.PacketBuffer {
return f.pkt
}
// CreateEndpoint creates a new endpoint for this forwarder request.
func (f *ICMPForwarderRequest) CreateEndpoint(s *stack.Stack, wq *waiter.Queue) (tcpip.Endpoint, tcpip.Error) {
// Use the stack's public NewEndpoint API instead of linkname
return s.NewEndpoint(f.pkt.TransportProtocolNumber, f.pkt.NetworkProtocolNumber, wq)
}
+253
View File
@@ -0,0 +1,253 @@
package forwarder
import (
"fmt"
"net"
log "github.com/sirupsen/logrus"
netIcmp "golang.org/x/net/icmp"
"golang.org/x/net/ipv4"
"gvisor.dev/gvisor/pkg/buffer"
"gvisor.dev/gvisor/pkg/tcpip"
"gvisor.dev/gvisor/pkg/tcpip/checksum"
"gvisor.dev/gvisor/pkg/tcpip/header"
"gvisor.dev/gvisor/pkg/tcpip/stack"
)
// echoRequestDetails contains the extracted details from an ICMP echo request.
type echoRequestDetails struct {
ident uint16
seq uint16
payload []byte
srcAddr tcpip.Address
dataBuf buffer.Buffer
}
// safeUint16 safely converts an int to uint16, clamping to valid range.
// ICMP ID and sequence numbers are 16-bit values, so values outside this range
// are invalid and will be clamped.
func safeUint16(v int) uint16 {
if v < 0 {
return 0
}
if v > 0xFFFF {
return 0xFFFF
}
return uint16(v)
}
// handlePingRequest handles forwarding an ICMP echo request (PING) from the VM
// to the external network and injecting the reply back into the VM.
func handlePingRequest(s *stack.Stack, r *ICMPForwarderRequest, destAddr tcpip.Address, icmpHeader header.ICMPv4, pkt *stack.PacketBuffer) {
defer pkt.DecRef()
// Extract ICMP echo request details
details, err := extractEchoRequestDetails(r, icmpHeader, pkt)
if err != nil {
return
}
defer details.dataBuf.Release()
// Create ICMP connection
conn, err := createICMPConnection()
if err != nil {
return
}
defer conn.Close()
// Send the echo request
if err := sendEchoRequest(conn, destAddr, details.ident, details.seq, details.payload); err != nil {
return
}
// Receive and parse the echo reply
echoReply, err := receiveEchoReply(conn)
if err != nil {
return
}
// Validate the reply matches our request
if !validateEchoReply(echoReply, details.ident, details.seq) {
return
}
// Forward the reply back to the VM's network stack
// Safely convert int to uint16 (ICMP ID and Seq are 16-bit values)
forwardEchoReply(s, r, details.srcAddr, destAddr, safeUint16(echoReply.ID), safeUint16(echoReply.Seq), echoReply.Data)
}
// extractEchoRequestDetails extracts the identifier, sequence, payload, and source address
// from an ICMP echo request packet.
func extractEchoRequestDetails(r *ICMPForwarderRequest, icmpHeader header.ICMPv4, pkt *stack.PacketBuffer) (*echoRequestDetails, error) {
ident := icmpHeader.Ident()
seq := icmpHeader.Sequence()
// Extract payload data
dataBuf := pkt.Data().ToBuffer()
dataSize := int(dataBuf.Size())
payload := make([]byte, dataSize)
if dataSize > 0 {
_, _ = dataBuf.ReadAt(payload, 0)
}
// Get source address from the request
srcAddr := r.ID().RemoteAddress
return &echoRequestDetails{
ident: ident,
seq: seq,
payload: payload,
srcAddr: srcAddr,
dataBuf: dataBuf,
}, nil
}
// sendEchoRequest creates and sends an ICMP echo request message.
func sendEchoRequest(conn *netIcmp.PacketConn, destAddr tcpip.Address, ident, seq uint16, payload []byte) error {
// Create ICMP echo request message
msg := &netIcmp.Message{
Type: ipv4.ICMPTypeEcho,
Code: 0,
Body: &netIcmp.Echo{
ID: int(ident),
Seq: int(seq),
Data: payload,
},
}
// Marshal the message
msgBytes, err := msg.Marshal(nil)
if err != nil {
log.Debugf("Failed to marshal ICMP message: %v", err)
return err
}
// Parse destination address
dstIP := net.ParseIP(destAddr.String())
if dstIP == nil {
log.Debugf("Failed to parse destination address: %s", destAddr)
return fmt.Errorf("failed to parse destination address: %s", destAddr)
}
// Create destination address based on platform
dst := createDestinationAddr(dstIP)
// Send the ping request
_, err = conn.WriteTo(msgBytes, dst)
if err != nil {
log.Debugf("Failed to send ICMP echo request: %v", err)
return err
}
return nil
}
// receiveEchoReply reads and parses an ICMP echo reply from the connection.
func receiveEchoReply(conn *netIcmp.PacketConn) (*netIcmp.Echo, error) {
// Read the reply
replyBytes := make([]byte, 1500)
n, _, err := conn.ReadFrom(replyBytes)
if err != nil {
log.Debugf("Failed to receive ICMP echo reply: %v", err)
return nil, err
}
// Extract ICMP data (skip IP header on Windows)
replyData, err := extractICMPData(replyBytes[:n])
if err != nil {
return nil, err
}
// Parse the reply
replyMsg, err := netIcmp.ParseMessage(ipv4.ICMPTypeEchoReply.Protocol(), replyData)
if err != nil {
log.Debugf("Failed to parse ICMP reply: %v", err)
return nil, err
}
// Check if it's an echo reply
if replyMsg.Type != ipv4.ICMPTypeEchoReply {
log.Debugf("Received ICMP message type %v, expected Echo Reply", replyMsg.Type)
return nil, fmt.Errorf("unexpected ICMP message type: %v", replyMsg.Type)
}
echoReply, ok := replyMsg.Body.(*netIcmp.Echo)
if !ok {
log.Debugf("ICMP reply body is not an Echo")
return nil, fmt.Errorf("ICMP reply body is not an Echo")
}
return echoReply, nil
}
// validateEchoReply verifies that the echo reply matches the original request.
func validateEchoReply(echoReply *netIcmp.Echo, expectedIdent, expectedSeq uint16) bool {
if echoReply.ID != int(expectedIdent) || echoReply.Seq != int(expectedSeq) {
log.Debugf("ICMP reply ID/Seq mismatch: got ID=%d Seq=%d, expected ID=%d Seq=%d",
echoReply.ID, echoReply.Seq, expectedIdent, expectedSeq)
return false
}
return true
}
// forwardEchoReply creates an ICMP echo reply packet and forwards it back to the VM.
func forwardEchoReply(s *stack.Stack, r *ICMPForwarderRequest, dstAddr tcpip.Address, srcAddr tcpip.Address, ident, seq uint16, data []byte) {
// Create ICMP echo reply header
icmpHeaderSize := header.ICMPv4MinimumSize
icmpBuf := make([]byte, icmpHeaderSize+len(data))
icmpHdr := header.ICMPv4(icmpBuf)
icmpHdr.SetType(header.ICMPv4EchoReply)
icmpHdr.SetCode(0)
icmpHdr.SetIdent(ident)
icmpHdr.SetSequence(seq)
// Copy data
if len(data) > 0 {
copy(icmpBuf[icmpHeaderSize:], data)
}
// Calculate checksum
icmpHdr.SetChecksum(0)
icmpHdr.SetChecksum(^checksum.Checksum(icmpBuf, 0))
// Get the original packet's network info
origPkt := r.Packet()
if origPkt == nil {
return
}
// Find route to send the reply back
// Use srcAddr (the address we pinged) as the local address so the reply appears
// to come from the address the VM originally pinged, not from the gateway
route, err := s.FindRoute(origPkt.NICID, srcAddr, dstAddr, header.IPv4ProtocolNumber, false)
if err != nil {
log.Debugf("Failed to find route for ICMP reply: %v", err)
return
}
defer route.Release()
// Create packet buffer with ICMP reply
payload := buffer.MakeWithData(icmpBuf)
pkt := stack.NewPacketBuffer(stack.PacketBufferOptions{
ReserveHeaderBytes: int(route.MaxHeaderLength()),
Payload: payload,
})
defer pkt.DecRef()
pkt.NetworkProtocolNumber = header.IPv4ProtocolNumber
pkt.TransportProtocolNumber = header.ICMPv4ProtocolNumber
// Write the packet
params := stack.NetworkHeaderParams{
Protocol: header.ICMPv4ProtocolNumber,
TTL: 64,
TOS: 0,
}
if err := route.WritePacket(params, pkt); err != nil {
log.Debugf("Failed to forward ICMP echo reply: %v", err)
return
}
log.Debugf("Successfully forwarded ICMP echo reply to %s", dstAddr)
}
@@ -0,0 +1,42 @@
//go:build !windows
package forwarder
import (
"net"
"time"
log "github.com/sirupsen/logrus"
netIcmp "golang.org/x/net/icmp"
)
// createICMPConnection creates an ICMP connection using unprivileged ICMP sockets (udp4) on Linux/macOS.
func createICMPConnection() (*netIcmp.PacketConn, error) {
conn, err := netIcmp.ListenPacket("udp4", "0.0.0.0")
if err != nil {
log.Debugf("Failed to create ICMP connection: %v", err)
return nil, err
}
// Set read deadline
if err := conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil {
conn.Close()
log.Debugf("Failed to set read deadline: %v", err)
return nil, err
}
return conn, nil
}
// createDestinationAddr creates a destination address for Unix unprivileged sockets.
func createDestinationAddr(dstIP net.IP) net.Addr {
// Linux/macOS use net.UDPAddr for unprivileged sockets
return &net.UDPAddr{IP: dstIP, Port: 0}
}
// extractICMPData extracts ICMP data from the received bytes.
// On Linux/macOS unprivileged sockets, it returns the data as-is.
func extractICMPData(replyBytes []byte) ([]byte, error) {
// Linux/macOS unprivileged sockets return just the ICMP data
return replyBytes, nil
}
@@ -0,0 +1,62 @@
//go:build windows
package forwarder
import (
"fmt"
"net"
"time"
log "github.com/sirupsen/logrus"
netIcmp "golang.org/x/net/icmp"
)
// createICMPConnection creates an ICMP connection using privileged raw sockets (ip4:icmp) on Windows.
func createICMPConnection() (*netIcmp.PacketConn, error) {
conn, err := netIcmp.ListenPacket("ip4:icmp", "0.0.0.0")
if err != nil {
log.Debugf("Failed to create ICMP connection: %v", err)
return nil, err
}
// Set read deadline
if err := conn.SetReadDeadline(time.Now().Add(5 * time.Second)); err != nil {
conn.Close()
log.Debugf("Failed to set read deadline: %v", err)
return nil, err
}
return conn, nil
}
// createDestinationAddr creates a destination address for Windows raw sockets.
func createDestinationAddr(dstIP net.IP) net.Addr {
// Windows requires net.IPAddr for raw sockets
return &net.IPAddr{IP: dstIP}
}
// extractICMPData extracts ICMP data from the received bytes.
// On Windows with raw sockets, it skips the IP header.
func extractICMPData(replyBytes []byte) ([]byte, error) {
// Raw sockets on Windows include the IP header, so we need to skip it
if len(replyBytes) < 20 {
log.Debugf("Reply packet too short: %d bytes", len(replyBytes))
return nil, fmt.Errorf("reply packet too short: %d bytes", len(replyBytes))
}
// Check if it's IPv4 (first byte: version and IHL)
version := (replyBytes[0] >> 4) & 0x0F
if version != 4 {
log.Debugf("Unexpected IP version: %d", version)
return nil, fmt.Errorf("unexpected IP version: %d", version)
}
// Get IP header length (IHL is in the lower 4 bits of first byte, in 4-byte units)
ihl := int(replyBytes[0]&0x0F) * 4
if ihl < 20 || ihl > len(replyBytes) {
log.Debugf("Invalid IP header length: %d", ihl)
return nil, fmt.Errorf("invalid IP header length: %d", ihl)
}
return replyBytes[ihl:], nil
}
+3
View File
@@ -16,6 +16,7 @@ import (
"gvisor.dev/gvisor/pkg/tcpip/adapters/gonet"
"gvisor.dev/gvisor/pkg/tcpip/network/ipv4"
"gvisor.dev/gvisor/pkg/tcpip/stack"
"gvisor.dev/gvisor/pkg/tcpip/transport/icmp"
"gvisor.dev/gvisor/pkg/tcpip/transport/tcp"
"gvisor.dev/gvisor/pkg/tcpip/transport/udp"
)
@@ -28,6 +29,8 @@ func addServices(configuration *types.Configuration, s *stack.Stack, ipPool *tap
s.SetTransportProtocolHandler(tcp.ProtocolNumber, tcpForwarder.HandlePacket)
udpForwarder := forwarder.UDP(s, translation, &natLock)
s.SetTransportProtocolHandler(udp.ProtocolNumber, udpForwarder.HandlePacket)
icmpForwarder := forwarder.ICMP(s, translation, &natLock)
s.SetTransportProtocolHandler(icmp.ProtocolNumber4, icmpForwarder.HandlePacket)
dnsMux, err := dnsServer(configuration, s)
if err != nil {
+5
View File
@@ -124,4 +124,9 @@ var _ = ginkgo.Describe("ping with gvproxy and vfkit", func() {
log.Infof("ping: %s", out)
gomega.Expect(err).To(gomega.HaveOccurred())
})
ginkgo.It("should succeed to ping an localhost", func() {
out, err := sshExec("ping -w2 127.0.0.1")
log.Infof("ping: %s", out)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
})
})
+59
View File
@@ -0,0 +1,59 @@
// 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 icmp
import (
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// A DstUnreach represents an ICMP destination unreachable message
// body.
type DstUnreach struct {
Data []byte // data, known as original datagram field
Extensions []Extension // extensions
}
// Len implements the Len method of MessageBody interface.
func (p *DstUnreach) Len(proto int) int {
if p == nil {
return 0
}
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
return l
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *DstUnreach) Marshal(proto int) ([]byte, error) {
var typ Type
switch proto {
case iana.ProtocolICMP:
typ = ipv4.ICMPTypeDestinationUnreachable
case iana.ProtocolIPv6ICMP:
typ = ipv6.ICMPTypeDestinationUnreachable
default:
return nil, errInvalidProtocol
}
if !validExtensions(typ, p.Extensions) {
return nil, errInvalidExtension
}
return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
}
// parseDstUnreach parses b as an ICMP destination unreachable message
// body.
func parseDstUnreach(proto int, typ Type, b []byte) (MessageBody, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
p := &DstUnreach{}
var err error
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
if err != nil {
return nil, err
}
return p, nil
}
+173
View File
@@ -0,0 +1,173 @@
// Copyright 2012 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 icmp
import (
"encoding/binary"
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// An Echo represents an ICMP echo request or reply message body.
type Echo struct {
ID int // identifier
Seq int // sequence number
Data []byte // data
}
// Len implements the Len method of MessageBody interface.
func (p *Echo) Len(proto int) int {
if p == nil {
return 0
}
return 4 + len(p.Data)
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *Echo) Marshal(proto int) ([]byte, error) {
b := make([]byte, 4+len(p.Data))
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
binary.BigEndian.PutUint16(b[2:4], uint16(p.Seq))
copy(b[4:], p.Data)
return b, nil
}
// parseEcho parses b as an ICMP echo request or reply message body.
func parseEcho(proto int, _ Type, b []byte) (MessageBody, error) {
bodyLen := len(b)
if bodyLen < 4 {
return nil, errMessageTooShort
}
p := &Echo{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(binary.BigEndian.Uint16(b[2:4]))}
if bodyLen > 4 {
p.Data = make([]byte, bodyLen-4)
copy(p.Data, b[4:])
}
return p, nil
}
// An ExtendedEchoRequest represents an ICMP extended echo request
// message body.
type ExtendedEchoRequest struct {
ID int // identifier
Seq int // sequence number
Local bool // must be true when identifying by name or index
Extensions []Extension // extensions
}
// Len implements the Len method of MessageBody interface.
func (p *ExtendedEchoRequest) Len(proto int) int {
if p == nil {
return 0
}
l, _ := multipartMessageBodyDataLen(proto, false, nil, p.Extensions)
return l
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *ExtendedEchoRequest) Marshal(proto int) ([]byte, error) {
var typ Type
switch proto {
case iana.ProtocolICMP:
typ = ipv4.ICMPTypeExtendedEchoRequest
case iana.ProtocolIPv6ICMP:
typ = ipv6.ICMPTypeExtendedEchoRequest
default:
return nil, errInvalidProtocol
}
if !validExtensions(typ, p.Extensions) {
return nil, errInvalidExtension
}
b, err := marshalMultipartMessageBody(proto, false, nil, p.Extensions)
if err != nil {
return nil, err
}
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
b[2] = byte(p.Seq)
if p.Local {
b[3] |= 0x01
}
return b, nil
}
// parseExtendedEchoRequest parses b as an ICMP extended echo request
// message body.
func parseExtendedEchoRequest(proto int, typ Type, b []byte) (MessageBody, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
p := &ExtendedEchoRequest{ID: int(binary.BigEndian.Uint16(b[:2])), Seq: int(b[2])}
if b[3]&0x01 != 0 {
p.Local = true
}
var err error
_, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
if err != nil {
return nil, err
}
return p, nil
}
// An ExtendedEchoReply represents an ICMP extended echo reply message
// body.
type ExtendedEchoReply struct {
ID int // identifier
Seq int // sequence number
State int // 3-bit state working together with Message.Code
Active bool // probed interface is active
IPv4 bool // probed interface runs IPv4
IPv6 bool // probed interface runs IPv6
}
// Len implements the Len method of MessageBody interface.
func (p *ExtendedEchoReply) Len(proto int) int {
if p == nil {
return 0
}
return 4
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *ExtendedEchoReply) Marshal(proto int) ([]byte, error) {
b := make([]byte, 4)
binary.BigEndian.PutUint16(b[:2], uint16(p.ID))
b[2] = byte(p.Seq)
b[3] = byte(p.State<<5) & 0xe0
if p.Active {
b[3] |= 0x04
}
if p.IPv4 {
b[3] |= 0x02
}
if p.IPv6 {
b[3] |= 0x01
}
return b, nil
}
// parseExtendedEchoReply parses b as an ICMP extended echo reply
// message body.
func parseExtendedEchoReply(proto int, _ Type, b []byte) (MessageBody, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
p := &ExtendedEchoReply{
ID: int(binary.BigEndian.Uint16(b[:2])),
Seq: int(b[2]),
State: int(b[3]) >> 5,
}
if b[3]&0x04 != 0 {
p.Active = true
}
if b[3]&0x02 != 0 {
p.IPv4 = true
}
if b[3]&0x01 != 0 {
p.IPv6 = true
}
return p, nil
}
+113
View File
@@ -0,0 +1,113 @@
// 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 icmp
import (
"net"
"runtime"
"time"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
var _ net.PacketConn = &PacketConn{}
// A PacketConn represents a packet network endpoint that uses either
// ICMPv4 or ICMPv6.
type PacketConn struct {
c net.PacketConn
p4 *ipv4.PacketConn
p6 *ipv6.PacketConn
}
func (c *PacketConn) ok() bool { return c != nil && c.c != nil }
// IPv4PacketConn returns the ipv4.PacketConn of c.
// It returns nil when c is not created as the endpoint for ICMPv4.
func (c *PacketConn) IPv4PacketConn() *ipv4.PacketConn {
if !c.ok() {
return nil
}
return c.p4
}
// IPv6PacketConn returns the ipv6.PacketConn of c.
// It returns nil when c is not created as the endpoint for ICMPv6.
func (c *PacketConn) IPv6PacketConn() *ipv6.PacketConn {
if !c.ok() {
return nil
}
return c.p6
}
// ReadFrom reads an ICMP message from the connection.
func (c *PacketConn) ReadFrom(b []byte) (int, net.Addr, error) {
if !c.ok() {
return 0, nil, errInvalidConn
}
// Please be informed that ipv4.NewPacketConn enables
// IP_STRIPHDR option by default on Darwin.
// See golang.org/issue/9395 for further information.
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && c.p4 != nil {
n, _, peer, err := c.p4.ReadFrom(b)
return n, peer, err
}
return c.c.ReadFrom(b)
}
// WriteTo writes the ICMP message b to dst.
// The provided dst must be net.UDPAddr when c is a non-privileged
// datagram-oriented ICMP endpoint.
// Otherwise it must be net.IPAddr.
func (c *PacketConn) WriteTo(b []byte, dst net.Addr) (int, error) {
if !c.ok() {
return 0, errInvalidConn
}
return c.c.WriteTo(b, dst)
}
// Close closes the endpoint.
func (c *PacketConn) Close() error {
if !c.ok() {
return errInvalidConn
}
return c.c.Close()
}
// LocalAddr returns the local network address.
func (c *PacketConn) LocalAddr() net.Addr {
if !c.ok() {
return nil
}
return c.c.LocalAddr()
}
// SetDeadline sets the read and write deadlines associated with the
// endpoint.
func (c *PacketConn) SetDeadline(t time.Time) error {
if !c.ok() {
return errInvalidConn
}
return c.c.SetDeadline(t)
}
// SetReadDeadline sets the read deadline associated with the
// endpoint.
func (c *PacketConn) SetReadDeadline(t time.Time) error {
if !c.ok() {
return errInvalidConn
}
return c.c.SetReadDeadline(t)
}
// SetWriteDeadline sets the write deadline associated with the
// endpoint.
func (c *PacketConn) SetWriteDeadline(t time.Time) error {
if !c.ok() {
return errInvalidConn
}
return c.c.SetWriteDeadline(t)
}
+170
View File
@@ -0,0 +1,170 @@
// Copyright 2015 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 icmp
import (
"encoding/binary"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// An Extension represents an ICMP extension.
type Extension interface {
// Len returns the length of ICMP extension.
// The provided proto must be either the ICMPv4 or ICMPv6
// protocol number.
Len(proto int) int
// Marshal returns the binary encoding of ICMP extension.
// The provided proto must be either the ICMPv4 or ICMPv6
// protocol number.
Marshal(proto int) ([]byte, error)
}
const extensionVersion = 2
func validExtensionHeader(b []byte) bool {
v := int(b[0]&0xf0) >> 4
s := binary.BigEndian.Uint16(b[2:4])
if s != 0 {
s = checksum(b)
}
if v != extensionVersion || s != 0 {
return false
}
return true
}
// parseExtensions parses b as a list of ICMP extensions.
// The length attribute l must be the length attribute field in
// received icmp messages.
//
// It will return a list of ICMP extensions and an adjusted length
// attribute that represents the length of the padded original
// datagram field. Otherwise, it returns an error.
func parseExtensions(typ Type, b []byte, l int) ([]Extension, int, error) {
// Still a lot of non-RFC 4884 compliant implementations are
// out there. Set the length attribute l to 128 when it looks
// inappropriate for backwards compatibility.
//
// A minimal extension at least requires 8 octets; 4 octets
// for an extension header, and 4 octets for a single object
// header.
//
// See RFC 4884 for further information.
switch typ {
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
if len(b) < 8 || !validExtensionHeader(b) {
return nil, -1, errNoExtension
}
l = 0
default:
if 128 > l || l+8 > len(b) {
l = 128
}
if l+8 > len(b) {
return nil, -1, errNoExtension
}
if !validExtensionHeader(b[l:]) {
if l == 128 {
return nil, -1, errNoExtension
}
l = 128
if !validExtensionHeader(b[l:]) {
return nil, -1, errNoExtension
}
}
}
var exts []Extension
for b = b[l+4:]; len(b) >= 4; {
ol := int(binary.BigEndian.Uint16(b[:2]))
if 4 > ol || ol > len(b) {
break
}
switch b[2] {
case classMPLSLabelStack:
ext, err := parseMPLSLabelStack(b[:ol])
if err != nil {
return nil, -1, err
}
exts = append(exts, ext)
case classInterfaceInfo:
ext, err := parseInterfaceInfo(b[:ol])
if err != nil {
return nil, -1, err
}
exts = append(exts, ext)
case classInterfaceIdent:
ext, err := parseInterfaceIdent(b[:ol])
if err != nil {
return nil, -1, err
}
exts = append(exts, ext)
default:
ext := &RawExtension{Data: make([]byte, ol)}
copy(ext.Data, b[:ol])
exts = append(exts, ext)
}
b = b[ol:]
}
return exts, l, nil
}
func validExtensions(typ Type, exts []Extension) bool {
switch typ {
case ipv4.ICMPTypeDestinationUnreachable, ipv4.ICMPTypeTimeExceeded, ipv4.ICMPTypeParameterProblem,
ipv6.ICMPTypeDestinationUnreachable, ipv6.ICMPTypeTimeExceeded:
for i := range exts {
switch exts[i].(type) {
case *MPLSLabelStack, *InterfaceInfo, *RawExtension:
default:
return false
}
}
return true
case ipv4.ICMPTypeExtendedEchoRequest, ipv6.ICMPTypeExtendedEchoRequest:
var n int
for i := range exts {
switch exts[i].(type) {
case *InterfaceIdent:
n++
case *RawExtension:
default:
return false
}
}
// Not a single InterfaceIdent object or a combo of
// RawExtension and InterfaceIdent objects is not
// allowed.
if n == 1 && len(exts) > 1 {
return false
}
return true
default:
return false
}
}
// A RawExtension represents a raw extension.
//
// A raw extension is excluded from message processing and can be used
// to construct applications such as protocol conformance testing.
type RawExtension struct {
Data []byte // data
}
// Len implements the Len method of Extension interface.
func (p *RawExtension) Len(proto int) int {
if p == nil {
return 0
}
return len(p.Data)
}
// Marshal implements the Marshal method of Extension interface.
func (p *RawExtension) Marshal(proto int) ([]byte, error) {
return p.Data, nil
}
+75
View File
@@ -0,0 +1,75 @@
// 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.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
package icmp
import (
"net"
"strconv"
"syscall"
)
func sockaddr(family int, address string) (syscall.Sockaddr, error) {
switch family {
case syscall.AF_INET:
a, err := net.ResolveIPAddr("ip4", address)
if err != nil {
return nil, err
}
if len(a.IP) == 0 {
a.IP = net.IPv4zero
}
if a.IP = a.IP.To4(); a.IP == nil {
return nil, net.InvalidAddrError("non-ipv4 address")
}
sa := &syscall.SockaddrInet4{}
copy(sa.Addr[:], a.IP)
return sa, nil
case syscall.AF_INET6:
a, err := net.ResolveIPAddr("ip6", address)
if err != nil {
return nil, err
}
if len(a.IP) == 0 {
a.IP = net.IPv6unspecified
}
if a.IP.Equal(net.IPv4zero) {
a.IP = net.IPv6unspecified
}
if a.IP = a.IP.To16(); a.IP == nil || a.IP.To4() != nil {
return nil, net.InvalidAddrError("non-ipv6 address")
}
sa := &syscall.SockaddrInet6{ZoneId: zoneToUint32(a.Zone)}
copy(sa.Addr[:], a.IP)
return sa, nil
default:
return nil, net.InvalidAddrError("unexpected family")
}
}
func zoneToUint32(zone string) uint32 {
if zone == "" {
return 0
}
if ifi, err := net.InterfaceByName(zone); err == nil {
return uint32(ifi.Index)
}
n, err := strconv.Atoi(zone)
if err != nil {
return 0
}
return uint32(n)
}
func last(s string, b byte) int {
i := len(s)
for i--; i >= 0; i-- {
if s[i] == b {
break
}
}
return i
}
+322
View File
@@ -0,0 +1,322 @@
// Copyright 2015 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 icmp
import (
"encoding/binary"
"net"
"strings"
"golang.org/x/net/internal/iana"
)
const (
classInterfaceInfo = 2
)
const (
attrMTU = 1 << iota
attrName
attrIPAddr
attrIfIndex
)
// An InterfaceInfo represents interface and next-hop identification.
type InterfaceInfo struct {
Class int // extension object class number
Type int // extension object sub-type
Interface *net.Interface
Addr *net.IPAddr
}
func (ifi *InterfaceInfo) nameLen() int {
if len(ifi.Interface.Name) > 63 {
return 64
}
l := 1 + len(ifi.Interface.Name)
return (l + 3) &^ 3
}
func (ifi *InterfaceInfo) attrsAndLen(proto int) (attrs, l int) {
l = 4
if ifi.Interface != nil && ifi.Interface.Index > 0 {
attrs |= attrIfIndex
l += 4
if len(ifi.Interface.Name) > 0 {
attrs |= attrName
l += ifi.nameLen()
}
if ifi.Interface.MTU > 0 {
attrs |= attrMTU
l += 4
}
}
if ifi.Addr != nil {
switch proto {
case iana.ProtocolICMP:
if ifi.Addr.IP.To4() != nil {
attrs |= attrIPAddr
l += 4 + net.IPv4len
}
case iana.ProtocolIPv6ICMP:
if ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil {
attrs |= attrIPAddr
l += 4 + net.IPv6len
}
}
}
return
}
// Len implements the Len method of Extension interface.
func (ifi *InterfaceInfo) Len(proto int) int {
_, l := ifi.attrsAndLen(proto)
return l
}
// Marshal implements the Marshal method of Extension interface.
func (ifi *InterfaceInfo) Marshal(proto int) ([]byte, error) {
attrs, l := ifi.attrsAndLen(proto)
b := make([]byte, l)
if err := ifi.marshal(proto, b, attrs, l); err != nil {
return nil, err
}
return b, nil
}
func (ifi *InterfaceInfo) marshal(proto int, b []byte, attrs, l int) error {
binary.BigEndian.PutUint16(b[:2], uint16(l))
b[2], b[3] = classInterfaceInfo, byte(ifi.Type)
for b = b[4:]; len(b) > 0 && attrs != 0; {
switch {
case attrs&attrIfIndex != 0:
b = ifi.marshalIfIndex(proto, b)
attrs &^= attrIfIndex
case attrs&attrIPAddr != 0:
b = ifi.marshalIPAddr(proto, b)
attrs &^= attrIPAddr
case attrs&attrName != 0:
b = ifi.marshalName(proto, b)
attrs &^= attrName
case attrs&attrMTU != 0:
b = ifi.marshalMTU(proto, b)
attrs &^= attrMTU
}
}
return nil
}
func (ifi *InterfaceInfo) marshalIfIndex(proto int, b []byte) []byte {
binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.Index))
return b[4:]
}
func (ifi *InterfaceInfo) parseIfIndex(b []byte) ([]byte, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
ifi.Interface.Index = int(binary.BigEndian.Uint32(b[:4]))
return b[4:], nil
}
func (ifi *InterfaceInfo) marshalIPAddr(proto int, b []byte) []byte {
switch proto {
case iana.ProtocolICMP:
binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv4))
copy(b[4:4+net.IPv4len], ifi.Addr.IP.To4())
b = b[4+net.IPv4len:]
case iana.ProtocolIPv6ICMP:
binary.BigEndian.PutUint16(b[:2], uint16(iana.AddrFamilyIPv6))
copy(b[4:4+net.IPv6len], ifi.Addr.IP.To16())
b = b[4+net.IPv6len:]
}
return b
}
func (ifi *InterfaceInfo) parseIPAddr(b []byte) ([]byte, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
afi := int(binary.BigEndian.Uint16(b[:2]))
b = b[4:]
switch afi {
case iana.AddrFamilyIPv4:
if len(b) < net.IPv4len {
return nil, errMessageTooShort
}
ifi.Addr.IP = make(net.IP, net.IPv4len)
copy(ifi.Addr.IP, b[:net.IPv4len])
b = b[net.IPv4len:]
case iana.AddrFamilyIPv6:
if len(b) < net.IPv6len {
return nil, errMessageTooShort
}
ifi.Addr.IP = make(net.IP, net.IPv6len)
copy(ifi.Addr.IP, b[:net.IPv6len])
b = b[net.IPv6len:]
}
return b, nil
}
func (ifi *InterfaceInfo) marshalName(proto int, b []byte) []byte {
l := byte(ifi.nameLen())
b[0] = l
copy(b[1:], []byte(ifi.Interface.Name))
return b[l:]
}
func (ifi *InterfaceInfo) parseName(b []byte) ([]byte, error) {
if 4 > len(b) || len(b) < int(b[0]) {
return nil, errMessageTooShort
}
l := int(b[0])
if l%4 != 0 || 4 > l || l > 64 {
return nil, errInvalidExtension
}
var name [63]byte
copy(name[:], b[1:l])
ifi.Interface.Name = strings.Trim(string(name[:]), "\000")
return b[l:], nil
}
func (ifi *InterfaceInfo) marshalMTU(proto int, b []byte) []byte {
binary.BigEndian.PutUint32(b[:4], uint32(ifi.Interface.MTU))
return b[4:]
}
func (ifi *InterfaceInfo) parseMTU(b []byte) ([]byte, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
ifi.Interface.MTU = int(binary.BigEndian.Uint32(b[:4]))
return b[4:], nil
}
func parseInterfaceInfo(b []byte) (Extension, error) {
ifi := &InterfaceInfo{
Class: int(b[2]),
Type: int(b[3]),
}
if ifi.Type&(attrIfIndex|attrName|attrMTU) != 0 {
ifi.Interface = &net.Interface{}
}
if ifi.Type&attrIPAddr != 0 {
ifi.Addr = &net.IPAddr{}
}
attrs := ifi.Type & (attrIfIndex | attrIPAddr | attrName | attrMTU)
for b = b[4:]; len(b) > 0 && attrs != 0; {
var err error
switch {
case attrs&attrIfIndex != 0:
b, err = ifi.parseIfIndex(b)
attrs &^= attrIfIndex
case attrs&attrIPAddr != 0:
b, err = ifi.parseIPAddr(b)
attrs &^= attrIPAddr
case attrs&attrName != 0:
b, err = ifi.parseName(b)
attrs &^= attrName
case attrs&attrMTU != 0:
b, err = ifi.parseMTU(b)
attrs &^= attrMTU
}
if err != nil {
return nil, err
}
}
if ifi.Interface != nil && ifi.Interface.Name != "" && ifi.Addr != nil && ifi.Addr.IP.To16() != nil && ifi.Addr.IP.To4() == nil {
ifi.Addr.Zone = ifi.Interface.Name
}
return ifi, nil
}
const (
classInterfaceIdent = 3
typeInterfaceByName = 1
typeInterfaceByIndex = 2
typeInterfaceByAddress = 3
)
// An InterfaceIdent represents interface identification.
type InterfaceIdent struct {
Class int // extension object class number
Type int // extension object sub-type
Name string // interface name
Index int // interface index
AFI int // address family identifier; see address family numbers in IANA registry
Addr []byte // address
}
// Len implements the Len method of Extension interface.
func (ifi *InterfaceIdent) Len(_ int) int {
switch ifi.Type {
case typeInterfaceByName:
l := len(ifi.Name)
if l > 255 {
l = 255
}
return 4 + (l+3)&^3
case typeInterfaceByIndex:
return 4 + 4
case typeInterfaceByAddress:
return 4 + 4 + (len(ifi.Addr)+3)&^3
default:
return 4
}
}
// Marshal implements the Marshal method of Extension interface.
func (ifi *InterfaceIdent) Marshal(proto int) ([]byte, error) {
b := make([]byte, ifi.Len(proto))
if err := ifi.marshal(proto, b); err != nil {
return nil, err
}
return b, nil
}
func (ifi *InterfaceIdent) marshal(proto int, b []byte) error {
l := ifi.Len(proto)
binary.BigEndian.PutUint16(b[:2], uint16(l))
b[2], b[3] = classInterfaceIdent, byte(ifi.Type)
switch ifi.Type {
case typeInterfaceByName:
copy(b[4:], ifi.Name)
case typeInterfaceByIndex:
binary.BigEndian.PutUint32(b[4:4+4], uint32(ifi.Index))
case typeInterfaceByAddress:
binary.BigEndian.PutUint16(b[4:4+2], uint16(ifi.AFI))
b[4+2] = byte(len(ifi.Addr))
copy(b[4+4:], ifi.Addr)
}
return nil
}
func parseInterfaceIdent(b []byte) (Extension, error) {
ifi := &InterfaceIdent{
Class: int(b[2]),
Type: int(b[3]),
}
switch ifi.Type {
case typeInterfaceByName:
ifi.Name = strings.Trim(string(b[4:]), "\x00")
case typeInterfaceByIndex:
if len(b[4:]) < 4 {
return nil, errInvalidExtension
}
ifi.Index = int(binary.BigEndian.Uint32(b[4 : 4+4]))
case typeInterfaceByAddress:
if len(b[4:]) < 4 {
return nil, errInvalidExtension
}
ifi.AFI = int(binary.BigEndian.Uint16(b[4 : 4+2]))
l := int(b[4+2])
if len(b[4+4:]) < l {
return nil, errInvalidExtension
}
ifi.Addr = make([]byte, l)
copy(ifi.Addr, b[4+4:])
}
return ifi, nil
}
+68
View File
@@ -0,0 +1,68 @@
// 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 icmp
import (
"encoding/binary"
"net"
"runtime"
"golang.org/x/net/ipv4"
)
// freebsdVersion is set in sys_freebsd.go.
// See http://www.freebsd.org/doc/en/books/porters-handbook/freebsd-versions.html.
var freebsdVersion uint32
// ParseIPv4Header returns the IPv4 header of the IPv4 packet that
// triggered an ICMP error message.
// This is found in the Data field of the ICMP error message body.
//
// The provided b must be in the format used by a raw ICMP socket on
// the local system.
// This may differ from the wire format, and the format used by a raw
// IP socket, depending on the system.
//
// To parse an IPv6 header, use ipv6.ParseHeader.
func ParseIPv4Header(b []byte) (*ipv4.Header, error) {
if len(b) < ipv4.HeaderLen {
return nil, errHeaderTooShort
}
hdrlen := int(b[0]&0x0f) << 2
if hdrlen > len(b) {
return nil, errBufferTooShort
}
h := &ipv4.Header{
Version: int(b[0] >> 4),
Len: hdrlen,
TOS: int(b[1]),
ID: int(binary.BigEndian.Uint16(b[4:6])),
FragOff: int(binary.BigEndian.Uint16(b[6:8])),
TTL: int(b[8]),
Protocol: int(b[9]),
Checksum: int(binary.BigEndian.Uint16(b[10:12])),
Src: net.IPv4(b[12], b[13], b[14], b[15]),
Dst: net.IPv4(b[16], b[17], b[18], b[19]),
}
switch runtime.GOOS {
case "darwin", "ios":
h.TotalLen = int(binary.NativeEndian.Uint16(b[2:4]))
case "freebsd":
if freebsdVersion >= 1000000 {
h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
} else {
h.TotalLen = int(binary.NativeEndian.Uint16(b[2:4]))
}
default:
h.TotalLen = int(binary.BigEndian.Uint16(b[2:4]))
}
h.Flags = ipv4.HeaderFlags(h.FragOff&0xe000) >> 13
h.FragOff = h.FragOff & 0x1fff
if hdrlen-ipv4.HeaderLen > 0 {
h.Options = make([]byte, hdrlen-ipv4.HeaderLen)
copy(h.Options, b[ipv4.HeaderLen:])
}
return h, nil
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2013 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 icmp
import (
"net"
"golang.org/x/net/internal/iana"
)
const ipv6PseudoHeaderLen = 2*net.IPv6len + 8
// IPv6PseudoHeader returns an IPv6 pseudo header for checksum
// calculation.
func IPv6PseudoHeader(src, dst net.IP) []byte {
b := make([]byte, ipv6PseudoHeaderLen)
copy(b, src.To16())
copy(b[net.IPv6len:], dst.To16())
b[len(b)-1] = byte(iana.ProtocolIPv6ICMP)
return b
}
+105
View File
@@ -0,0 +1,105 @@
// 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.
//go:build aix || darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris || windows
package icmp
import (
"net"
"os"
"runtime"
"syscall"
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
const sysIP_STRIPHDR = 0x17 // for now only darwin supports this option
// ListenPacket listens for incoming ICMP packets addressed to
// address. See net.Dial for the syntax of address.
//
// For non-privileged datagram-oriented ICMP endpoints, network must
// be "udp4" or "udp6". The endpoint allows to read, write a few
// limited ICMP messages such as echo request and echo reply.
// Currently only Darwin and Linux support this.
//
// Examples:
//
// ListenPacket("udp4", "192.168.0.1")
// ListenPacket("udp4", "0.0.0.0")
// ListenPacket("udp6", "fe80::1%en0")
// ListenPacket("udp6", "::")
//
// For privileged raw ICMP endpoints, network must be "ip4" or "ip6"
// followed by a colon and an ICMP protocol number or name.
//
// Examples:
//
// ListenPacket("ip4:icmp", "192.168.0.1")
// ListenPacket("ip4:1", "0.0.0.0")
// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0")
// ListenPacket("ip6:58", "::")
func ListenPacket(network, address string) (*PacketConn, error) {
var family, proto int
switch network {
case "udp4":
family, proto = syscall.AF_INET, iana.ProtocolICMP
case "udp6":
family, proto = syscall.AF_INET6, iana.ProtocolIPv6ICMP
default:
i := last(network, ':')
if i < 0 {
i = len(network)
}
switch network[:i] {
case "ip4":
proto = iana.ProtocolICMP
case "ip6":
proto = iana.ProtocolIPv6ICMP
}
}
var cerr error
var c net.PacketConn
switch family {
case syscall.AF_INET, syscall.AF_INET6:
s, err := syscall.Socket(family, syscall.SOCK_DGRAM, proto)
if err != nil {
return nil, os.NewSyscallError("socket", err)
}
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && family == syscall.AF_INET {
if err := syscall.SetsockoptInt(s, iana.ProtocolIP, sysIP_STRIPHDR, 1); err != nil {
syscall.Close(s)
return nil, os.NewSyscallError("setsockopt", err)
}
}
sa, err := sockaddr(family, address)
if err != nil {
syscall.Close(s)
return nil, err
}
if err := syscall.Bind(s, sa); err != nil {
syscall.Close(s)
return nil, os.NewSyscallError("bind", err)
}
f := os.NewFile(uintptr(s), "datagram-oriented icmp")
c, cerr = net.FilePacketConn(f)
f.Close()
default:
c, cerr = net.ListenPacket(network, address)
}
if cerr != nil {
return nil, cerr
}
switch proto {
case iana.ProtocolICMP:
return &PacketConn{c: c, p4: ipv4.NewPacketConn(c)}, nil
case iana.ProtocolIPv6ICMP:
return &PacketConn{c: c, p6: ipv6.NewPacketConn(c)}, nil
default:
return &PacketConn{c: c}, nil
}
}
+35
View File
@@ -0,0 +1,35 @@
// 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.
//go:build !aix && !darwin && !dragonfly && !freebsd && !linux && !netbsd && !openbsd && !solaris && !windows
package icmp
// ListenPacket listens for incoming ICMP packets addressed to
// address. See net.Dial for the syntax of address.
//
// For non-privileged datagram-oriented ICMP endpoints, network must
// be "udp4" or "udp6". The endpoint allows to read, write a few
// limited ICMP messages such as echo request and echo reply.
// Currently only Darwin and Linux support this.
//
// Examples:
//
// ListenPacket("udp4", "192.168.0.1")
// ListenPacket("udp4", "0.0.0.0")
// ListenPacket("udp6", "fe80::1%en0")
// ListenPacket("udp6", "::")
//
// For privileged raw ICMP endpoints, network must be "ip4" or "ip6"
// followed by a colon and an ICMP protocol number or name.
//
// Examples:
//
// ListenPacket("ip4:icmp", "192.168.0.1")
// ListenPacket("ip4:1", "0.0.0.0")
// ListenPacket("ip6:ipv6-icmp", "fe80::1%en0")
// ListenPacket("ip6:58", "::")
func ListenPacket(network, address string) (*PacketConn, error) {
return nil, errNotImplemented
}
+162
View File
@@ -0,0 +1,162 @@
// Copyright 2012 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 icmp provides basic functions for the manipulation of
// messages used in the Internet Control Message Protocols,
// ICMPv4 and ICMPv6.
//
// ICMPv4 and ICMPv6 are defined in RFC 792 and RFC 4443.
// Multi-part message support for ICMP is defined in RFC 4884.
// ICMP extensions for MPLS are defined in RFC 4950.
// ICMP extensions for interface and next-hop identification are
// defined in RFC 5837.
// PROBE: A utility for probing interfaces is defined in RFC 8335.
package icmp // import "golang.org/x/net/icmp"
import (
"encoding/binary"
"errors"
"net"
"runtime"
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// BUG(mikio): This package is not implemented on JS, NaCl and Plan 9.
var (
errInvalidConn = errors.New("invalid connection")
errInvalidProtocol = errors.New("invalid protocol")
errMessageTooShort = errors.New("message too short")
errHeaderTooShort = errors.New("header too short")
errBufferTooShort = errors.New("buffer too short")
errInvalidBody = errors.New("invalid body")
errNoExtension = errors.New("no extension")
errInvalidExtension = errors.New("invalid extension")
errNotImplemented = errors.New("not implemented on " + runtime.GOOS + "/" + runtime.GOARCH)
)
func checksum(b []byte) uint16 {
csumcv := len(b) - 1 // checksum coverage
s := uint32(0)
for i := 0; i < csumcv; i += 2 {
s += uint32(b[i+1])<<8 | uint32(b[i])
}
if csumcv&1 == 0 {
s += uint32(b[csumcv])
}
s = s>>16 + s&0xffff
s = s + s>>16
return ^uint16(s)
}
// A Type represents an ICMP message type.
type Type interface {
Protocol() int
}
// A Message represents an ICMP message.
type Message struct {
Type Type // type, either ipv4.ICMPType or ipv6.ICMPType
Code int // code
Checksum int // checksum
Body MessageBody // body
}
// Marshal returns the binary encoding of the ICMP message m.
//
// For an ICMPv4 message, the returned message always contains the
// calculated checksum field.
//
// For an ICMPv6 message, the returned message contains the calculated
// checksum field when psh is not nil, otherwise the kernel will
// compute the checksum field during the message transmission.
// When psh is not nil, it must be the pseudo header for IPv6.
func (m *Message) Marshal(psh []byte) ([]byte, error) {
var mtype byte
switch typ := m.Type.(type) {
case ipv4.ICMPType:
mtype = byte(typ)
case ipv6.ICMPType:
mtype = byte(typ)
default:
return nil, errInvalidProtocol
}
b := []byte{mtype, byte(m.Code), 0, 0}
proto := m.Type.Protocol()
if proto == iana.ProtocolIPv6ICMP && psh != nil {
b = append(psh, b...)
}
if m.Body != nil && m.Body.Len(proto) != 0 {
mb, err := m.Body.Marshal(proto)
if err != nil {
return nil, err
}
b = append(b, mb...)
}
if proto == iana.ProtocolIPv6ICMP {
if psh == nil { // cannot calculate checksum here
return b, nil
}
off, l := 2*net.IPv6len, len(b)-len(psh)
binary.BigEndian.PutUint32(b[off:off+4], uint32(l))
}
s := checksum(b)
// Place checksum back in header; using ^= avoids the
// assumption the checksum bytes are zero.
b[len(psh)+2] ^= byte(s)
b[len(psh)+3] ^= byte(s >> 8)
return b[len(psh):], nil
}
var parseFns = map[Type]func(int, Type, []byte) (MessageBody, error){
ipv4.ICMPTypeDestinationUnreachable: parseDstUnreach,
ipv4.ICMPTypeTimeExceeded: parseTimeExceeded,
ipv4.ICMPTypeParameterProblem: parseParamProb,
ipv4.ICMPTypeEcho: parseEcho,
ipv4.ICMPTypeEchoReply: parseEcho,
ipv4.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
ipv4.ICMPTypeExtendedEchoReply: parseExtendedEchoReply,
ipv6.ICMPTypeDestinationUnreachable: parseDstUnreach,
ipv6.ICMPTypePacketTooBig: parsePacketTooBig,
ipv6.ICMPTypeTimeExceeded: parseTimeExceeded,
ipv6.ICMPTypeParameterProblem: parseParamProb,
ipv6.ICMPTypeEchoRequest: parseEcho,
ipv6.ICMPTypeEchoReply: parseEcho,
ipv6.ICMPTypeExtendedEchoRequest: parseExtendedEchoRequest,
ipv6.ICMPTypeExtendedEchoReply: parseExtendedEchoReply,
}
// ParseMessage parses b as an ICMP message.
// The provided proto must be either the ICMPv4 or ICMPv6 protocol
// number.
func ParseMessage(proto int, b []byte) (*Message, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
var err error
m := &Message{Code: int(b[1]), Checksum: int(binary.BigEndian.Uint16(b[2:4]))}
switch proto {
case iana.ProtocolICMP:
m.Type = ipv4.ICMPType(b[0])
case iana.ProtocolIPv6ICMP:
m.Type = ipv6.ICMPType(b[0])
default:
return nil, errInvalidProtocol
}
if fn, ok := parseFns[m.Type]; !ok {
m.Body, err = parseRawBody(proto, b[4:])
} else {
m.Body, err = fn(proto, m.Type, b[4:])
}
if err != nil {
return nil, err
}
return m, nil
}
+52
View File
@@ -0,0 +1,52 @@
// Copyright 2012 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 icmp
// A MessageBody represents an ICMP message body.
type MessageBody interface {
// Len returns the length of ICMP message body.
// The provided proto must be either the ICMPv4 or ICMPv6
// protocol number.
Len(proto int) int
// Marshal returns the binary encoding of ICMP message body.
// The provided proto must be either the ICMPv4 or ICMPv6
// protocol number.
Marshal(proto int) ([]byte, error)
}
// A RawBody represents a raw message body.
//
// A raw message body is excluded from message processing and can be
// used to construct applications such as protocol conformance
// testing.
type RawBody struct {
Data []byte // data
}
// Len implements the Len method of MessageBody interface.
func (p *RawBody) Len(proto int) int {
if p == nil {
return 0
}
return len(p.Data)
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *RawBody) Marshal(proto int) ([]byte, error) {
return p.Data, nil
}
// parseRawBody parses b as an ICMP message body.
func parseRawBody(proto int, b []byte) (MessageBody, error) {
p := &RawBody{Data: make([]byte, len(b))}
copy(p.Data, b)
return p, nil
}
// A DefaultMessageBody represents the default message body.
//
// Deprecated: Use RawBody instead.
type DefaultMessageBody = RawBody
+77
View File
@@ -0,0 +1,77 @@
// Copyright 2015 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 icmp
import "encoding/binary"
// MPLSLabel represents an MPLS label stack entry.
type MPLSLabel struct {
Label int // label value
TC int // traffic class; formerly experimental use
S bool // bottom of stack
TTL int // time to live
}
const (
classMPLSLabelStack = 1
typeIncomingMPLSLabelStack = 1
)
// MPLSLabelStack represents an MPLS label stack.
type MPLSLabelStack struct {
Class int // extension object class number
Type int // extension object sub-type
Labels []MPLSLabel
}
// Len implements the Len method of Extension interface.
func (ls *MPLSLabelStack) Len(proto int) int {
return 4 + (4 * len(ls.Labels))
}
// Marshal implements the Marshal method of Extension interface.
func (ls *MPLSLabelStack) Marshal(proto int) ([]byte, error) {
b := make([]byte, ls.Len(proto))
if err := ls.marshal(proto, b); err != nil {
return nil, err
}
return b, nil
}
func (ls *MPLSLabelStack) marshal(proto int, b []byte) error {
l := ls.Len(proto)
binary.BigEndian.PutUint16(b[:2], uint16(l))
b[2], b[3] = classMPLSLabelStack, typeIncomingMPLSLabelStack
off := 4
for _, ll := range ls.Labels {
b[off], b[off+1], b[off+2] = byte(ll.Label>>12), byte(ll.Label>>4&0xff), byte(ll.Label<<4&0xf0)
b[off+2] |= byte(ll.TC << 1 & 0x0e)
if ll.S {
b[off+2] |= 0x1
}
b[off+3] = byte(ll.TTL)
off += 4
}
return nil
}
func parseMPLSLabelStack(b []byte) (Extension, error) {
ls := &MPLSLabelStack{
Class: int(b[2]),
Type: int(b[3]),
}
for b = b[4:]; len(b) >= 4; b = b[4:] {
ll := MPLSLabel{
Label: int(b[0])<<12 | int(b[1])<<4 | int(b[2])>>4,
TC: int(b[2]&0x0e) >> 1,
TTL: int(b[3]),
}
if b[2]&0x1 != 0 {
ll.S = true
}
ls.Labels = append(ls.Labels, ll)
}
return ls, nil
}
+129
View File
@@ -0,0 +1,129 @@
// Copyright 2015 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 icmp
import "golang.org/x/net/internal/iana"
// multipartMessageBodyDataLen takes b as an original datagram and
// exts as extensions, and returns a required length for message body
// and a required length for a padded original datagram in wire
// format.
func multipartMessageBodyDataLen(proto int, withOrigDgram bool, b []byte, exts []Extension) (bodyLen, dataLen int) {
bodyLen = 4 // length of leading octets
var extLen int
var rawExt bool // raw extension may contain an empty object
for _, ext := range exts {
extLen += ext.Len(proto)
if _, ok := ext.(*RawExtension); ok {
rawExt = true
}
}
if extLen > 0 && withOrigDgram {
dataLen = multipartMessageOrigDatagramLen(proto, b)
} else {
dataLen = len(b)
}
if extLen > 0 || rawExt {
bodyLen += 4 // length of extension header
}
bodyLen += dataLen + extLen
return bodyLen, dataLen
}
// multipartMessageOrigDatagramLen takes b as an original datagram,
// and returns a required length for a padded original datagram in wire
// format.
func multipartMessageOrigDatagramLen(proto int, b []byte) int {
roundup := func(b []byte, align int) int {
// According to RFC 4884, the padded original datagram
// field must contain at least 128 octets.
if len(b) < 128 {
return 128
}
r := len(b)
return (r + align - 1) &^ (align - 1)
}
switch proto {
case iana.ProtocolICMP:
return roundup(b, 4)
case iana.ProtocolIPv6ICMP:
return roundup(b, 8)
default:
return len(b)
}
}
// marshalMultipartMessageBody takes data as an original datagram and
// exts as extesnsions, and returns a binary encoding of message body.
// It can be used for non-multipart message bodies when exts is nil.
func marshalMultipartMessageBody(proto int, withOrigDgram bool, data []byte, exts []Extension) ([]byte, error) {
bodyLen, dataLen := multipartMessageBodyDataLen(proto, withOrigDgram, data, exts)
b := make([]byte, bodyLen)
copy(b[4:], data)
if len(exts) > 0 {
b[4+dataLen] = byte(extensionVersion << 4)
off := 4 + dataLen + 4 // leading octets, data, extension header
for _, ext := range exts {
switch ext := ext.(type) {
case *MPLSLabelStack:
if err := ext.marshal(proto, b[off:]); err != nil {
return nil, err
}
off += ext.Len(proto)
case *InterfaceInfo:
attrs, l := ext.attrsAndLen(proto)
if err := ext.marshal(proto, b[off:], attrs, l); err != nil {
return nil, err
}
off += ext.Len(proto)
case *InterfaceIdent:
if err := ext.marshal(proto, b[off:]); err != nil {
return nil, err
}
off += ext.Len(proto)
case *RawExtension:
copy(b[off:], ext.Data)
off += ext.Len(proto)
}
}
s := checksum(b[4+dataLen:])
b[4+dataLen+2] ^= byte(s)
b[4+dataLen+3] ^= byte(s >> 8)
if withOrigDgram {
switch proto {
case iana.ProtocolICMP:
b[1] = byte(dataLen / 4)
case iana.ProtocolIPv6ICMP:
b[0] = byte(dataLen / 8)
}
}
}
return b, nil
}
// parseMultipartMessageBody parses b as either a non-multipart
// message body or a multipart message body.
func parseMultipartMessageBody(proto int, typ Type, b []byte) ([]byte, []Extension, error) {
var l int
switch proto {
case iana.ProtocolICMP:
l = 4 * int(b[1])
case iana.ProtocolIPv6ICMP:
l = 8 * int(b[0])
}
if len(b) == 4 {
return nil, nil, nil
}
exts, l, err := parseExtensions(typ, b[4:], l)
if err != nil {
l = len(b) - 4
}
var data []byte
if l > 0 {
data = make([]byte, l)
copy(data, b[4:])
}
return data, exts, nil
}
+43
View File
@@ -0,0 +1,43 @@
// 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 icmp
import "encoding/binary"
// A PacketTooBig represents an ICMP packet too big message body.
type PacketTooBig struct {
MTU int // maximum transmission unit of the nexthop link
Data []byte // data, known as original datagram field
}
// Len implements the Len method of MessageBody interface.
func (p *PacketTooBig) Len(proto int) int {
if p == nil {
return 0
}
return 4 + len(p.Data)
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *PacketTooBig) Marshal(proto int) ([]byte, error) {
b := make([]byte, 4+len(p.Data))
binary.BigEndian.PutUint32(b[:4], uint32(p.MTU))
copy(b[4:], p.Data)
return b, nil
}
// parsePacketTooBig parses b as an ICMP packet too big message body.
func parsePacketTooBig(proto int, _ Type, b []byte) (MessageBody, error) {
bodyLen := len(b)
if bodyLen < 4 {
return nil, errMessageTooShort
}
p := &PacketTooBig{MTU: int(binary.BigEndian.Uint32(b[:4]))}
if bodyLen > 4 {
p.Data = make([]byte, bodyLen-4)
copy(p.Data, b[4:])
}
return p, nil
}
+72
View File
@@ -0,0 +1,72 @@
// 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 icmp
import (
"encoding/binary"
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
)
// A ParamProb represents an ICMP parameter problem message body.
type ParamProb struct {
Pointer uintptr // offset within the data where the error was detected
Data []byte // data, known as original datagram field
Extensions []Extension // extensions
}
// Len implements the Len method of MessageBody interface.
func (p *ParamProb) Len(proto int) int {
if p == nil {
return 0
}
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
return l
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *ParamProb) Marshal(proto int) ([]byte, error) {
switch proto {
case iana.ProtocolICMP:
if !validExtensions(ipv4.ICMPTypeParameterProblem, p.Extensions) {
return nil, errInvalidExtension
}
b, err := marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
if err != nil {
return nil, err
}
b[0] = byte(p.Pointer)
return b, nil
case iana.ProtocolIPv6ICMP:
b := make([]byte, p.Len(proto))
binary.BigEndian.PutUint32(b[:4], uint32(p.Pointer))
copy(b[4:], p.Data)
return b, nil
default:
return nil, errInvalidProtocol
}
}
// parseParamProb parses b as an ICMP parameter problem message body.
func parseParamProb(proto int, typ Type, b []byte) (MessageBody, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
p := &ParamProb{}
if proto == iana.ProtocolIPv6ICMP {
p.Pointer = uintptr(binary.BigEndian.Uint32(b[:4]))
p.Data = make([]byte, len(b)-4)
copy(p.Data, b[4:])
return p, nil
}
p.Pointer = uintptr(b[0])
var err error
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
if err != nil {
return nil, err
}
return p, nil
}
+11
View File
@@ -0,0 +1,11 @@
// 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 icmp
import "syscall"
func init() {
freebsdVersion, _ = syscall.SysctlUint32("kern.osreldate")
}
+57
View File
@@ -0,0 +1,57 @@
// 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 icmp
import (
"golang.org/x/net/internal/iana"
"golang.org/x/net/ipv4"
"golang.org/x/net/ipv6"
)
// A TimeExceeded represents an ICMP time exceeded message body.
type TimeExceeded struct {
Data []byte // data, known as original datagram field
Extensions []Extension // extensions
}
// Len implements the Len method of MessageBody interface.
func (p *TimeExceeded) Len(proto int) int {
if p == nil {
return 0
}
l, _ := multipartMessageBodyDataLen(proto, true, p.Data, p.Extensions)
return l
}
// Marshal implements the Marshal method of MessageBody interface.
func (p *TimeExceeded) Marshal(proto int) ([]byte, error) {
var typ Type
switch proto {
case iana.ProtocolICMP:
typ = ipv4.ICMPTypeTimeExceeded
case iana.ProtocolIPv6ICMP:
typ = ipv6.ICMPTypeTimeExceeded
default:
return nil, errInvalidProtocol
}
if !validExtensions(typ, p.Extensions) {
return nil, errInvalidExtension
}
return marshalMultipartMessageBody(proto, true, p.Data, p.Extensions)
}
// parseTimeExceeded parses b as an ICMP time exceeded message body.
func parseTimeExceeded(proto int, typ Type, b []byte) (MessageBody, error) {
if len(b) < 4 {
return nil, errMessageTooShort
}
p := &TimeExceeded{}
var err error
p.Data, p.Extensions, err = parseMultipartMessageBody(proto, typ, b)
if err != nil {
return nil, err
}
return p, nil
}
+1
View File
@@ -175,6 +175,7 @@ golang.org/x/net/bpf
golang.org/x/net/html
golang.org/x/net/html/atom
golang.org/x/net/html/charset
golang.org/x/net/icmp
golang.org/x/net/internal/iana
golang.org/x/net/internal/socket
golang.org/x/net/ipv4