tailscale/types/ipproto/ipproto.go
James Tucker c1ef55249a types/ipproto: import and test string parsing for ipproto
IPProto has been being converted to and from string formats in multiple
locations with variations in behavior. TextMarshaller and JSONMarshaller
implementations are now added, along with defined accepted and preferred
formats to centralize the logic into a single cross compatible
implementation.

Updates tailscale/corp#15043
Fixes tailscale/corp#15141

Signed-off-by: James Tucker <james@tailscale.com>
2023-10-11 18:56:33 -07:00

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