summaryrefslogtreecommitdiffhomepage
path: root/types/ipproto/ipproto.go
blob: a08985b3aba265acdcb8d4e525394e68c9ae5ad6 (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
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause

// Package ipproto contains IP Protocol constants.
package ipproto

import (
	"fmt"
	"strconv"

	"tailscale.com/util/nocasemaps"
	"tailscale.com/util/vizerror"
)

// Version describes the IP address version.
type Version uint8

// Valid Version values.
const (
	Version4 = 4
	Version6 = 6
)

func (p Version) String() string {
	switch p {
	case Version4:
		return "IPv4"
	case Version6:
		return "IPv6"
	default:
		return fmt.Sprintf("Version-%d", int(p))
	}
}

// Proto is an IP subprotocol as defined by the IANA protocol
// numbers list
// (https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml),
// or the special values Unknown or Fragment.
type Proto uint8

const (
	// Unknown represents an unknown or unsupported protocol; it's
	// deliberately the zero value. Strictly speaking the zero
	// value is IPv6 hop-by-hop extensions, but we don't support
	// those, so this is still technically correct.
	Unknown Proto = 0x00

	// Values from the IANA registry.
	ICMPv4 Proto = 0x01
	IGMP   Proto = 0x02
	ICMPv6 Proto = 0x3a
	TCP    Proto = 0x06
	UDP    Proto = 0x11
	DCCP   Proto = 0x21
	GRE    Proto = 0x2f
	SCTP   Proto = 0x84

	// TSMP is the Tailscale Message Protocol (our ICMP-ish
	// thing), an IP protocol used only between Tailscale nodes
	// (still encrypted by WireGuard) that communicates why things
	// failed, etc.
	//
	// Proto number 99 is reserved for "any private encryption
	// scheme". We never accept these from the host OS stack nor
	// send them to the host network stack. It's only used between
	// nodes.
	TSMP Proto = 99

	// Fragment represents any non-first IP fragment, for which we
	// don't have the sub-protocol header (and therefore can't
	// figure out what the sub-protocol is).
	//
	// 0xFF is reserved in the IANA registry, so we steal it for
	// internal use.
	Fragment Proto = 0xFF
)

// Deprecated: use MarshalText instead.
func (p Proto) String() string {
	switch p {
	case Unknown:
		return "Unknown"
	case Fragment:
		return "Frag"
	case ICMPv4:
		return "ICMPv4"
	case IGMP:
		return "IGMP"
	case ICMPv6:
		return "ICMPv6"
	case UDP:
		return "UDP"
	case TCP:
		return "TCP"
	case SCTP:
		return "SCTP"
	case TSMP:
		return "TSMP"
	case GRE:
		return "GRE"
	case DCCP:
		return "DCCP"
	default:
		return fmt.Sprintf("IPProto-%d", int(p))
	}
}

// Prefer names from
// https://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml
// unless otherwise noted.
var (
	// preferredNames is the set of protocol names that re produced by
	// MarshalText, and are the preferred representation.
	preferredNames = map[Proto]string{
		51:     "ah",
		DCCP:   "dccp",
		8:      "egp",
		50:     "esp",
		47:     "gre",
		ICMPv4: "icmp",
		IGMP:   "igmp",
		9:      "igp",
		4:      "ipv4",
		ICMPv6: "ipv6-icmp",
		SCTP:   "sctp",
		TCP:    "tcp",
		UDP:    "udp",
	}

	// acceptedNames is the set of protocol names that are accepted by
	// UnmarshalText.
	acceptedNames = map[string]Proto{
		"ah":        51,
		"dccp":      DCCP,
		"egp":       8,
		"esp":       50,
		"gre":       47,
		"icmp":      ICMPv4,
		"icmpv4":    ICMPv4,
		"icmpv6":    ICMPv6,
		"igmp":      IGMP,
		"igp":       9,
		"ip-in-ip":  4, // IANA says "ipv4"; Wikipedia/popular use says "ip-in-ip"
		"ipv4":      4,
		"ipv6-icmp": ICMPv6,
		"sctp":      SCTP,
		"tcp":       TCP,
		"tsmp":      TSMP,
		"udp":       UDP,
	}
)

// UnmarshalText implements encoding.TextUnmarshaler. If the input is empty, p
// is set to 0. If an error occurs, p is unchanged.
func (p *Proto) UnmarshalText(b []byte) error {
	if len(b) == 0 {
		*p = 0
		return nil
	}

	if u, err := strconv.ParseUint(string(b), 10, 8); err == nil {
		*p = Proto(u)
		return nil
	}

	if newP, ok := nocasemaps.GetOk(acceptedNames, string(b)); ok {
		*p = newP
		return nil
	}

	return vizerror.Errorf("proto name %q not known; use protocol number 0-255", b)
}

// MarshalText implements encoding.TextMarshaler.
func (p Proto) MarshalText() ([]byte, error) {
	if s, ok := preferredNames[p]; ok {
		return []byte(s), nil
	}
	return []byte(strconv.Itoa(int(p))), nil
}

// MarshalJSON implements json.Marshaler.
func (p Proto) MarshalJSON() ([]byte, error) {
	return []byte(strconv.Itoa(int(p))), nil
}

// UnmarshalJSON implements json.Unmarshaler. If the input is empty, p is set to
// 0. If an error occurs, p is unchanged. The input must be a JSON number or an
// accepted string name.
func (p *Proto) UnmarshalJSON(b []byte) error {
	if len(b) == 0 {
		*p = 0
		return nil
	}
	if b[0] == '"' {
		b = b[1 : len(b)-1]
	}
	return p.UnmarshalText(b)
}