summaryrefslogtreecommitdiffhomepage
path: root/net/packet/icmp6.go
blob: a91db53c9e50c669b3a3d3c30848eb09fb85c955 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

package packet

import (
	"encoding/binary"

	"tailscale.com/types/ipproto"
)

// icmp6HeaderLength is the size of the ICMPv6 packet header, not
// including the outer IP layer or the variable "response data"
// trailer.
const icmp6HeaderLength = 4

// ICMP6Type is an ICMPv6 type, as specified in
// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml
type ICMP6Type uint8

const (
	ICMP6Unreachable  ICMP6Type = 1
	ICMP6PacketTooBig ICMP6Type = 2
	ICMP6TimeExceeded ICMP6Type = 3
	ICMP6ParamProblem ICMP6Type = 4
	ICMP6EchoRequest  ICMP6Type = 128
	ICMP6EchoReply    ICMP6Type = 129
)

func (t ICMP6Type) String() string {
	switch t {
	case ICMP6Unreachable:
		return "Unreachable"
	case ICMP6PacketTooBig:
		return "PacketTooBig"
	case ICMP6TimeExceeded:
		return "TimeExceeded"
	case ICMP6ParamProblem:
		return "ParamProblem"
	case ICMP6EchoRequest:
		return "EchoRequest"
	case ICMP6EchoReply:
		return "EchoReply"
	default:
		return "Unknown"
	}
}

// ICMP6Code is an ICMPv6 code, as specified in
// https://www.iana.org/assignments/icmpv6-parameters/icmpv6-parameters.xhtml
type ICMP6Code uint8

const (
	ICMP6NoCode ICMP6Code = 0
)

// ICMP6Header is an IPv4+ICMPv4 header.
type ICMP6Header struct {
	IP6Header
	Type ICMP6Type
	Code ICMP6Code
}

// Len implements Header.
func (h ICMP6Header) Len() int {
	return h.IP6Header.Len() + icmp6HeaderLength
}

// Marshal implements Header.
func (h ICMP6Header) Marshal(buf []byte) error {
	if len(buf) < h.Len() {
		return errSmallBuffer
	}
	if len(buf) > maxPacketLength {
		return errLargePacket
	}
	// The caller does not need to set this.
	h.IPProto = ipproto.ICMPv6

	h.IP6Header.Marshal(buf)

	const o = ip6HeaderLength // start offset of ICMPv6 header
	buf[o+0] = uint8(h.Type)
	buf[o+1] = uint8(h.Code)
	buf[o+2] = 0 // checksum, to be filled in later
	buf[o+3] = 0 // checksum, to be filled in later
	return nil
}

// ToResponse implements Header. TODO: it doesn't implement it
// correctly, instead it statically generates an ICMP Echo Reply
// packet.
func (h *ICMP6Header) ToResponse() {
	// TODO: this doesn't implement ToResponse correctly, as it
	// assumes the ICMP request type.
	h.Type = ICMP6EchoReply
	h.Code = ICMP6NoCode
	h.IP6Header.ToResponse()
}

// WriteChecksum implements HeaderChecksummer, writing just the checksum bytes
// into the otherwise fully marshaled ICMP6 packet p (which should include the
// IPv6 header, ICMPv6 header, and payload).
func (h ICMP6Header) WriteChecksum(p []byte) {
	const payOff = ip6HeaderLength + icmp6HeaderLength
	xsum := icmp6Checksum(p[ip6HeaderLength:payOff], h.Src.As16(), h.Dst.As16(), p[payOff:])
	binary.BigEndian.PutUint16(p[ip6HeaderLength+2:], xsum)
}

// Adapted from gVisor:

// icmp6Checksum calculates the ICMP checksum over the provided ICMPv6
// header (without the IPv6 header), IPv6 src/dst addresses and the
// payload.
//
// The header's existing checksum must be zeroed.
func icmp6Checksum(header []byte, src, dst [16]byte, payload []byte) uint16 {
	// Calculate the IPv6 pseudo-header upper-layer checksum.
	xsum := checksumBytes(src[:], 0)
	xsum = checksumBytes(dst[:], xsum)

	var scratch [4]byte
	binary.BigEndian.PutUint32(scratch[:], uint32(len(header)+len(payload)))
	xsum = checksumBytes(scratch[:], xsum)
	xsum = checksumBytes(append(scratch[:0], 0, 0, 0, uint8(ipproto.ICMPv6)), xsum)
	xsum = checksumBytes(payload, xsum)

	var hdrz [icmp6HeaderLength]byte
	copy(hdrz[:], header)
	// Zero out the header.
	hdrz[2] = 0
	hdrz[3] = 0
	xsum = ^checksumBytes(hdrz[:], xsum)
	return xsum
}

// checksumCombine combines the two uint16 to form their
// checksum. This is done by adding them and the carry.
//
// Note that checksum a must have been computed on an even number of
// bytes.
func checksumCombine(a, b uint16) uint16 {
	v := uint32(a) + uint32(b)
	return uint16(v + v>>16)
}

// checksumBytes calculates the checksum (as defined in RFC 1071) of
// the bytes in buf.
//
// The initial checksum must have been computed on an even number of bytes.
func checksumBytes(buf []byte, initial uint16) uint16 {
	v := uint32(initial)

	odd := len(buf)%2 == 1
	if odd {
		v += uint32(buf[0])
		buf = buf[1:]
	}

	n := len(buf)
	odd = n&1 != 0
	if odd {
		n--
		v += uint32(buf[n]) << 8
	}

	for i := 0; i < n; i += 2 {
		v += (uint32(buf[i]) << 8) + uint32(buf[i+1])
	}

	return checksumCombine(uint16(v), uint16(v>>16))
}