tailscale/types/ipproto/ipproto.go

200 lines
4.5 KiB
Go

// Copyright (c) Tailscale Inc & AUTHORS
// 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)
}