github.com/cilium/cilium@v1.16.2/pkg/bgpv1/agent/annotations.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package agent 5 6 import ( 7 "errors" 8 "fmt" 9 "net/netip" 10 "strconv" 11 "strings" 12 13 "github.com/cilium/cilium/pkg/annotation" 14 ) 15 16 // ErrNotVRouterAnno is an error returned from parseAnnotation() when the 17 // the casted string is not a `cilium.io/bgp-virtual-router` annotation 18 type ErrNotVRouterAnno struct { 19 a string 20 } 21 22 func (e ErrNotVRouterAnno) Error() string { 23 return "annotation " + e.a + " is not a valid cilium.io/bgp-virtual-router annotation" 24 } 25 26 // ErrNoASNAnno is an error returned from parseAnnotation() when the bgp-virtual-router 27 // annotation does not include a local ASN. 28 type ErrNoASNAnno struct { 29 a string 30 } 31 32 func (e ErrNoASNAnno) Error() string { 33 return "annotation " + e.a + " provides no asn" 34 } 35 36 // ErrASN is an error returned from parseAnnotation() when the bgp-virtual-router 37 // annotation includes an ASN that cannot be parsed into an 38 type ErrASNAnno struct { 39 err string 40 asn string 41 anno string 42 } 43 44 func (e ErrASNAnno) Error() string { 45 return "ASN" + e.asn + " in annotation " + e.anno + " cannot be parsed into integer: " + e.err 46 } 47 48 // ErrAttrib is an error returned from parseAnnotation() when an attribute is 49 // provided but its value is malformed. 50 type ErrAttrib struct { 51 anno string 52 attr string 53 err string 54 } 55 56 func (e ErrAttrib) Error() string { 57 return "annotation " + e.anno + " failed to parse attribute " + e.attr + ":" + e.err 58 } 59 60 // The BGP control plane may need some node-specific configuration for 61 // instantiating virtual routers. 62 // 63 // For example, BGP router IDs cannot repeat in a BGP peering topology. 64 // When Cilium cannot generate a unique router ID it will look for a unique 65 // router ID for the virtual router identified by its local ASN. 66 // 67 // We define a set of attributes which can be defined via Node-specific 68 // kubernetes annotations. 69 // 70 // This Kubernetes annotation's syntax is: 71 // `cilium.io/bgp-virtual-router.{asn}="attr1=value1,attr2=value2" 72 // 73 // Where {asn} is replaced by the local ASN of the virtual router. 74 // 75 // Currently supported attributes are: 76 // 77 // router-id=IPv4 (string): when present on a specific node, use this value for 78 // the router ID of the virtual router with local {asn} 79 // local-port=port (int): the local port to listen on for incoming BGP connections 80 type Attributes struct { 81 // The local ASN of the virtual router these Attributes targets. 82 ASN int64 83 // The router ID to use for the virtual router with the above local ASN. 84 RouterID string 85 // The local BGP port to listen on. 86 LocalPort int32 87 } 88 89 // AnnotationMap coorelates a parsed Annotations structure with the local 90 // ASN its annotating. 91 type AnnotationMap map[int64]Attributes 92 93 func (a AnnotationMap) ResolveRouterID(localASN int64) (string, error) { 94 if attr, ok := a[localASN]; ok && attr.RouterID != "" { 95 return attr.RouterID, nil 96 } 97 return "", fmt.Errorf("router id not specified by annotation, cannot resolve router id for local ASN %v", localASN) 98 } 99 100 // NewAnnotationMap parses a Node's annotations into a AnnotationMap 101 // and returns the latter. 102 // 103 // An error is returned containing one or more parsing errors. 104 // 105 // This is for convenience so the caller can log all parsing errors at once. 106 // The error should still be treated as a normal descrete error and an empty 107 // AnnotationMap is returned. 108 func NewAnnotationMap(a map[string]string) (AnnotationMap, error) { 109 am := AnnotationMap{} 110 errs := make([]error, 0, len(a)) 111 for key, value := range a { 112 asn, attrs, err := parseAnnotation(key, value) 113 if err != nil && !errors.As(err, &ErrNotVRouterAnno{}) { 114 errs = append(errs, err) 115 continue 116 } 117 am[asn] = attrs 118 } 119 if len(errs) > 0 { 120 return am, errors.Join(errs...) 121 } 122 return am, nil 123 } 124 125 // parseAnnotation will attempt to parse a `cilium.io/bgp-virtual-router` 126 // annotation into an Attributes structure. 127 // 128 // Errors returned by this parse method are defined at top of file. 129 func parseAnnotation(key string, value string) (int64, Attributes, error) { 130 var out Attributes 131 // is this an annotation we care about? 132 if !strings.HasPrefix(key, annotation.BGPVRouterAnnoPrefix) { 133 return 0, out, ErrNotVRouterAnno{key} 134 } 135 136 // parse out asn from annotation key, if split at "." will be 3rd element 137 var asn int64 138 if anno := strings.Split(key, "."); len(anno) != 3 { 139 return 0, out, ErrNoASNAnno{key} 140 } else { 141 asn64, err := strconv.ParseUint(anno[2], 10, 32) 142 if err != nil { 143 return 0, out, ErrASNAnno{"could not parse ASN as a 32bit integer", anno[2], key} 144 } 145 asn = int64(asn64) 146 } 147 out.ASN = asn 148 149 // split annotation value into multiple "key=value" formatted attributes. 150 attrs := strings.Split(value, ",") 151 if len(attrs) == 0 { 152 return 0, out, nil // empty attributes, not an error 153 } 154 // parse string attributes into Attributes structure. 155 for _, attr := range attrs { 156 kv := strings.Split(attr, "=") 157 if len(kv) != 2 { 158 continue 159 } 160 switch kv[0] { 161 case "router-id": 162 addr, err := netip.ParseAddr(kv[1]) 163 if err != nil { 164 return 0, out, ErrAttrib{key, kv[0], "could not parse router-id as an IPv4 address"} 165 } 166 if !addr.Is4() { 167 return 0, out, ErrAttrib{key, kv[0], "router-id must be a valid IPv4 address"} 168 } 169 out.RouterID = kv[1] 170 case "local-port": 171 port, err := strconv.ParseInt(kv[1], 10, 16) 172 if err != nil { 173 return 0, out, ErrAttrib{key, kv[0], "could not parse into port number as 16bit integer"} 174 } 175 out.LocalPort = int32(port) 176 } 177 } 178 return asn, out, nil 179 }