diff options
author | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2018-07-07 13:48:38 +0900 |
---|---|---|
committer | FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp> | 2018-07-07 20:44:25 +0900 |
commit | c4775c42510d1f1ddd55036dc19e982712fa6a0b (patch) | |
tree | 6ec8b61d4338c809e239e3003a2d32d480898e22 /pkg | |
parent | b3079759aa13172fcb548a83da9a9653d8d5fed4 (diff) |
follow Standard Go Project Layout
https://github.com/golang-standards/project-layout
Now you can see clearly what are private and public library code.
Signed-off-by: FUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>
Diffstat (limited to 'pkg')
37 files changed, 28347 insertions, 0 deletions
diff --git a/pkg/packet/bgp/bgp.go b/pkg/packet/bgp/bgp.go new file mode 100644 index 00000000..01251167 --- /dev/null +++ b/pkg/packet/bgp/bgp.go @@ -0,0 +1,9674 @@ +// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgp + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "math" + "net" + "reflect" + "regexp" + "sort" + "strconv" + "strings" +) + +type MarshallingOption struct { + AddPath map[RouteFamily]BGPAddPathMode +} + +func IsAddPathEnabled(decode bool, f RouteFamily, options []*MarshallingOption) bool { + for _, opt := range options { + if opt == nil { + continue + } + if o := opt.AddPath; o != nil { + if decode && o[f]&BGP_ADD_PATH_RECEIVE > 0 { + return true + } else if !decode && o[f]&BGP_ADD_PATH_SEND > 0 { + return true + } + } + } + return false +} + +const ( + AFI_IP = 1 + AFI_IP6 = 2 + AFI_L2VPN = 25 + AFI_OPAQUE = 16397 +) + +const ( + SAFI_UNICAST = 1 + SAFI_MULTICAST = 2 + SAFI_MPLS_LABEL = 4 + SAFI_ENCAPSULATION = 7 + SAFI_VPLS = 65 + SAFI_EVPN = 70 + SAFI_MPLS_VPN = 128 + SAFI_MPLS_VPN_MULTICAST = 129 + SAFI_ROUTE_TARGET_CONSTRAINTS = 132 + SAFI_FLOW_SPEC_UNICAST = 133 + SAFI_FLOW_SPEC_VPN = 134 + SAFI_KEY_VALUE = 241 +) + +const ( + BGP_ORIGIN_ATTR_TYPE_IGP uint8 = 0 + BGP_ORIGIN_ATTR_TYPE_EGP uint8 = 1 + BGP_ORIGIN_ATTR_TYPE_INCOMPLETE uint8 = 2 +) + +const ( + BGP_ASPATH_ATTR_TYPE_SET = 1 + BGP_ASPATH_ATTR_TYPE_SEQ = 2 + BGP_ASPATH_ATTR_TYPE_CONFED_SEQ = 3 + BGP_ASPATH_ATTR_TYPE_CONFED_SET = 4 +) + +// RFC7153 5.1. Registries for the "Type" Field +// RANGE REGISTRATION PROCEDURES +// 0x00-0x3F Transitive First Come First Served +// 0x40-0x7F Non-Transitive First Come First Served +// 0x80-0x8F Transitive Experimental Use +// 0x90-0xBF Transitive Standards Action +// 0xC0-0xCF Non-Transitive Experimental Use +// 0xD0-0xFF Non-Transitive Standards Action +type ExtendedCommunityAttrType uint8 + +const ( + EC_TYPE_TRANSITIVE_TWO_OCTET_AS_SPECIFIC ExtendedCommunityAttrType = 0x00 + EC_TYPE_TRANSITIVE_IP6_SPECIFIC ExtendedCommunityAttrType = 0x00 // RFC5701 + EC_TYPE_TRANSITIVE_IP4_SPECIFIC ExtendedCommunityAttrType = 0x01 + EC_TYPE_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC ExtendedCommunityAttrType = 0x02 + EC_TYPE_TRANSITIVE_OPAQUE ExtendedCommunityAttrType = 0x03 + EC_TYPE_TRANSITIVE_QOS_MARKING ExtendedCommunityAttrType = 0x04 + EC_TYPE_COS_CAPABILITY ExtendedCommunityAttrType = 0x05 + EC_TYPE_EVPN ExtendedCommunityAttrType = 0x06 + EC_TYPE_FLOWSPEC_REDIRECT_MIRROR ExtendedCommunityAttrType = 0x08 + EC_TYPE_NON_TRANSITIVE_TWO_OCTET_AS_SPECIFIC ExtendedCommunityAttrType = 0x40 + EC_TYPE_NON_TRANSITIVE_IP6_SPECIFIC ExtendedCommunityAttrType = 0x40 // RFC5701 + EC_TYPE_NON_TRANSITIVE_IP4_SPECIFIC ExtendedCommunityAttrType = 0x41 + EC_TYPE_NON_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC ExtendedCommunityAttrType = 0x42 + EC_TYPE_NON_TRANSITIVE_OPAQUE ExtendedCommunityAttrType = 0x43 + EC_TYPE_NON_TRANSITIVE_QOS_MARKING ExtendedCommunityAttrType = 0x44 + EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL ExtendedCommunityAttrType = 0x80 + EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2 ExtendedCommunityAttrType = 0x81 // RFC7674 + EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3 ExtendedCommunityAttrType = 0x82 // RFC7674 +) + +// RFC7153 5.2. Registries for the "Sub-Type" Field +// RANGE REGISTRATION PROCEDURES +// 0x00-0xBF First Come First Served +// 0xC0-0xFF IETF Review +type ExtendedCommunityAttrSubType uint8 + +const ( + EC_SUBTYPE_ROUTE_TARGET ExtendedCommunityAttrSubType = 0x02 // EC_TYPE: 0x00, 0x01, 0x02 + EC_SUBTYPE_ROUTE_ORIGIN ExtendedCommunityAttrSubType = 0x03 // EC_TYPE: 0x00, 0x01, 0x02 + EC_SUBTYPE_LINK_BANDWIDTH ExtendedCommunityAttrSubType = 0x04 // EC_TYPE: 0x40 + EC_SUBTYPE_GENERIC ExtendedCommunityAttrSubType = 0x04 // EC_TYPE: 0x02, 0x42 + EC_SUBTYPE_OSPF_DOMAIN_ID ExtendedCommunityAttrSubType = 0x05 // EC_TYPE: 0x00, 0x01, 0x02 + EC_SUBTYPE_OSPF_ROUTE_ID ExtendedCommunityAttrSubType = 0x07 // EC_TYPE: 0x01 + EC_SUBTYPE_BGP_DATA_COLLECTION ExtendedCommunityAttrSubType = 0x08 // EC_TYPE: 0x00, 0x02 + EC_SUBTYPE_SOURCE_AS ExtendedCommunityAttrSubType = 0x09 // EC_TYPE: 0x00, 0x02 + EC_SUBTYPE_L2VPN_ID ExtendedCommunityAttrSubType = 0x0A // EC_TYPE: 0x00, 0x01 + EC_SUBTYPE_VRF_ROUTE_IMPORT ExtendedCommunityAttrSubType = 0x0B // EC_TYPE: 0x01 + EC_SUBTYPE_CISCO_VPN_DISTINGUISHER ExtendedCommunityAttrSubType = 0x10 // EC_TYPE: 0x00, 0x01, 0x02 + + EC_SUBTYPE_OSPF_ROUTE_TYPE ExtendedCommunityAttrSubType = 0x06 // EC_TYPE: 0x03 + EC_SUBTYPE_COLOR ExtendedCommunityAttrSubType = 0x0B // EC_TYPE: 0x03 + EC_SUBTYPE_ENCAPSULATION ExtendedCommunityAttrSubType = 0x0C // EC_TYPE: 0x03 + EC_SUBTYPE_DEFAULT_GATEWAY ExtendedCommunityAttrSubType = 0x0D // EC_TYPE: 0x03 + + EC_SUBTYPE_ORIGIN_VALIDATION ExtendedCommunityAttrSubType = 0x00 // EC_TYPE: 0x43 + + EC_SUBTYPE_FLOWSPEC_TRAFFIC_RATE ExtendedCommunityAttrSubType = 0x06 // EC_TYPE: 0x80 + EC_SUBTYPE_FLOWSPEC_TRAFFIC_ACTION ExtendedCommunityAttrSubType = 0x07 // EC_TYPE: 0x80 + EC_SUBTYPE_FLOWSPEC_REDIRECT ExtendedCommunityAttrSubType = 0x08 // EC_TYPE: 0x80 + EC_SUBTYPE_FLOWSPEC_TRAFFIC_REMARK ExtendedCommunityAttrSubType = 0x09 // EC_TYPE: 0x80 + EC_SUBTYPE_L2_INFO ExtendedCommunityAttrSubType = 0x0A // EC_TYPE: 0x80 + EC_SUBTYPE_FLOWSPEC_REDIRECT_IP6 ExtendedCommunityAttrSubType = 0x0B // EC_TYPE: 0x80 + + EC_SUBTYPE_MAC_MOBILITY ExtendedCommunityAttrSubType = 0x00 // EC_TYPE: 0x06 + EC_SUBTYPE_ESI_LABEL ExtendedCommunityAttrSubType = 0x01 // EC_TYPE: 0x06 + EC_SUBTYPE_ES_IMPORT ExtendedCommunityAttrSubType = 0x02 // EC_TYPE: 0x06 + EC_SUBTYPE_ROUTER_MAC ExtendedCommunityAttrSubType = 0x03 // EC_TYPE: 0x06 + + EC_SUBTYPE_UUID_BASED_RT ExtendedCommunityAttrSubType = 0x11 +) + +type TunnelType uint16 + +const ( + TUNNEL_TYPE_L2TP3 TunnelType = 1 + TUNNEL_TYPE_GRE TunnelType = 2 + TUNNEL_TYPE_IP_IN_IP TunnelType = 7 + TUNNEL_TYPE_VXLAN TunnelType = 8 + TUNNEL_TYPE_NVGRE TunnelType = 9 + TUNNEL_TYPE_MPLS TunnelType = 10 + TUNNEL_TYPE_MPLS_IN_GRE TunnelType = 11 + TUNNEL_TYPE_VXLAN_GRE TunnelType = 12 + TUNNEL_TYPE_MPLS_IN_UDP TunnelType = 13 +) + +func (p TunnelType) String() string { + switch p { + case TUNNEL_TYPE_L2TP3: + return "l2tp3" + case TUNNEL_TYPE_GRE: + return "gre" + case TUNNEL_TYPE_IP_IN_IP: + return "ip-in-ip" + case TUNNEL_TYPE_VXLAN: + return "vxlan" + case TUNNEL_TYPE_NVGRE: + return "nvgre" + case TUNNEL_TYPE_MPLS: + return "mpls" + case TUNNEL_TYPE_MPLS_IN_GRE: + return "mpls-in-gre" + case TUNNEL_TYPE_VXLAN_GRE: + return "vxlan-gre" + case TUNNEL_TYPE_MPLS_IN_UDP: + return "mpls-in-udp" + default: + return fmt.Sprintf("TunnelType(%d)", uint8(p)) + } +} + +type PmsiTunnelType uint8 + +const ( + PMSI_TUNNEL_TYPE_NO_TUNNEL PmsiTunnelType = 0 + PMSI_TUNNEL_TYPE_RSVP_TE_P2MP PmsiTunnelType = 1 + PMSI_TUNNEL_TYPE_MLDP_P2MP PmsiTunnelType = 2 + PMSI_TUNNEL_TYPE_PIM_SSM_TREE PmsiTunnelType = 3 + PMSI_TUNNEL_TYPE_PIM_SM_TREE PmsiTunnelType = 4 + PMSI_TUNNEL_TYPE_BIDIR_PIM_TREE PmsiTunnelType = 5 + PMSI_TUNNEL_TYPE_INGRESS_REPL PmsiTunnelType = 6 + PMSI_TUNNEL_TYPE_MLDP_MP2MP PmsiTunnelType = 7 +) + +func (p PmsiTunnelType) String() string { + switch p { + case PMSI_TUNNEL_TYPE_NO_TUNNEL: + return "no-tunnel" + case PMSI_TUNNEL_TYPE_RSVP_TE_P2MP: + return "rsvp-te-p2mp" + case PMSI_TUNNEL_TYPE_MLDP_P2MP: + return "mldp-p2mp" + case PMSI_TUNNEL_TYPE_PIM_SSM_TREE: + return "pim-ssm-tree" + case PMSI_TUNNEL_TYPE_PIM_SM_TREE: + return "pim-sm-tree" + case PMSI_TUNNEL_TYPE_BIDIR_PIM_TREE: + return "bidir-pim-tree" + case PMSI_TUNNEL_TYPE_INGRESS_REPL: + return "ingress-repl" + case PMSI_TUNNEL_TYPE_MLDP_MP2MP: + return "mldp-mp2mp" + default: + return fmt.Sprintf("PmsiTunnelType(%d)", uint8(p)) + } +} + +type EncapSubTLVType uint8 + +const ( + ENCAP_SUBTLV_TYPE_ENCAPSULATION EncapSubTLVType = 1 + ENCAP_SUBTLV_TYPE_PROTOCOL EncapSubTLVType = 2 + ENCAP_SUBTLV_TYPE_COLOR EncapSubTLVType = 4 +) + +const ( + _ = iota + BGP_MSG_OPEN + BGP_MSG_UPDATE + BGP_MSG_NOTIFICATION + BGP_MSG_KEEPALIVE + BGP_MSG_ROUTE_REFRESH +) + +const ( + BGP_OPT_CAPABILITY = 2 +) + +type BGPCapabilityCode uint8 + +const ( + BGP_CAP_MULTIPROTOCOL BGPCapabilityCode = 1 + BGP_CAP_ROUTE_REFRESH BGPCapabilityCode = 2 + BGP_CAP_CARRYING_LABEL_INFO BGPCapabilityCode = 4 + BGP_CAP_EXTENDED_NEXTHOP BGPCapabilityCode = 5 + BGP_CAP_GRACEFUL_RESTART BGPCapabilityCode = 64 + BGP_CAP_FOUR_OCTET_AS_NUMBER BGPCapabilityCode = 65 + BGP_CAP_ADD_PATH BGPCapabilityCode = 69 + BGP_CAP_ENHANCED_ROUTE_REFRESH BGPCapabilityCode = 70 + BGP_CAP_LONG_LIVED_GRACEFUL_RESTART BGPCapabilityCode = 71 + BGP_CAP_ROUTE_REFRESH_CISCO BGPCapabilityCode = 128 +) + +var CapNameMap = map[BGPCapabilityCode]string{ + BGP_CAP_MULTIPROTOCOL: "multiprotocol", + BGP_CAP_ROUTE_REFRESH: "route-refresh", + BGP_CAP_CARRYING_LABEL_INFO: "carrying-label-info", + BGP_CAP_GRACEFUL_RESTART: "graceful-restart", + BGP_CAP_EXTENDED_NEXTHOP: "extended-nexthop", + BGP_CAP_FOUR_OCTET_AS_NUMBER: "4-octet-as", + BGP_CAP_ADD_PATH: "add-path", + BGP_CAP_ENHANCED_ROUTE_REFRESH: "enhanced-route-refresh", + BGP_CAP_ROUTE_REFRESH_CISCO: "cisco-route-refresh", + BGP_CAP_LONG_LIVED_GRACEFUL_RESTART: "long-lived-graceful-restart", +} + +func (c BGPCapabilityCode) String() string { + if n, y := CapNameMap[c]; y { + return n + } + return fmt.Sprintf("UnknownCapability(%d)", c) +} + +var ( + // Used parsing RouteDistinguisher + _regexpRouteDistinguisher = regexp.MustCompile(`^((\d+)\.(\d+)\.(\d+)\.(\d+)|((\d+)\.)?(\d+)|([\w]+:[\w:]*:[\w]+)):(\d+)$`) + + // Used for operator and value for the FlowSpec numeric type + // Example: + // re.FindStringSubmatch("&==80") + // >>> ["&==80" "&" "==" "80"] + _regexpFlowSpecNumericType = regexp.MustCompile(`(&?)(==|=|>|>=|<|<=|!|!=|=!)?(\d+|-\d|true|false)`) + + // - "=!" is used in the old style format of "tcp-flags" and "fragment". + // - The value field should be one of the followings: + // * Decimal value (e.g., 80) + // * Combination of the small letters, decimals, "-" and "+" + // (e.g., tcp, ipv4, is-fragment+first-fragment) + // * Capital letters (e.g., SA) + _regexpFlowSpecOperator = regexp.MustCompile(`&|=|>|<|!|[\w\-+]+`) + _regexpFlowSpecOperatorValue = regexp.MustCompile(`[\w\-+]+`) + + // Note: "(-*)" and "(.*)" catch the invalid flags + // Example: In this case, "Z" is unsupported flag type. + // re.FindStringSubmatch("&==-SZU") + // >>> ["&==-SZU" "&" "==" "-" "S" "ZU"] + _regexpFlowSpecTCPFlag = regexp.MustCompile("(&?)(==|=|!|!=|=!)?(-*)([FSRPAUCE]+)(.*)") + + // Note: "(.*)" catches the invalid flags + // re.FindStringSubmatch("&!=+first-fragment+last-fragment+invalid-fragment") + // >>> ["&!=+first-fragment+last-fragment+invalid-fragment" "&" "!=" "+first-fragment+last-fragment" "+last-fragment" "+" "last" "+invalid-fragment"] + _regexpFlowSpecFragment = regexp.MustCompile(`(&?)(==|=|!|!=|=!)?(((\+)?(dont|is|first|last|not-a)-fragment)+)(.*)`) + + // re.FindStringSubmatch("192.168.0.0/24") + // >>> ["192.168.0.0/24" "192.168.0.0" "/24" "24"] + // re.FindStringSubmatch("192.168.0.1") + // >>> ["192.168.0.1" "192.168.0.1" "" ""] + _regexpFindIPv4Prefix = regexp.MustCompile(`^([\d.]+)(/(\d{1,2}))?`) + + // re.FindStringSubmatch("2001:dB8::/64") + // >>> ["2001:dB8::/64" "2001:dB8::" "/64" "64" "" ""] + // re.FindStringSubmatch("2001:dB8::/64/8") + // >>> ["2001:dB8::/64/8" "2001:dB8::" "/64" "64" "/8" "8"] + // re.FindStringSubmatch("2001:dB8::1") + // >>> ["2001:dB8::1" "2001:dB8::1" "" "" "" ""] + _regexpFindIPv6Prefix = regexp.MustCompile(`^([a-fA-F\d:.]+)(/(\d{1,3}))?(/(\d{1,3}))?`) +) + +type ParameterCapabilityInterface interface { + DecodeFromBytes([]byte) error + Serialize() ([]byte, error) + Len() int + Code() BGPCapabilityCode +} + +type DefaultParameterCapability struct { + CapCode BGPCapabilityCode `json:"code"` + CapLen uint8 `json:"-"` + CapValue []byte `json:"value,omitempty"` +} + +func (c *DefaultParameterCapability) Code() BGPCapabilityCode { + return c.CapCode +} + +func (c *DefaultParameterCapability) DecodeFromBytes(data []byte) error { + c.CapCode = BGPCapabilityCode(data[0]) + c.CapLen = data[1] + if len(data) < 2+int(c.CapLen) { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all OptionParameterCapability bytes available") + } + if c.CapLen > 0 { + c.CapValue = data[2 : 2+c.CapLen] + } + return nil +} + +func (c *DefaultParameterCapability) Serialize() ([]byte, error) { + c.CapLen = uint8(len(c.CapValue)) + buf := make([]byte, 2) + buf[0] = uint8(c.CapCode) + buf[1] = c.CapLen + buf = append(buf, c.CapValue...) + return buf, nil +} + +func (c *DefaultParameterCapability) Len() int { + return int(c.CapLen + 2) +} + +type CapMultiProtocol struct { + DefaultParameterCapability + CapValue RouteFamily +} + +func (c *CapMultiProtocol) DecodeFromBytes(data []byte) error { + c.DefaultParameterCapability.DecodeFromBytes(data) + data = data[2:] + if len(data) < 4 { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all CapabilityMultiProtocol bytes available") + } + c.CapValue = AfiSafiToRouteFamily(binary.BigEndian.Uint16(data[0:2]), data[3]) + return nil +} + +func (c *CapMultiProtocol) Serialize() ([]byte, error) { + buf := make([]byte, 4) + afi, safi := RouteFamilyToAfiSafi(c.CapValue) + binary.BigEndian.PutUint16(buf[0:], afi) + buf[3] = safi + c.DefaultParameterCapability.CapValue = buf + return c.DefaultParameterCapability.Serialize() +} + +func (c *CapMultiProtocol) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Code BGPCapabilityCode `json:"code"` + Value RouteFamily `json:"value"` + }{ + Code: c.Code(), + Value: c.CapValue, + }) +} + +func NewCapMultiProtocol(rf RouteFamily) *CapMultiProtocol { + return &CapMultiProtocol{ + DefaultParameterCapability{ + CapCode: BGP_CAP_MULTIPROTOCOL, + }, + rf, + } +} + +type CapRouteRefresh struct { + DefaultParameterCapability +} + +func NewCapRouteRefresh() *CapRouteRefresh { + return &CapRouteRefresh{ + DefaultParameterCapability{ + CapCode: BGP_CAP_ROUTE_REFRESH, + }, + } +} + +type CapCarryingLabelInfo struct { + DefaultParameterCapability +} + +func NewCapCarryingLabelInfo() *CapCarryingLabelInfo { + return &CapCarryingLabelInfo{ + DefaultParameterCapability{ + CapCode: BGP_CAP_CARRYING_LABEL_INFO, + }, + } +} + +type CapExtendedNexthopTuple struct { + NLRIAFI uint16 + NLRISAFI uint16 + NexthopAFI uint16 +} + +func (c *CapExtendedNexthopTuple) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + NLRIAddressFamily RouteFamily `json:"nlri_address_family"` + NexthopAddressFamily uint16 `json:"nexthop_address_family"` + }{ + NLRIAddressFamily: AfiSafiToRouteFamily(c.NLRIAFI, uint8(c.NLRISAFI)), + NexthopAddressFamily: c.NexthopAFI, + }) +} + +func NewCapExtendedNexthopTuple(af RouteFamily, nexthop uint16) *CapExtendedNexthopTuple { + afi, safi := RouteFamilyToAfiSafi(af) + return &CapExtendedNexthopTuple{ + NLRIAFI: afi, + NLRISAFI: uint16(safi), + NexthopAFI: nexthop, + } +} + +type CapExtendedNexthop struct { + DefaultParameterCapability + Tuples []*CapExtendedNexthopTuple +} + +func (c *CapExtendedNexthop) DecodeFromBytes(data []byte) error { + c.DefaultParameterCapability.DecodeFromBytes(data) + data = data[2:] + if len(data) < 6 { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all CapabilityExtendedNexthop bytes available") + } + c.Tuples = []*CapExtendedNexthopTuple{} + for len(data) >= 6 { + t := &CapExtendedNexthopTuple{ + binary.BigEndian.Uint16(data[0:2]), + binary.BigEndian.Uint16(data[2:4]), + binary.BigEndian.Uint16(data[4:6]), + } + c.Tuples = append(c.Tuples, t) + data = data[6:] + } + return nil +} + +func (c *CapExtendedNexthop) Serialize() ([]byte, error) { + buf := make([]byte, len(c.Tuples)*6) + for i, t := range c.Tuples { + binary.BigEndian.PutUint16(buf[i*6:i*6+2], t.NLRIAFI) + binary.BigEndian.PutUint16(buf[i*6+2:i*6+4], t.NLRISAFI) + binary.BigEndian.PutUint16(buf[i*6+4:i*6+6], t.NexthopAFI) + } + c.DefaultParameterCapability.CapValue = buf + return c.DefaultParameterCapability.Serialize() +} + +func (c *CapExtendedNexthop) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Code BGPCapabilityCode `json:"code"` + Tuples []*CapExtendedNexthopTuple `json:"tuples"` + }{ + Code: c.Code(), + Tuples: c.Tuples, + }) +} + +func NewCapExtendedNexthop(tuples []*CapExtendedNexthopTuple) *CapExtendedNexthop { + return &CapExtendedNexthop{ + DefaultParameterCapability{ + CapCode: BGP_CAP_EXTENDED_NEXTHOP, + }, + tuples, + } +} + +type CapGracefulRestartTuple struct { + AFI uint16 + SAFI uint8 + Flags uint8 +} + +func (c *CapGracefulRestartTuple) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RouteFamily RouteFamily `json:"route_family"` + Flags uint8 `json:"flags"` + }{ + RouteFamily: AfiSafiToRouteFamily(c.AFI, c.SAFI), + Flags: c.Flags, + }) +} + +func NewCapGracefulRestartTuple(rf RouteFamily, forward bool) *CapGracefulRestartTuple { + afi, safi := RouteFamilyToAfiSafi(rf) + flags := 0 + if forward { + flags = 0x80 + } + return &CapGracefulRestartTuple{ + AFI: afi, + SAFI: safi, + Flags: uint8(flags), + } +} + +type CapGracefulRestart struct { + DefaultParameterCapability + Flags uint8 + Time uint16 + Tuples []*CapGracefulRestartTuple +} + +func (c *CapGracefulRestart) DecodeFromBytes(data []byte) error { + c.DefaultParameterCapability.DecodeFromBytes(data) + data = data[2:] + if len(data) < 2 { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all CapabilityGracefulRestart bytes available") + } + restart := binary.BigEndian.Uint16(data[0:2]) + c.Flags = uint8(restart >> 12) + c.Time = restart & 0xfff + data = data[2:] + + valueLen := int(c.CapLen) - 2 + + if valueLen >= 4 && len(data) >= valueLen { + c.Tuples = make([]*CapGracefulRestartTuple, 0, valueLen/4) + + for i := valueLen; i >= 4; i -= 4 { + t := &CapGracefulRestartTuple{binary.BigEndian.Uint16(data[0:2]), + data[2], data[3]} + c.Tuples = append(c.Tuples, t) + data = data[4:] + } + } + return nil +} + +func (c *CapGracefulRestart) Serialize() ([]byte, error) { + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf[0:], uint16(c.Flags)<<12|c.Time) + for _, t := range c.Tuples { + tbuf := make([]byte, 4) + binary.BigEndian.PutUint16(tbuf[0:2], t.AFI) + tbuf[2] = t.SAFI + tbuf[3] = t.Flags + buf = append(buf, tbuf...) + } + c.DefaultParameterCapability.CapValue = buf + return c.DefaultParameterCapability.Serialize() +} + +func (c *CapGracefulRestart) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Code BGPCapabilityCode `json:"code"` + Flags uint8 `json:"flags"` + Time uint16 `json:"time"` + Tuples []*CapGracefulRestartTuple `json:"tuples"` + }{ + Code: c.Code(), + Flags: c.Flags, + Time: c.Time, + Tuples: c.Tuples, + }) +} + +func NewCapGracefulRestart(restarting, notification bool, time uint16, tuples []*CapGracefulRestartTuple) *CapGracefulRestart { + flags := 0 + if restarting { + flags = 0x08 + } + if notification { + flags |= 0x04 + } + return &CapGracefulRestart{ + DefaultParameterCapability: DefaultParameterCapability{ + CapCode: BGP_CAP_GRACEFUL_RESTART, + }, + Flags: uint8(flags), + Time: time, + Tuples: tuples, + } +} + +type CapFourOctetASNumber struct { + DefaultParameterCapability + CapValue uint32 +} + +func (c *CapFourOctetASNumber) DecodeFromBytes(data []byte) error { + c.DefaultParameterCapability.DecodeFromBytes(data) + data = data[2:] + if len(data) < 4 { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all CapabilityFourOctetASNumber bytes available") + } + c.CapValue = binary.BigEndian.Uint32(data[0:4]) + return nil +} + +func (c *CapFourOctetASNumber) Serialize() ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, c.CapValue) + c.DefaultParameterCapability.CapValue = buf + return c.DefaultParameterCapability.Serialize() +} + +func (c *CapFourOctetASNumber) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Code BGPCapabilityCode `json:"code"` + Value uint32 `json:"value"` + }{ + Code: c.Code(), + Value: c.CapValue, + }) +} + +func NewCapFourOctetASNumber(asnum uint32) *CapFourOctetASNumber { + return &CapFourOctetASNumber{ + DefaultParameterCapability{ + CapCode: BGP_CAP_FOUR_OCTET_AS_NUMBER, + }, + asnum, + } +} + +type BGPAddPathMode uint8 + +const ( + BGP_ADD_PATH_NONE BGPAddPathMode = iota + BGP_ADD_PATH_RECEIVE + BGP_ADD_PATH_SEND + BGP_ADD_PATH_BOTH +) + +func (m BGPAddPathMode) String() string { + switch m { + case BGP_ADD_PATH_NONE: + return "none" + case BGP_ADD_PATH_RECEIVE: + return "receive" + case BGP_ADD_PATH_SEND: + return "send" + case BGP_ADD_PATH_BOTH: + return "receive/send" + default: + return fmt.Sprintf("unknown(%d)", m) + } +} + +type CapAddPathTuple struct { + RouteFamily RouteFamily + Mode BGPAddPathMode +} + +func (t *CapAddPathTuple) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RouteFamily RouteFamily `json:"family"` + Mode uint8 `json:"mode"` + }{ + RouteFamily: t.RouteFamily, + Mode: uint8(t.Mode), + }) +} + +func NewCapAddPathTuple(family RouteFamily, mode BGPAddPathMode) *CapAddPathTuple { + return &CapAddPathTuple{ + RouteFamily: family, + Mode: mode, + } +} + +type CapAddPath struct { + DefaultParameterCapability + Tuples []*CapAddPathTuple +} + +func (c *CapAddPath) DecodeFromBytes(data []byte) error { + c.DefaultParameterCapability.DecodeFromBytes(data) + data = data[2:] + if len(data) < 4 { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all CapabilityAddPath bytes available") + } + c.Tuples = []*CapAddPathTuple{} + for len(data) >= 4 { + t := &CapAddPathTuple{ + RouteFamily: AfiSafiToRouteFamily(binary.BigEndian.Uint16(data[:2]), data[2]), + Mode: BGPAddPathMode(data[3]), + } + c.Tuples = append(c.Tuples, t) + data = data[4:] + } + return nil +} + +func (c *CapAddPath) Serialize() ([]byte, error) { + buf := make([]byte, len(c.Tuples)*4) + for i, t := range c.Tuples { + afi, safi := RouteFamilyToAfiSafi(t.RouteFamily) + binary.BigEndian.PutUint16(buf[i*4:i*4+2], afi) + buf[i*4+2] = safi + buf[i*4+3] = byte(t.Mode) + } + c.DefaultParameterCapability.CapValue = buf + return c.DefaultParameterCapability.Serialize() +} + +func (c *CapAddPath) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Code BGPCapabilityCode `json:"code"` + Tuples []*CapAddPathTuple `json:"tuples"` + }{ + Code: c.Code(), + Tuples: c.Tuples, + }) +} + +func NewCapAddPath(tuples []*CapAddPathTuple) *CapAddPath { + return &CapAddPath{ + DefaultParameterCapability: DefaultParameterCapability{ + CapCode: BGP_CAP_ADD_PATH, + }, + Tuples: tuples, + } +} + +type CapEnhancedRouteRefresh struct { + DefaultParameterCapability +} + +func NewCapEnhancedRouteRefresh() *CapEnhancedRouteRefresh { + return &CapEnhancedRouteRefresh{ + DefaultParameterCapability{ + CapCode: BGP_CAP_ENHANCED_ROUTE_REFRESH, + }, + } +} + +type CapRouteRefreshCisco struct { + DefaultParameterCapability +} + +func NewCapRouteRefreshCisco() *CapRouteRefreshCisco { + return &CapRouteRefreshCisco{ + DefaultParameterCapability{ + CapCode: BGP_CAP_ROUTE_REFRESH_CISCO, + }, + } +} + +type CapLongLivedGracefulRestartTuple struct { + AFI uint16 + SAFI uint8 + Flags uint8 + RestartTime uint32 +} + +func (c *CapLongLivedGracefulRestartTuple) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RouteFamily RouteFamily `json:"route_family"` + Flags uint8 `json:"flags"` + RestartTime uint32 `json:"restart_time"` + }{ + RouteFamily: AfiSafiToRouteFamily(c.AFI, c.SAFI), + Flags: c.Flags, + RestartTime: c.RestartTime, + }) +} + +func NewCapLongLivedGracefulRestartTuple(rf RouteFamily, forward bool, restartTime uint32) *CapLongLivedGracefulRestartTuple { + afi, safi := RouteFamilyToAfiSafi(rf) + flags := 0 + if forward { + flags = 0x80 + } + return &CapLongLivedGracefulRestartTuple{ + AFI: afi, + SAFI: safi, + Flags: uint8(flags), + RestartTime: restartTime, + } +} + +type CapLongLivedGracefulRestart struct { + DefaultParameterCapability + Tuples []*CapLongLivedGracefulRestartTuple +} + +func (c *CapLongLivedGracefulRestart) DecodeFromBytes(data []byte) error { + c.DefaultParameterCapability.DecodeFromBytes(data) + data = data[2:] + + valueLen := int(c.CapLen) + if valueLen%7 != 0 || len(data) < valueLen { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "invalid length of long lived graceful restart capablity") + } + for i := valueLen; i >= 7; i -= 7 { + t := &CapLongLivedGracefulRestartTuple{ + binary.BigEndian.Uint16(data), + data[2], + data[3], + uint32(data[4])<<16 | uint32(data[5])<<8 | uint32(data[6]), + } + c.Tuples = append(c.Tuples, t) + data = data[7:] + } + return nil +} + +func (c *CapLongLivedGracefulRestart) Serialize() ([]byte, error) { + buf := make([]byte, 7*len(c.Tuples)) + for idx, t := range c.Tuples { + binary.BigEndian.PutUint16(buf[idx*7:], t.AFI) + buf[idx*7+2] = t.SAFI + buf[idx*7+3] = t.Flags + buf[idx*7+4] = uint8((t.RestartTime >> 16) & 0xff) + buf[idx*7+5] = uint8((t.RestartTime >> 8) & 0xff) + buf[idx*7+6] = uint8(t.RestartTime & 0xff) + } + c.DefaultParameterCapability.CapValue = buf + return c.DefaultParameterCapability.Serialize() +} + +func (c *CapLongLivedGracefulRestart) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Code BGPCapabilityCode `json:"code"` + Tuples []*CapLongLivedGracefulRestartTuple `json:"tuples"` + }{ + Code: c.Code(), + Tuples: c.Tuples, + }) +} + +func NewCapLongLivedGracefulRestart(tuples []*CapLongLivedGracefulRestartTuple) *CapLongLivedGracefulRestart { + return &CapLongLivedGracefulRestart{ + DefaultParameterCapability: DefaultParameterCapability{ + CapCode: BGP_CAP_LONG_LIVED_GRACEFUL_RESTART, + }, + Tuples: tuples, + } +} + +type CapUnknown struct { + DefaultParameterCapability +} + +func NewCapUnknown(code BGPCapabilityCode, value []byte) *CapUnknown { + return &CapUnknown{ + DefaultParameterCapability{ + CapCode: code, + CapValue: value, + }, + } +} + +func DecodeCapability(data []byte) (ParameterCapabilityInterface, error) { + if len(data) < 2 { + return nil, NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY, nil, "Not all ParameterCapability bytes available") + } + var c ParameterCapabilityInterface + switch BGPCapabilityCode(data[0]) { + case BGP_CAP_MULTIPROTOCOL: + c = &CapMultiProtocol{} + case BGP_CAP_ROUTE_REFRESH: + c = &CapRouteRefresh{} + case BGP_CAP_CARRYING_LABEL_INFO: + c = &CapCarryingLabelInfo{} + case BGP_CAP_EXTENDED_NEXTHOP: + c = &CapExtendedNexthop{} + case BGP_CAP_GRACEFUL_RESTART: + c = &CapGracefulRestart{} + case BGP_CAP_FOUR_OCTET_AS_NUMBER: + c = &CapFourOctetASNumber{} + case BGP_CAP_ADD_PATH: + c = &CapAddPath{} + case BGP_CAP_ENHANCED_ROUTE_REFRESH: + c = &CapEnhancedRouteRefresh{} + case BGP_CAP_ROUTE_REFRESH_CISCO: + c = &CapRouteRefreshCisco{} + case BGP_CAP_LONG_LIVED_GRACEFUL_RESTART: + c = &CapLongLivedGracefulRestart{} + default: + c = &CapUnknown{} + } + err := c.DecodeFromBytes(data) + return c, err +} + +type OptionParameterInterface interface { + Serialize() ([]byte, error) +} + +type OptionParameterCapability struct { + ParamType uint8 + ParamLen uint8 + Capability []ParameterCapabilityInterface +} + +func (o *OptionParameterCapability) DecodeFromBytes(data []byte) error { + if uint8(len(data)) < o.ParamLen { + return NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_OPTIONAL_PARAMETER, nil, "Not all OptionParameterCapability bytes available") + } + for len(data) >= 2 { + c, err := DecodeCapability(data) + if err != nil { + return err + } + o.Capability = append(o.Capability, c) + data = data[c.Len():] + } + return nil +} + +func (o *OptionParameterCapability) Serialize() ([]byte, error) { + buf := make([]byte, 2) + buf[0] = o.ParamType + for _, p := range o.Capability { + pbuf, err := p.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) + } + o.ParamLen = uint8(len(buf) - 2) + buf[1] = o.ParamLen + return buf, nil +} + +func NewOptionParameterCapability(capability []ParameterCapabilityInterface) *OptionParameterCapability { + return &OptionParameterCapability{ + ParamType: BGP_OPT_CAPABILITY, + Capability: capability, + } +} + +type OptionParameterUnknown struct { + ParamType uint8 + ParamLen uint8 + Value []byte +} + +func (o *OptionParameterUnknown) Serialize() ([]byte, error) { + buf := make([]byte, 2) + buf[0] = o.ParamType + if o.ParamLen == 0 { + o.ParamLen = uint8(len(o.Value)) + } + buf[1] = o.ParamLen + return append(buf, o.Value...), nil +} + +type BGPOpen struct { + Version uint8 + MyAS uint16 + HoldTime uint16 + ID net.IP + OptParamLen uint8 + OptParams []OptionParameterInterface +} + +func (msg *BGPOpen) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if len(data) < 10 { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "Not all BGP Open message bytes available") + } + msg.Version = data[0] + msg.MyAS = binary.BigEndian.Uint16(data[1:3]) + msg.HoldTime = binary.BigEndian.Uint16(data[3:5]) + msg.ID = net.IP(data[5:9]).To4() + msg.OptParamLen = data[9] + data = data[10:] + if len(data) < int(msg.OptParamLen) { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "Not all BGP Open message bytes available") + } + + msg.OptParams = []OptionParameterInterface{} + for rest := msg.OptParamLen; rest > 0; { + if rest < 2 { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "Malformed BGP Open message") + } + paramtype := data[0] + paramlen := data[1] + if rest < paramlen+2 { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "Malformed BGP Open message") + } + rest -= paramlen + 2 + + if paramtype == BGP_OPT_CAPABILITY { + p := &OptionParameterCapability{} + p.ParamType = paramtype + p.ParamLen = paramlen + p.DecodeFromBytes(data[2 : 2+paramlen]) + msg.OptParams = append(msg.OptParams, p) + } else { + p := &OptionParameterUnknown{} + p.ParamType = paramtype + p.ParamLen = paramlen + p.Value = data[2 : 2+paramlen] + msg.OptParams = append(msg.OptParams, p) + } + data = data[2+paramlen:] + } + return nil +} + +func (msg *BGPOpen) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 10) + buf[0] = msg.Version + binary.BigEndian.PutUint16(buf[1:3], msg.MyAS) + binary.BigEndian.PutUint16(buf[3:5], msg.HoldTime) + copy(buf[5:9], msg.ID.To4()) + pbuf := make([]byte, 0) + for _, p := range msg.OptParams { + onepbuf, err := p.Serialize() + if err != nil { + return nil, err + } + pbuf = append(pbuf, onepbuf...) + } + msg.OptParamLen = uint8(len(pbuf)) + buf[9] = msg.OptParamLen + return append(buf, pbuf...), nil +} + +func NewBGPOpenMessage(myas uint16, holdtime uint16, id string, optparams []OptionParameterInterface) *BGPMessage { + return &BGPMessage{ + Header: BGPHeader{Type: BGP_MSG_OPEN}, + Body: &BGPOpen{4, myas, holdtime, net.ParseIP(id).To4(), 0, optparams}, + } +} + +type AddrPrefixInterface interface { + DecodeFromBytes([]byte, ...*MarshallingOption) error + Serialize(...*MarshallingOption) ([]byte, error) + AFI() uint16 + SAFI() uint8 + Len(...*MarshallingOption) int + String() string + MarshalJSON() ([]byte, error) + // Create a flat map to describe attributes and their + // values. This can be used to create structured outputs. + Flat() map[string]string + PathIdentifier() uint32 + SetPathIdentifier(uint32) + PathLocalIdentifier() uint32 + SetPathLocalIdentifier(uint32) +} + +func LabelString(nlri AddrPrefixInterface) string { + label := "" + switch n := nlri.(type) { + case *LabeledIPAddrPrefix: + label = n.Labels.String() + case *LabeledIPv6AddrPrefix: + label = n.Labels.String() + case *LabeledVPNIPAddrPrefix: + label = n.Labels.String() + case *LabeledVPNIPv6AddrPrefix: + label = n.Labels.String() + case *EVPNNLRI: + switch route := n.RouteTypeData.(type) { + case *EVPNEthernetAutoDiscoveryRoute: + label = fmt.Sprintf("[%d]", route.Label) + case *EVPNMacIPAdvertisementRoute: + var l []string + for _, i := range route.Labels { + l = append(l, strconv.Itoa(int(i))) + } + label = fmt.Sprintf("[%s]", strings.Join(l, ",")) + case *EVPNIPPrefixRoute: + label = fmt.Sprintf("[%d]", route.Label) + } + } + return label +} + +type PrefixDefault struct { + id uint32 + localId uint32 +} + +func (p *PrefixDefault) PathIdentifier() uint32 { + return p.id +} + +func (p *PrefixDefault) SetPathIdentifier(id uint32) { + p.id = id +} + +func (p *PrefixDefault) PathLocalIdentifier() uint32 { + return p.localId +} + +func (p *PrefixDefault) SetPathLocalIdentifier(id uint32) { + p.localId = id +} + +func (p *PrefixDefault) decodePathIdentifier(data []byte) ([]byte, error) { + if len(data) < 4 { + code := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + subcode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return nil, NewMessageError(code, subcode, nil, "prefix misses path identifier field") + } + p.SetPathIdentifier(binary.BigEndian.Uint32(data[:4])) + return data[4:], nil +} + +func (p *PrefixDefault) serializeIdentifier() ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, p.PathLocalIdentifier()) + return buf, nil +} + +type IPAddrPrefixDefault struct { + PrefixDefault + Length uint8 + Prefix net.IP +} + +func (r *IPAddrPrefixDefault) decodePrefix(data []byte, bitlen uint8, addrlen uint8) error { + bytelen := (int(bitlen) + 7) / 8 + if len(data) < bytelen { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "network bytes is short") + } + b := make([]byte, addrlen) + copy(b, data[:bytelen]) + // clear trailing bits in the last byte. rfc doesn't require + // this but some bgp implementations need this... + rem := bitlen % 8 + if rem != 0 { + mask := 0xff00 >> rem + lastByte := b[bytelen-1] & byte(mask) + b[bytelen-1] = lastByte + } + r.Prefix = b + return nil +} + +func (r *IPAddrPrefixDefault) serializePrefix(bitLen uint8) ([]byte, error) { + byteLen := (int(bitLen) + 7) / 8 + buf := make([]byte, byteLen) + copy(buf, r.Prefix) + return buf, nil +} + +func (r *IPAddrPrefixDefault) String() string { + return fmt.Sprintf("%s/%d", r.Prefix.String(), r.Length) +} + +func (r *IPAddrPrefixDefault) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Prefix string `json:"prefix"` + }{ + Prefix: r.String(), + }) +} + +type IPAddrPrefix struct { + IPAddrPrefixDefault + addrlen uint8 +} + +func (r *IPAddrPrefix) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if r.addrlen == 0 { + r.addrlen = 4 + } + f := RF_IPv4_UC + if r.addrlen == 16 { + f = RF_IPv6_UC + } + if IsAddPathEnabled(true, f, options) { + var err error + data, err = r.decodePathIdentifier(data) + if err != nil { + return err + } + } + if len(data) < 1 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "prefix misses length field") + } + r.Length = data[0] + return r.decodePrefix(data[1:], r.Length, r.addrlen) +} + +func (r *IPAddrPrefix) Serialize(options ...*MarshallingOption) ([]byte, error) { + f := RF_IPv4_UC + if r.addrlen == 16 { + f = RF_IPv6_UC + } + var buf []byte + if IsAddPathEnabled(false, f, options) { + var err error + buf, err = r.serializeIdentifier() + if err != nil { + return nil, err + } + } + buf = append(buf, r.Length) + pbuf, err := r.serializePrefix(r.Length) + if err != nil { + return nil, err + } + return append(buf, pbuf...), nil +} + +func (r *IPAddrPrefix) AFI() uint16 { + return AFI_IP +} + +func (r *IPAddrPrefix) SAFI() uint8 { + return SAFI_UNICAST +} + +func (r *IPAddrPrefix) Len(options ...*MarshallingOption) int { + return 1 + ((int(r.Length) + 7) / 8) +} + +func NewIPAddrPrefix(length uint8, prefix string) *IPAddrPrefix { + p := &IPAddrPrefix{ + IPAddrPrefixDefault{ + Length: length, + }, + 4, + } + p.IPAddrPrefixDefault.decodePrefix(net.ParseIP(prefix).To4(), length, 4) + return p +} + +func isIPv4MappedIPv6(ip net.IP) bool { + return len(ip) == net.IPv6len && ip.To4() != nil +} + +type IPv6AddrPrefix struct { + IPAddrPrefix +} + +func (r *IPv6AddrPrefix) AFI() uint16 { + return AFI_IP6 +} + +func (r *IPv6AddrPrefix) String() string { + prefix := r.Prefix.String() + if isIPv4MappedIPv6(r.Prefix) { + prefix = "::ffff:" + prefix + } + return fmt.Sprintf("%s/%d", prefix, r.Length) +} + +func NewIPv6AddrPrefix(length uint8, prefix string) *IPv6AddrPrefix { + p := &IPv6AddrPrefix{ + IPAddrPrefix{ + IPAddrPrefixDefault{ + Length: length, + }, + 16, + }, + } + p.IPAddrPrefixDefault.decodePrefix(net.ParseIP(prefix), length, 16) + return p +} + +const ( + BGP_RD_TWO_OCTET_AS = iota + BGP_RD_IPV4_ADDRESS + BGP_RD_FOUR_OCTET_AS +) + +type RouteDistinguisherInterface interface { + DecodeFromBytes([]byte) error + Serialize() ([]byte, error) + Len() int + String() string + MarshalJSON() ([]byte, error) +} + +type DefaultRouteDistinguisher struct { + Type uint16 +} + +func (rd *DefaultRouteDistinguisher) serialize(value []byte) ([]byte, error) { + buf := make([]byte, 8) + binary.BigEndian.PutUint16(buf, rd.Type) + copy(buf[2:], value) + return buf, nil +} + +func (rd *DefaultRouteDistinguisher) Len() int { + return 8 +} + +type RouteDistinguisherTwoOctetAS struct { + DefaultRouteDistinguisher + Admin uint16 + Assigned uint32 +} + +func (rd *RouteDistinguisherTwoOctetAS) DecodeFromBytes(data []byte) error { + rd.Admin = binary.BigEndian.Uint16(data[0:2]) + rd.Assigned = binary.BigEndian.Uint32(data[2:6]) + return nil +} + +func (rd *RouteDistinguisherTwoOctetAS) Serialize() ([]byte, error) { + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:2], rd.Admin) + binary.BigEndian.PutUint32(buf[2:6], rd.Assigned) + return rd.serialize(buf) +} + +func (rd *RouteDistinguisherTwoOctetAS) String() string { + return fmt.Sprintf("%d:%d", rd.Admin, rd.Assigned) +} + +func (rd *RouteDistinguisherTwoOctetAS) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint16 `json:"type"` + Admin uint16 `json:"admin"` + Assigned uint32 `json:"assigned"` + }{ + Type: rd.Type, + Admin: rd.Admin, + Assigned: rd.Assigned, + }) +} + +func NewRouteDistinguisherTwoOctetAS(admin uint16, assigned uint32) *RouteDistinguisherTwoOctetAS { + return &RouteDistinguisherTwoOctetAS{ + DefaultRouteDistinguisher: DefaultRouteDistinguisher{ + Type: BGP_RD_TWO_OCTET_AS, + }, + Admin: admin, + Assigned: assigned, + } +} + +type RouteDistinguisherIPAddressAS struct { + DefaultRouteDistinguisher + Admin net.IP + Assigned uint16 +} + +func (rd *RouteDistinguisherIPAddressAS) DecodeFromBytes(data []byte) error { + rd.Admin = data[0:4] + rd.Assigned = binary.BigEndian.Uint16(data[4:6]) + return nil +} + +func (rd *RouteDistinguisherIPAddressAS) Serialize() ([]byte, error) { + buf := make([]byte, 6) + copy(buf[0:4], rd.Admin.To4()) + binary.BigEndian.PutUint16(buf[4:6], rd.Assigned) + return rd.serialize(buf) +} + +func (rd *RouteDistinguisherIPAddressAS) String() string { + return fmt.Sprintf("%s:%d", rd.Admin.String(), rd.Assigned) +} + +func (rd *RouteDistinguisherIPAddressAS) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint16 `json:"type"` + Admin string `json:"admin"` + Assigned uint16 `json:"assigned"` + }{ + Type: rd.Type, + Admin: rd.Admin.String(), + Assigned: rd.Assigned, + }) +} + +func NewRouteDistinguisherIPAddressAS(admin string, assigned uint16) *RouteDistinguisherIPAddressAS { + return &RouteDistinguisherIPAddressAS{ + DefaultRouteDistinguisher: DefaultRouteDistinguisher{ + Type: BGP_RD_IPV4_ADDRESS, + }, + Admin: net.ParseIP(admin).To4(), + Assigned: assigned, + } +} + +type RouteDistinguisherFourOctetAS struct { + DefaultRouteDistinguisher + Admin uint32 + Assigned uint16 +} + +func (rd *RouteDistinguisherFourOctetAS) DecodeFromBytes(data []byte) error { + rd.Admin = binary.BigEndian.Uint32(data[0:4]) + rd.Assigned = binary.BigEndian.Uint16(data[4:6]) + return nil +} + +func (rd *RouteDistinguisherFourOctetAS) Serialize() ([]byte, error) { + buf := make([]byte, 6) + binary.BigEndian.PutUint32(buf[0:4], rd.Admin) + binary.BigEndian.PutUint16(buf[4:6], rd.Assigned) + return rd.serialize(buf) +} + +func (rd *RouteDistinguisherFourOctetAS) String() string { + fst := rd.Admin >> 16 & 0xffff + snd := rd.Admin & 0xffff + return fmt.Sprintf("%d.%d:%d", fst, snd, rd.Assigned) +} + +func (rd *RouteDistinguisherFourOctetAS) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint16 `json:"type"` + Admin uint32 `json:"admin"` + Assigned uint16 `json:"assigned"` + }{ + Type: rd.Type, + Admin: rd.Admin, + Assigned: rd.Assigned, + }) +} + +func NewRouteDistinguisherFourOctetAS(admin uint32, assigned uint16) *RouteDistinguisherFourOctetAS { + return &RouteDistinguisherFourOctetAS{ + DefaultRouteDistinguisher: DefaultRouteDistinguisher{ + Type: BGP_RD_FOUR_OCTET_AS, + }, + Admin: admin, + Assigned: assigned, + } +} + +type RouteDistinguisherUnknown struct { + DefaultRouteDistinguisher + Value []byte +} + +func (rd *RouteDistinguisherUnknown) DecodeFromBytes(data []byte) error { + rd.Value = data[0:6] + return nil +} + +func (rd *RouteDistinguisherUnknown) Serialize() ([]byte, error) { + return rd.DefaultRouteDistinguisher.serialize(rd.Value) +} + +func (rd *RouteDistinguisherUnknown) String() string { + return fmt.Sprintf("%v", rd.Value) +} + +func (rd *RouteDistinguisherUnknown) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint16 `json:"type"` + Value []byte `json:"value"` + }{ + Type: rd.Type, + Value: rd.Value, + }) +} + +func GetRouteDistinguisher(data []byte) RouteDistinguisherInterface { + typ := binary.BigEndian.Uint16(data[0:2]) + switch typ { + case BGP_RD_TWO_OCTET_AS: + return NewRouteDistinguisherTwoOctetAS(binary.BigEndian.Uint16(data[2:4]), binary.BigEndian.Uint32(data[4:8])) + case BGP_RD_IPV4_ADDRESS: + return NewRouteDistinguisherIPAddressAS(net.IP(data[2:6]).String(), binary.BigEndian.Uint16(data[6:8])) + case BGP_RD_FOUR_OCTET_AS: + return NewRouteDistinguisherFourOctetAS(binary.BigEndian.Uint32(data[2:6]), binary.BigEndian.Uint16(data[6:8])) + } + rd := &RouteDistinguisherUnknown{ + DefaultRouteDistinguisher: DefaultRouteDistinguisher{ + Type: typ, + }, + } + return rd +} + +func parseRdAndRt(input string) ([]string, error) { + elems := _regexpRouteDistinguisher.FindStringSubmatch(input) + if len(elems) != 11 { + return nil, fmt.Errorf("failed to parse") + } + return elems, nil +} + +func ParseRouteDistinguisher(rd string) (RouteDistinguisherInterface, error) { + elems, err := parseRdAndRt(rd) + if err != nil { + return nil, err + } + assigned, _ := strconv.ParseUint(elems[10], 10, 32) + ip := net.ParseIP(elems[1]) + switch { + case ip.To4() != nil: + return NewRouteDistinguisherIPAddressAS(elems[1], uint16(assigned)), nil + case elems[6] == "" && elems[7] == "": + asn, _ := strconv.ParseUint(elems[8], 10, 16) + return NewRouteDistinguisherTwoOctetAS(uint16(asn), uint32(assigned)), nil + default: + fst, _ := strconv.ParseUint(elems[7], 10, 16) + snd, _ := strconv.ParseUint(elems[8], 10, 16) + asn := fst<<16 | snd + return NewRouteDistinguisherFourOctetAS(uint32(asn), uint16(assigned)), nil + } +} + +// +// RFC3107 Carrying Label Information in BGP-4 +// +// 3. Carrying Label Mapping Information +// +// b) Label: +// +// The Label field carries one or more labels (that corresponds to +// the stack of labels [MPLS-ENCAPS(RFC3032)]). Each label is encoded as +// 4 octets, where the high-order 20 bits contain the label value, and +// the low order bit contains "Bottom of Stack" +// +// RFC3032 MPLS Label Stack Encoding +// +// 2.1. Encoding the Label Stack +// +// 0 1 2 3 +// 0 ... 9 0 ... 9 0 1 2 3 4 ... 9 0 1 +// +-----+-+-+---+-+-+-+-+-+-----+-+-+-+ +// | Label | Exp |S| TTL | +// +-----+-+-+---+-+-+-+-+-+-----+-+-+-+ +// + +// RFC3107 Carrying Label Information in BGP-4 +// +// 3. Carrying Label Mapping Information +// +// The label information carried (as part of NLRI) in the Withdrawn +// Routes field should be set to 0x800000. +const WITHDRAW_LABEL = uint32(0x800000) +const ZERO_LABEL = uint32(0) // some platform uses this as withdraw label + +type MPLSLabelStack struct { + Labels []uint32 +} + +func (l *MPLSLabelStack) DecodeFromBytes(data []byte) error { + labels := []uint32{} + foundBottom := false + for len(data) >= 3 { + label := uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2]) + if label == WITHDRAW_LABEL || label == ZERO_LABEL { + l.Labels = []uint32{label} + return nil + } + data = data[3:] + labels = append(labels, label>>4) + if label&1 == 1 { + foundBottom = true + break + } + } + + if !foundBottom { + l.Labels = []uint32{} + return nil + } + l.Labels = labels + return nil +} + +func (l *MPLSLabelStack) Serialize() ([]byte, error) { + buf := make([]byte, len(l.Labels)*3) + for i, label := range l.Labels { + if label == WITHDRAW_LABEL { + return []byte{128, 0, 0}, nil + } + label = label << 4 + buf[i*3] = byte((label >> 16) & 0xff) + buf[i*3+1] = byte((label >> 8) & 0xff) + buf[i*3+2] = byte(label & 0xff) + } + buf[len(buf)-1] |= 1 + return buf, nil +} + +func (l *MPLSLabelStack) Len() int { return 3 * len(l.Labels) } + +func (l *MPLSLabelStack) String() string { + if len(l.Labels) == 0 { + return "" + } + s := bytes.NewBuffer(make([]byte, 0, 64)) + s.WriteString("[") + ss := make([]string, 0, len(l.Labels)) + for _, label := range l.Labels { + ss = append(ss, fmt.Sprintf("%d", label)) + } + s.WriteString(strings.Join(ss, ", ")) + s.WriteString("]") + return s.String() +} + +func NewMPLSLabelStack(labels ...uint32) *MPLSLabelStack { + if len(labels) == 0 { + labels = []uint32{0} + } + return &MPLSLabelStack{labels} +} + +func ParseMPLSLabelStack(buf string) (*MPLSLabelStack, error) { + elems := strings.Split(buf, "/") + labels := make([]uint32, 0, len(elems)) + if len(elems) == 0 { + goto ERR + } + for _, elem := range elems { + i, err := strconv.ParseUint(elem, 10, 32) + if err != nil { + goto ERR + } + if i > ((1 << 20) - 1) { + goto ERR + } + labels = append(labels, uint32(i)) + } + return NewMPLSLabelStack(labels...), nil +ERR: + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "invalid mpls label stack format") +} + +// +// RFC3107 Carrying Label Information in BGP-4 +// +// 3. Carrying Label Mapping Information +// +// +----------------------+ +// | Length (1 octet) | +// +----------------------+ +// | Label (3 octets) | +// +----------------------+ +// ....................... +// +----------------------+ +// | Prefix (variable) | +// +----------------------+ +// +// RFC4364 BGP/MPLS IP VPNs +// +// 4.3.4. How VPN-IPv4 NLRI Is Carried in BGP +// +// The labeled VPN-IPv4 NLRI itself is encoded as specified in +// [MPLS-BGP(RFC3107)], where the prefix consists of an 8-byte RD +// followed by an IPv4 prefix. +// + +type LabeledVPNIPAddrPrefix struct { + IPAddrPrefixDefault + Labels MPLSLabelStack + RD RouteDistinguisherInterface + addrlen uint8 +} + +func (l *LabeledVPNIPAddrPrefix) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + f := RF_IPv4_VPN + if l.addrlen == 16 { + f = RF_IPv6_VPN + } + if IsAddPathEnabled(true, f, options) { + var err error + data, err = l.decodePathIdentifier(data) + if err != nil { + return err + } + } + if len(data) < 1 { + return NewMessageError(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST), nil, "prefix misses length field") + } + l.Length = uint8(data[0]) + data = data[1:] + l.Labels.DecodeFromBytes(data) + if int(l.Length)-8*(l.Labels.Len()) < 0 { + l.Labels.Labels = []uint32{} + } + data = data[l.Labels.Len():] + l.RD = GetRouteDistinguisher(data) + data = data[l.RD.Len():] + restbits := int(l.Length) - 8*(l.Labels.Len()+l.RD.Len()) + return l.decodePrefix(data, uint8(restbits), l.addrlen) +} + +func (l *LabeledVPNIPAddrPrefix) Serialize(options ...*MarshallingOption) ([]byte, error) { + f := RF_IPv4_VPN + if l.addrlen == 16 { + f = RF_IPv6_VPN + } + var buf []byte + if IsAddPathEnabled(false, f, options) { + var err error + buf, err = l.serializeIdentifier() + if err != nil { + return nil, err + } + } + buf = append(buf, l.Length) + lbuf, err := l.Labels.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, lbuf...) + rbuf, err := l.RD.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, rbuf...) + restbits := int(l.Length) - 8*(l.Labels.Len()+l.RD.Len()) + pbuf, err := l.serializePrefix(uint8(restbits)) + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) + return buf, nil +} + +func (l *LabeledVPNIPAddrPrefix) AFI() uint16 { + return AFI_IP +} + +func (l *LabeledVPNIPAddrPrefix) SAFI() uint8 { + return SAFI_MPLS_VPN +} + +func (l *LabeledVPNIPAddrPrefix) IPPrefixLen() uint8 { + return l.Length - 8*uint8(l.Labels.Len()+l.RD.Len()) +} + +func (l *LabeledVPNIPAddrPrefix) Len(options ...*MarshallingOption) int { + return 1 + l.Labels.Len() + l.RD.Len() + int((l.IPPrefixLen()+7)/8) +} + +func (l *LabeledVPNIPAddrPrefix) String() string { + return fmt.Sprintf("%s:%s", l.RD, l.IPPrefix()) +} + +func (l *LabeledVPNIPAddrPrefix) IPPrefix() string { + masklen := l.IPAddrPrefixDefault.Length - uint8(8*(l.Labels.Len()+l.RD.Len())) + return fmt.Sprintf("%s/%d", l.IPAddrPrefixDefault.Prefix, masklen) +} + +func (l *LabeledVPNIPAddrPrefix) MarshalJSON() ([]byte, error) { + masklen := l.IPAddrPrefixDefault.Length - uint8(8*(l.Labels.Len()+l.RD.Len())) + return json.Marshal(struct { + Prefix string `json:"prefix"` + Labels []uint32 `json:"labels"` + RD RouteDistinguisherInterface `json:"rd"` + }{ + Prefix: fmt.Sprintf("%s/%d", l.IPAddrPrefixDefault.Prefix, masklen), + Labels: l.Labels.Labels, + RD: l.RD, + }) +} + +func NewLabeledVPNIPAddrPrefix(length uint8, prefix string, label MPLSLabelStack, rd RouteDistinguisherInterface) *LabeledVPNIPAddrPrefix { + rdlen := 0 + if rd != nil { + rdlen = rd.Len() + } + return &LabeledVPNIPAddrPrefix{ + IPAddrPrefixDefault{ + Length: length + uint8(8*(label.Len()+rdlen)), + Prefix: net.ParseIP(prefix).To4(), + }, + label, + rd, + 4, + } +} + +type LabeledVPNIPv6AddrPrefix struct { + LabeledVPNIPAddrPrefix +} + +func (l *LabeledVPNIPv6AddrPrefix) AFI() uint16 { + return AFI_IP6 +} + +func NewLabeledVPNIPv6AddrPrefix(length uint8, prefix string, label MPLSLabelStack, rd RouteDistinguisherInterface) *LabeledVPNIPv6AddrPrefix { + rdlen := 0 + if rd != nil { + rdlen = rd.Len() + } + return &LabeledVPNIPv6AddrPrefix{ + LabeledVPNIPAddrPrefix{ + IPAddrPrefixDefault{ + Length: length + uint8(8*(label.Len()+rdlen)), + Prefix: net.ParseIP(prefix), + }, + label, + rd, + 16, + }, + } +} + +type LabeledIPAddrPrefix struct { + IPAddrPrefixDefault + Labels MPLSLabelStack + addrlen uint8 +} + +func (r *LabeledIPAddrPrefix) AFI() uint16 { + return AFI_IP +} + +func (r *LabeledIPAddrPrefix) SAFI() uint8 { + return SAFI_MPLS_LABEL +} + +func (l *LabeledIPAddrPrefix) IPPrefixLen() uint8 { + return l.Length - 8*uint8(l.Labels.Len()) +} + +func (l *LabeledIPAddrPrefix) Len(options ...*MarshallingOption) int { + return 1 + l.Labels.Len() + int((l.IPPrefixLen()+7)/8) +} + +func (l *LabeledIPAddrPrefix) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + f := RF_IPv4_MPLS + if l.addrlen == 16 { + f = RF_IPv6_MPLS + } + if IsAddPathEnabled(true, f, options) { + var err error + data, err = l.decodePathIdentifier(data) + if err != nil { + return err + } + } + l.Length = uint8(data[0]) + data = data[1:] + l.Labels.DecodeFromBytes(data) + + if int(l.Length)-8*(l.Labels.Len()) < 0 { + l.Labels.Labels = []uint32{} + } + restbits := int(l.Length) - 8*(l.Labels.Len()) + data = data[l.Labels.Len():] + return l.decodePrefix(data, uint8(restbits), l.addrlen) +} + +func (l *LabeledIPAddrPrefix) Serialize(options ...*MarshallingOption) ([]byte, error) { + f := RF_IPv4_MPLS + if l.addrlen == 16 { + f = RF_IPv6_MPLS + } + var buf []byte + if IsAddPathEnabled(false, f, options) { + var err error + buf, err = l.serializeIdentifier() + if err != nil { + return nil, err + } + } + buf = append(buf, l.Length) + restbits := int(l.Length) - 8*(l.Labels.Len()) + lbuf, err := l.Labels.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, lbuf...) + pbuf, err := l.serializePrefix(uint8(restbits)) + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) + return buf, nil +} + +func (l *LabeledIPAddrPrefix) String() string { + prefix := l.Prefix.String() + if isIPv4MappedIPv6(l.Prefix) { + prefix = "::ffff:" + prefix + } + return fmt.Sprintf("%s/%d", prefix, int(l.Length)-l.Labels.Len()*8) +} + +func (l *LabeledIPAddrPrefix) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Prefix string `json:"prefix"` + Labels []uint32 `json:"labels"` + }{ + Prefix: l.String(), + Labels: l.Labels.Labels, + }) +} + +func NewLabeledIPAddrPrefix(length uint8, prefix string, label MPLSLabelStack) *LabeledIPAddrPrefix { + return &LabeledIPAddrPrefix{ + IPAddrPrefixDefault{ + Length: length + uint8(label.Len()*8), + Prefix: net.ParseIP(prefix).To4(), + }, + label, + 4, + } +} + +type LabeledIPv6AddrPrefix struct { + LabeledIPAddrPrefix +} + +func (l *LabeledIPv6AddrPrefix) AFI() uint16 { + return AFI_IP6 +} + +func NewLabeledIPv6AddrPrefix(length uint8, prefix string, label MPLSLabelStack) *LabeledIPv6AddrPrefix { + return &LabeledIPv6AddrPrefix{ + LabeledIPAddrPrefix{ + IPAddrPrefixDefault{ + Length: length + uint8(label.Len()*8), + Prefix: net.ParseIP(prefix), + }, + label, + 16, + }, + } +} + +type RouteTargetMembershipNLRI struct { + PrefixDefault + Length uint8 + AS uint32 + RouteTarget ExtendedCommunityInterface +} + +func (n *RouteTargetMembershipNLRI) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if IsAddPathEnabled(true, RF_RTC_UC, options) { + var err error + data, err = n.decodePathIdentifier(data) + if err != nil { + return err + } + } + if len(data) < 1 { + return NewMessageError(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST), nil, "prefix misses length field") + } + n.Length = data[0] + data = data[1 : n.Length/8+1] + if len(data) == 0 { + return nil + } else if len(data) != 12 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all RouteTargetMembershipNLRI bytes available") + } + n.AS = binary.BigEndian.Uint32(data[0:4]) + rt, err := ParseExtended(data[4:]) + n.RouteTarget = rt + if err != nil { + return err + } + return nil +} + +func (n *RouteTargetMembershipNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + var buf []byte + if IsAddPathEnabled(false, RF_RTC_UC, options) { + var err error + buf, err = n.serializeIdentifier() + if err != nil { + return nil, err + } + } + if n.RouteTarget == nil { + return append(buf, 0), nil + } + offset := len(buf) + buf = append(buf, make([]byte, 5)...) + buf[offset] = 96 + binary.BigEndian.PutUint32(buf[offset+1:], n.AS) + ebuf, err := n.RouteTarget.Serialize() + if err != nil { + return nil, err + } + return append(buf, ebuf...), nil +} + +func (n *RouteTargetMembershipNLRI) AFI() uint16 { + return AFI_IP +} + +func (n *RouteTargetMembershipNLRI) SAFI() uint8 { + return SAFI_ROUTE_TARGET_CONSTRAINTS +} + +func (n *RouteTargetMembershipNLRI) Len(options ...*MarshallingOption) int { + if n.AS == 0 && n.RouteTarget == nil { + return 1 + } + return 13 +} + +func (n *RouteTargetMembershipNLRI) String() string { + target := "default" + if n.RouteTarget != nil { + target = n.RouteTarget.String() + } + return fmt.Sprintf("%d:%s", n.AS, target) +} + +func (n *RouteTargetMembershipNLRI) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Prefix string `json:"prefix"` + }{ + Prefix: n.String(), + }) +} + +func NewRouteTargetMembershipNLRI(as uint32, target ExtendedCommunityInterface) *RouteTargetMembershipNLRI { + l := 12 * 8 + if as == 0 && target == nil { + l = 1 + } + return &RouteTargetMembershipNLRI{ + Length: uint8(l), + AS: as, + RouteTarget: target, + } +} + +type ESIType uint8 + +const ( + ESI_ARBITRARY ESIType = iota + ESI_LACP + ESI_MSTP + ESI_MAC + ESI_ROUTERID + ESI_AS +) + +type EthernetSegmentIdentifier struct { + Type ESIType + Value []byte +} + +func (esi *EthernetSegmentIdentifier) DecodeFromBytes(data []byte) error { + esi.Type = ESIType(data[0]) + esi.Value = data[1:10] + switch esi.Type { + case ESI_LACP, ESI_MSTP, ESI_ROUTERID, ESI_AS: + if esi.Value[8] != 0x00 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("invalid %s. last octet must be 0x00 (0x%02x)", esi.Type.String(), esi.Value[8])) + } + } + return nil +} + +func (esi *EthernetSegmentIdentifier) Serialize() ([]byte, error) { + buf := make([]byte, 10) + buf[0] = uint8(esi.Type) + copy(buf[1:], esi.Value) + return buf, nil +} + +func isZeroBuf(buf []byte) bool { + for _, b := range buf { + if b != 0 { + return false + } + } + return true +} + +func (esi *EthernetSegmentIdentifier) String() string { + toHexArray := func(data []byte) string { + // Converts byte slice into the colon separated hex values and the + // number of elements are 9 at most (excluding Type field). + values := make([]string, 0, 9) + for _, v := range data { + values = append(values, fmt.Sprintf("%02x", v)) + } + return strings.Join(values, ":") + } + + s := bytes.NewBuffer(make([]byte, 0, 64)) + s.WriteString(fmt.Sprintf("%s | ", esi.Type.String())) + switch esi.Type { + case ESI_LACP: + s.WriteString(fmt.Sprintf("system mac %s, ", net.HardwareAddr(esi.Value[:6]).String())) + s.WriteString(fmt.Sprintf("port key %d", binary.BigEndian.Uint16(esi.Value[6:8]))) + case ESI_MSTP: + s.WriteString(fmt.Sprintf("bridge mac %s, ", net.HardwareAddr(esi.Value[:6]).String())) + s.WriteString(fmt.Sprintf("priority %d", binary.BigEndian.Uint16(esi.Value[6:8]))) + case ESI_MAC: + s.WriteString(fmt.Sprintf("system mac %s, ", net.HardwareAddr(esi.Value[:6]).String())) + s.WriteString(fmt.Sprintf("local discriminator %d", uint32(esi.Value[6])<<16|uint32(esi.Value[7])<<8|uint32(esi.Value[8]))) + case ESI_ROUTERID: + s.WriteString(fmt.Sprintf("router id %s, ", net.IP(esi.Value[:4]))) + s.WriteString(fmt.Sprintf("local discriminator %d", binary.BigEndian.Uint32(esi.Value[4:8]))) + case ESI_AS: + s.WriteString(fmt.Sprintf("as %d, ", binary.BigEndian.Uint32(esi.Value[:4]))) + s.WriteString(fmt.Sprintf("local discriminator %d", binary.BigEndian.Uint32(esi.Value[4:8]))) + case ESI_ARBITRARY: + if isZeroBuf(esi.Value) { + return "single-homed" + } + fallthrough + default: + s.WriteString(toHexArray(esi.Value)) + } + return s.String() +} + +// Decode Ethernet Segment Identifier (ESI) from string slice. +// +// The first element of args should be the Type field (e.g., "ARBITRARY", +// "arbitrary", "ESI_ARBITRARY" or "esi_arbitrary") and "single-homed" is +// the special keyword for all zeroed ESI. +// For the "ARBITRARY" Value field (Type 0), it should be the colon separated +// hex values and the number of elements should be 9 at most. +// e.g.) args := []string{"ARBITRARY", "11:22:33:44:55:66:77:88:99"} +// For the other types, the Value field format is the similar to the string +// format of ESI. +// e.g.) args := []string{"lacp", "aa:bb:cc:dd:ee:ff", "100"} +func ParseEthernetSegmentIdentifier(args []string) (EthernetSegmentIdentifier, error) { + esi := EthernetSegmentIdentifier{} + argLen := len(args) + if argLen == 0 || args[0] == "single-homed" { + return esi, nil + } + + typeStr := strings.TrimPrefix(strings.ToUpper(args[0]), "ESI_") + switch typeStr { + case "ARBITRARY": + esi.Type = ESI_ARBITRARY + case "LACP": + esi.Type = ESI_LACP + case "MSTP": + esi.Type = ESI_MSTP + case "MAC": + esi.Type = ESI_MAC + case "ROUTERID": + esi.Type = ESI_ROUTERID + case "AS": + esi.Type = ESI_AS + default: + typ, err := strconv.ParseUint(args[0], 10, 8) + if err != nil { + return esi, fmt.Errorf("invalid esi type: %s", args[0]) + } + esi.Type = ESIType(typ) + } + + invalidEsiValuesError := fmt.Errorf("invalid esi values for type %s: %s", esi.Type.String(), args[1:]) + esi.Value = make([]byte, 9) + switch esi.Type { + case ESI_LACP: + fallthrough + case ESI_MSTP: + if argLen < 3 { + return esi, invalidEsiValuesError + } + // MAC + mac, err := net.ParseMAC(args[1]) + if err != nil { + return esi, invalidEsiValuesError + } + copy(esi.Value[0:6], mac) + // Port Key or Bridge Priority + i, err := strconv.ParseUint(args[2], 10, 16) + if err != nil { + return esi, invalidEsiValuesError + } + binary.BigEndian.PutUint16(esi.Value[6:8], uint16(i)) + case ESI_MAC: + if argLen < 3 { + return esi, invalidEsiValuesError + } + // MAC + mac, err := net.ParseMAC(args[1]) + if err != nil { + return esi, invalidEsiValuesError + } + copy(esi.Value[0:6], mac) + // Local Discriminator + i, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + return esi, invalidEsiValuesError + } + iBuf := make([]byte, 4) + binary.BigEndian.PutUint32(iBuf, uint32(i)) + copy(esi.Value[6:9], iBuf[1:4]) + case ESI_ROUTERID: + if argLen < 3 { + return esi, invalidEsiValuesError + } + // Router ID + ip := net.ParseIP(args[1]) + if ip == nil || ip.To4() == nil { + return esi, invalidEsiValuesError + } + copy(esi.Value[0:4], ip.To4()) + // Local Discriminator + i, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + return esi, invalidEsiValuesError + } + binary.BigEndian.PutUint32(esi.Value[4:8], uint32(i)) + case ESI_AS: + if argLen < 3 { + return esi, invalidEsiValuesError + } + // AS + as, err := strconv.ParseUint(args[1], 10, 32) + if err != nil { + return esi, invalidEsiValuesError + } + binary.BigEndian.PutUint32(esi.Value[0:4], uint32(as)) + // Local Discriminator + i, err := strconv.ParseUint(args[2], 10, 32) + if err != nil { + return esi, invalidEsiValuesError + } + binary.BigEndian.PutUint32(esi.Value[4:8], uint32(i)) + case ESI_ARBITRARY: + fallthrough + default: + if argLen < 2 { + // Assumes the Value field is omitted + break + } + values := make([]byte, 0, 9) + for _, e := range strings.SplitN(args[1], ":", 9) { + v, err := strconv.ParseUint(e, 16, 16) + if err != nil { + return esi, invalidEsiValuesError + } + values = append(values, byte(v)) + } + copy(esi.Value, values) + } + + return esi, nil +} + +// +// I-D bess-evpn-overlay-01 +// +// 5.1.3 Constructing EVPN BGP Routes +// +// For the balance of this memo, the MPLS label field will be +// referred to as the VNI/VSID field. The VNI/VSID field is used for +// both local and global VNIs/VSIDs, and for either case the entire 24- +// bit field is used to encode the VNI/VSID value. +// +// We can't use type MPLSLabelStack for EVPN NLRI, because EVPN NLRI's MPLS +// field can be filled with VXLAN VNI. In that case, we must avoid modifying +// bottom of stack bit. +// + +func labelDecode(data []byte) (uint32, error) { + if len(data) < 3 { + return 0, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all Label bytes available") + } + return uint32(data[0])<<16 | uint32(data[1])<<8 | uint32(data[2]), nil +} + +func labelSerialize(label uint32) ([]byte, error) { + if label > 0xffffff { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Out of range Label: %d", label)) + } + buf := make([]byte, 3) + buf[0] = byte((label >> 16) & 0xff) + buf[1] = byte((label >> 8) & 0xff) + buf[2] = byte(label & 0xff) + return buf, nil +} + +type EVPNEthernetAutoDiscoveryRoute struct { + RD RouteDistinguisherInterface + ESI EthernetSegmentIdentifier + ETag uint32 + Label uint32 +} + +func (er *EVPNEthernetAutoDiscoveryRoute) Len() int { + // RD(8) + ESI(10) + ETag(4) + Label(3) + return 25 +} + +func (er *EVPNEthernetAutoDiscoveryRoute) DecodeFromBytes(data []byte) error { + er.RD = GetRouteDistinguisher(data) + data = data[er.RD.Len():] + err := er.ESI.DecodeFromBytes(data) + if err != nil { + return err + } + data = data[10:] + er.ETag = binary.BigEndian.Uint32(data[0:4]) + data = data[4:] + if er.Label, err = labelDecode(data); err != nil { + return err + } + return nil +} + +func (er *EVPNEthernetAutoDiscoveryRoute) Serialize() ([]byte, error) { + var buf []byte + var err error + if er.RD != nil { + buf, err = er.RD.Serialize() + if err != nil { + return nil, err + } + } else { + buf = make([]byte, 8) + } + tbuf, err := er.ESI.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + + tbuf = make([]byte, 4) + binary.BigEndian.PutUint32(tbuf, er.ETag) + buf = append(buf, tbuf...) + + tbuf, err = labelSerialize(er.Label) + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + + return buf, nil +} + +func (er *EVPNEthernetAutoDiscoveryRoute) String() string { + // RFC7432: BGP MPLS-Based Ethernet VPN + // 7.1. Ethernet Auto-discovery Route + // For the purpose of BGP route key processing, only the Ethernet + // Segment Identifier and the Ethernet Tag ID are considered to be part + // of the prefix in the NLRI. The MPLS Label field is to be treated as + // a route attribute as opposed to being part of the route. + return fmt.Sprintf("[type:A-D][rd:%s][esi:%s][etag:%d]", er.RD, er.ESI.String(), er.ETag) +} + +func (er *EVPNEthernetAutoDiscoveryRoute) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RD RouteDistinguisherInterface `json:"rd"` + ESI string `json:"esi"` + Etag uint32 `json:"etag"` + Label uint32 `json:"label"` + }{ + RD: er.RD, + ESI: er.ESI.String(), + Etag: er.ETag, + Label: er.Label, + }) +} + +func (er *EVPNEthernetAutoDiscoveryRoute) rd() RouteDistinguisherInterface { + return er.RD +} + +func NewEVPNEthernetAutoDiscoveryRoute(rd RouteDistinguisherInterface, esi EthernetSegmentIdentifier, etag uint32, label uint32) *EVPNNLRI { + return NewEVPNNLRI(EVPN_ROUTE_TYPE_ETHERNET_AUTO_DISCOVERY, &EVPNEthernetAutoDiscoveryRoute{ + RD: rd, + ESI: esi, + ETag: etag, + Label: label, + }) +} + +type EVPNMacIPAdvertisementRoute struct { + RD RouteDistinguisherInterface + ESI EthernetSegmentIdentifier + ETag uint32 + MacAddressLength uint8 + MacAddress net.HardwareAddr + IPAddressLength uint8 + IPAddress net.IP + Labels []uint32 +} + +func (er *EVPNMacIPAdvertisementRoute) Len() int { + // RD(8) + ESI(10) + ETag(4) + MacAddressLength(1) + MacAddress(6) + // + IPAddressLength(1) + IPAddress(0, 4 or 16) + Labels(3 or 6) + return 30 + int(er.IPAddressLength)/8 + len(er.Labels)*3 +} + +func (er *EVPNMacIPAdvertisementRoute) DecodeFromBytes(data []byte) error { + er.RD = GetRouteDistinguisher(data) + data = data[er.RD.Len():] + err := er.ESI.DecodeFromBytes(data) + if err != nil { + return err + } + data = data[10:] + er.ETag = binary.BigEndian.Uint32(data[0:4]) + data = data[4:] + er.MacAddressLength = data[0] + er.MacAddress = net.HardwareAddr(data[1:7]) + er.IPAddressLength = data[7] + data = data[8:] + if er.IPAddressLength == 32 || er.IPAddressLength == 128 { + er.IPAddress = net.IP(data[0:((er.IPAddressLength) / 8)]) + } else if er.IPAddressLength != 0 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid IP address length: %d", er.IPAddressLength)) + } + data = data[(er.IPAddressLength / 8):] + var label uint32 + if label, err = labelDecode(data); err != nil { + return err + } + er.Labels = append(er.Labels, label) + data = data[3:] + if len(data) == 3 { + if label, err = labelDecode(data); err != nil { + return err + } + er.Labels = append(er.Labels, label) + } + return nil +} + +func (er *EVPNMacIPAdvertisementRoute) Serialize() ([]byte, error) { + var buf []byte + var err error + if er.RD != nil { + buf, err = er.RD.Serialize() + if err != nil { + return nil, err + } + } else { + buf = make([]byte, 8) + } + + tbuf, err := er.ESI.Serialize() + if err != nil { + return nil, err + } + + buf = append(buf, tbuf...) + tbuf = make([]byte, 4) + binary.BigEndian.PutUint32(tbuf, er.ETag) + buf = append(buf, tbuf...) + tbuf = make([]byte, 7) + tbuf[0] = er.MacAddressLength + copy(tbuf[1:], er.MacAddress) + buf = append(buf, tbuf...) + + buf = append(buf, er.IPAddressLength) + switch er.IPAddressLength { + case 0: + // IP address omitted + case 32: + buf = append(buf, []byte(er.IPAddress.To4())...) + case 128: + buf = append(buf, []byte(er.IPAddress.To16())...) + default: + return nil, fmt.Errorf("Invalid IP address length: %d", er.IPAddressLength) + } + + for _, l := range er.Labels { + tbuf, err = labelSerialize(l) + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + } + return buf, nil +} + +func (er *EVPNMacIPAdvertisementRoute) String() string { + // RFC7432: BGP MPLS-Based Ethernet VPN + // 7.2. MAC/IP Advertisement Route + // For the purpose of BGP route key processing, only the Ethernet Tag + // ID, MAC Address Length, MAC Address, IP Address Length, and IP + // Address fields are considered to be part of the prefix in the NLRI. + // The Ethernet Segment Identifier, MPLS Label1, and MPLS Label2 fields + // are to be treated as route attributes as opposed to being part of the + // "route". + return fmt.Sprintf("[type:macadv][rd:%s][etag:%d][mac:%s][ip:%s]", er.RD, er.ETag, er.MacAddress, er.IPAddress) +} + +func (er *EVPNMacIPAdvertisementRoute) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RD RouteDistinguisherInterface `json:"rd"` + ESI string `json:"esi"` + Etag uint32 `json:"etag"` + MacAddress string `json:"mac"` + IPAddress string `json:"ip"` + Labels []uint32 `json:"labels"` + }{ + RD: er.RD, + ESI: er.ESI.String(), + Etag: er.ETag, + MacAddress: er.MacAddress.String(), + IPAddress: er.IPAddress.String(), + Labels: er.Labels, + }) +} + +func (er *EVPNMacIPAdvertisementRoute) rd() RouteDistinguisherInterface { + return er.RD +} + +func NewEVPNMacIPAdvertisementRoute(rd RouteDistinguisherInterface, esi EthernetSegmentIdentifier, etag uint32, macAddress string, ipAddress string, labels []uint32) *EVPNNLRI { + mac, _ := net.ParseMAC(macAddress) + var ipLen uint8 + ip := net.ParseIP(ipAddress) + if ip != nil { + if ipv4 := ip.To4(); ipv4 != nil { + ipLen = 32 + ip = ipv4 + } else { + ipLen = 128 + } + } + return NewEVPNNLRI(EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT, &EVPNMacIPAdvertisementRoute{ + RD: rd, + ESI: esi, + ETag: etag, + MacAddressLength: 6, + MacAddress: mac, + IPAddressLength: ipLen, + IPAddress: ip, + Labels: labels, + }) +} + +type EVPNMulticastEthernetTagRoute struct { + RD RouteDistinguisherInterface + ETag uint32 + IPAddressLength uint8 + IPAddress net.IP +} + +func (er *EVPNMulticastEthernetTagRoute) Len() int { + // RD(8) + ETag(4) + IPAddressLength(1) + IPAddress(4 or 16) + return 13 + int(er.IPAddressLength)/8 +} + +func (er *EVPNMulticastEthernetTagRoute) DecodeFromBytes(data []byte) error { + er.RD = GetRouteDistinguisher(data) + data = data[er.RD.Len():] + er.ETag = binary.BigEndian.Uint32(data[0:4]) + er.IPAddressLength = data[4] + data = data[5:] + if er.IPAddressLength == 32 || er.IPAddressLength == 128 { + er.IPAddress = net.IP(data[:er.IPAddressLength/8]) + } else { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid IP address length: %d", er.IPAddressLength)) + } + return nil +} + +func (er *EVPNMulticastEthernetTagRoute) Serialize() ([]byte, error) { + var buf []byte + var err error + if er.RD != nil { + buf, err = er.RD.Serialize() + if err != nil { + return nil, err + } + } else { + buf = make([]byte, 8) + } + tbuf := make([]byte, 4) + binary.BigEndian.PutUint32(tbuf, er.ETag) + buf = append(buf, tbuf...) + buf = append(buf, er.IPAddressLength) + switch er.IPAddressLength { + case 32: + buf = append(buf, []byte(er.IPAddress.To4())...) + case 128: + buf = append(buf, []byte(er.IPAddress.To16())...) + default: + return nil, fmt.Errorf("Invalid IP address length: %d", er.IPAddressLength) + } + if err != nil { + return nil, err + } + return buf, nil +} + +func (er *EVPNMulticastEthernetTagRoute) String() string { + // RFC7432: BGP MPLS-Based Ethernet VPN + // 7.3. Inclusive Multicast Ethernet Tag Route + // ...(snip)... For the purpose of BGP route key + // processing, only the Ethernet Tag ID, IP Address Length, and + // Originating Router's IP Address fields are considered to be part of + // the prefix in the NLRI. + return fmt.Sprintf("[type:multicast][rd:%s][etag:%d][ip:%s]", er.RD, er.ETag, er.IPAddress) +} + +func (er *EVPNMulticastEthernetTagRoute) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RD RouteDistinguisherInterface `json:"rd"` + Etag uint32 `json:"etag"` + IPAddress string `json:"ip"` + }{ + RD: er.RD, + Etag: er.ETag, + IPAddress: er.IPAddress.String(), + }) +} + +func (er *EVPNMulticastEthernetTagRoute) rd() RouteDistinguisherInterface { + return er.RD +} + +func NewEVPNMulticastEthernetTagRoute(rd RouteDistinguisherInterface, etag uint32, ipAddress string) *EVPNNLRI { + ipLen := uint8(32) + ip := net.ParseIP(ipAddress) + if ipv4 := ip.To4(); ipv4 != nil { + ip = ipv4 + } else { + ipLen = 128 + } + return NewEVPNNLRI(EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG, &EVPNMulticastEthernetTagRoute{ + RD: rd, + ETag: etag, + IPAddressLength: ipLen, + IPAddress: ip, + }) +} + +type EVPNEthernetSegmentRoute struct { + RD RouteDistinguisherInterface + ESI EthernetSegmentIdentifier + IPAddressLength uint8 + IPAddress net.IP +} + +func (er *EVPNEthernetSegmentRoute) Len() int { + // RD(8) + ESI(10) + IPAddressLength(1) + IPAddress(4 or 16) + return 19 + int(er.IPAddressLength)/8 +} + +func (er *EVPNEthernetSegmentRoute) DecodeFromBytes(data []byte) error { + er.RD = GetRouteDistinguisher(data) + data = data[er.RD.Len():] + er.ESI.DecodeFromBytes(data) + data = data[10:] + er.IPAddressLength = data[0] + data = data[1:] + if er.IPAddressLength == 32 || er.IPAddressLength == 128 { + er.IPAddress = net.IP(data[:er.IPAddressLength/8]) + } else { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid IP address length: %d", er.IPAddressLength)) + } + return nil +} + +func (er *EVPNEthernetSegmentRoute) Serialize() ([]byte, error) { + var buf []byte + var err error + if er.RD != nil { + buf, err = er.RD.Serialize() + if err != nil { + return nil, err + } + } else { + buf = make([]byte, 8) + } + tbuf, err := er.ESI.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + buf = append(buf, er.IPAddressLength) + switch er.IPAddressLength { + case 32: + buf = append(buf, []byte(er.IPAddress.To4())...) + case 128: + buf = append(buf, []byte(er.IPAddress.To16())...) + default: + return nil, fmt.Errorf("Invalid IP address length: %d", er.IPAddressLength) + } + return buf, nil +} + +func (er *EVPNEthernetSegmentRoute) String() string { + // RFC7432: BGP MPLS-Based Ethernet VPN + // 7.4. Ethernet Segment Route + // For the purpose of BGP route key processing, only the Ethernet + // Segment ID, IP Address Length, and Originating Router's IP Address + // fields are considered to be part of the prefix in the NLRI. + return fmt.Sprintf("[type:esi][rd:%s][esi:%s][ip:%s]", er.RD, er.ESI.String(), er.IPAddress) +} + +func (er *EVPNEthernetSegmentRoute) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RD RouteDistinguisherInterface `json:"rd"` + ESI string `json:"esi"` + IPAddress string `json:"ip"` + }{ + RD: er.RD, + ESI: er.ESI.String(), + IPAddress: er.IPAddress.String(), + }) +} + +func (er *EVPNEthernetSegmentRoute) rd() RouteDistinguisherInterface { + return er.RD +} + +func NewEVPNEthernetSegmentRoute(rd RouteDistinguisherInterface, esi EthernetSegmentIdentifier, ipAddress string) *EVPNNLRI { + ipLen := uint8(32) + ip := net.ParseIP(ipAddress) + if ipv4 := ip.To4(); ipv4 != nil { + ip = ipv4 + } else { + ipLen = 128 + } + return NewEVPNNLRI(EVPN_ETHERNET_SEGMENT_ROUTE, &EVPNEthernetSegmentRoute{ + RD: rd, + ESI: esi, + IPAddressLength: ipLen, + IPAddress: ip, + }) +} + +type EVPNIPPrefixRoute struct { + RD RouteDistinguisherInterface + ESI EthernetSegmentIdentifier + ETag uint32 + IPPrefixLength uint8 + IPPrefix net.IP + GWIPAddress net.IP + Label uint32 +} + +func (er *EVPNIPPrefixRoute) Len() int { + if er.IPPrefix.To4() != nil { + return 34 + } + return 58 +} + +func (er *EVPNIPPrefixRoute) DecodeFromBytes(data []byte) error { + addrLen := net.IPv4len + switch len(data) { + case 34: + // RD(8) + ESI(10) + ETag(4) + IPPrefixLength(1) + IPv4 Prefix(4) + GW IPv4(4) + Label(3) + case 58: + // RD(8) + ESI(10) + ETag(4) + IPPrefixLength(1) + IPv6 Prefix(16) + GW IPv6(16) + Label(3) + addrLen = net.IPv6len + default: + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all EVPN IP Prefix Route bytes available") + } + + er.RD = GetRouteDistinguisher(data[0:8]) + + err := er.ESI.DecodeFromBytes(data[8:18]) + if err != nil { + return err + } + + er.ETag = binary.BigEndian.Uint32(data[18:22]) + + er.IPPrefixLength = data[22] + + offset := 23 // RD(8) + ESI(10) + ETag(4) + IPPrefixLength(1) + er.IPPrefix = data[offset : offset+addrLen] + offset += addrLen + + er.GWIPAddress = data[offset : offset+addrLen] + offset += addrLen + + if er.Label, err = labelDecode(data[offset : offset+3]); err != nil { + return err + } + //offset += 3 + + return nil +} + +func (er *EVPNIPPrefixRoute) Serialize() ([]byte, error) { + buf := make([]byte, 23) // RD(8) + ESI(10) + ETag(4) + IPPrefixLength(1) + + if er.RD != nil { + tbuf, err := er.RD.Serialize() + if err != nil { + return nil, err + } + copy(buf[0:8], tbuf) + } + + tbuf, err := er.ESI.Serialize() + if err != nil { + return nil, err + } + copy(buf[8:18], tbuf) + + binary.BigEndian.PutUint32(buf[18:22], er.ETag) + + buf[22] = er.IPPrefixLength + + if er.IPPrefix == nil { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("IP Prefix is nil")) + } else if er.IPPrefix.To4() != nil { + buf = append(buf, er.IPPrefix.To4()...) + if er.GWIPAddress == nil { + // draft-ietf-bess-evpn-prefix-advertisement: IP Prefix Advertisement in EVPN + // The GW IP field SHOULD be zero if it is not used as an Overlay Index. + er.GWIPAddress = net.IPv4zero + } + buf = append(buf, er.GWIPAddress.To4()...) + } else { + buf = append(buf, er.IPPrefix.To16()...) + if er.GWIPAddress == nil { + er.GWIPAddress = net.IPv6zero + } + buf = append(buf, er.GWIPAddress.To16()...) + } + + tbuf, err = labelSerialize(er.Label) + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + + return buf, nil +} + +func (er *EVPNIPPrefixRoute) String() string { + // draft-ietf-bess-evpn-prefix-advertisement: IP Prefix Advertisement in EVPN + // 3.1 IP Prefix Route Encoding + // The RD, Eth-Tag ID, IP Prefix Length and IP Prefix will be part of + // the route key used by BGP to compare routes. The rest of the fields + // will not be part of the route key. + return fmt.Sprintf("[type:Prefix][rd:%s][etag:%d][prefix:%s/%d]", er.RD, er.ETag, er.IPPrefix, er.IPPrefixLength) +} + +func (er *EVPNIPPrefixRoute) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + RD RouteDistinguisherInterface `json:"rd"` + ESI string `json:"esi"` + Etag uint32 `json:"etag"` + Prefix string `json:"prefix"` + Gateway string `json:"gateway"` + Label uint32 `json:"label"` + }{ + RD: er.RD, + ESI: er.ESI.String(), + Etag: er.ETag, + Prefix: fmt.Sprintf("%s/%d", er.IPPrefix, er.IPPrefixLength), + Gateway: er.GWIPAddress.String(), + Label: er.Label, + }) +} + +func (er *EVPNIPPrefixRoute) rd() RouteDistinguisherInterface { + return er.RD +} + +func NewEVPNIPPrefixRoute(rd RouteDistinguisherInterface, esi EthernetSegmentIdentifier, etag uint32, ipPrefixLength uint8, ipPrefix string, gateway string, label uint32) *EVPNNLRI { + ip := net.ParseIP(ipPrefix) + gw := net.ParseIP(gateway) + if ipv4 := ip.To4(); ipv4 != nil { + ip = ipv4 + gw = gw.To4() + } + return NewEVPNNLRI(EVPN_IP_PREFIX, &EVPNIPPrefixRoute{ + RD: rd, + ESI: esi, + ETag: etag, + IPPrefixLength: ipPrefixLength, + IPPrefix: ip, + GWIPAddress: gw, + Label: label, + }) +} + +type EVPNRouteTypeInterface interface { + Len() int + DecodeFromBytes([]byte) error + Serialize() ([]byte, error) + String() string + rd() RouteDistinguisherInterface + MarshalJSON() ([]byte, error) +} + +func getEVPNRouteType(t uint8) (EVPNRouteTypeInterface, error) { + switch t { + case EVPN_ROUTE_TYPE_ETHERNET_AUTO_DISCOVERY: + return &EVPNEthernetAutoDiscoveryRoute{}, nil + case EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT: + return &EVPNMacIPAdvertisementRoute{}, nil + case EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG: + return &EVPNMulticastEthernetTagRoute{}, nil + case EVPN_ETHERNET_SEGMENT_ROUTE: + return &EVPNEthernetSegmentRoute{}, nil + case EVPN_IP_PREFIX: + return &EVPNIPPrefixRoute{}, nil + } + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Unknown EVPN Route type: %d", t)) +} + +const ( + EVPN_ROUTE_TYPE_ETHERNET_AUTO_DISCOVERY = 1 + EVPN_ROUTE_TYPE_MAC_IP_ADVERTISEMENT = 2 + EVPN_INCLUSIVE_MULTICAST_ETHERNET_TAG = 3 + EVPN_ETHERNET_SEGMENT_ROUTE = 4 + EVPN_IP_PREFIX = 5 +) + +type EVPNNLRI struct { + PrefixDefault + RouteType uint8 + Length uint8 + RouteTypeData EVPNRouteTypeInterface +} + +func (n *EVPNNLRI) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if IsAddPathEnabled(true, RF_EVPN, options) { + var err error + data, err = n.decodePathIdentifier(data) + if err != nil { + return err + } + } + if len(data) < 2 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all EVPNNLRI bytes available") + } + n.RouteType = data[0] + n.Length = data[1] + data = data[2:] + if len(data) < int(n.Length) { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all EVPNNLRI Route type bytes available") + } + r, err := getEVPNRouteType(n.RouteType) + if err != nil { + return err + } + n.RouteTypeData = r + return n.RouteTypeData.DecodeFromBytes(data[:n.Length]) +} + +func (n *EVPNNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + var buf []byte + if IsAddPathEnabled(false, RF_EVPN, options) { + var err error + buf, err = n.serializeIdentifier() + if err != nil { + return nil, err + } + } + offset := len(buf) + buf = append(buf, make([]byte, 2)...) + buf[offset] = n.RouteType + tbuf, err := n.RouteTypeData.Serialize() + buf[offset+1] = n.Length + if err != nil { + return nil, err + } + return append(buf, tbuf...), nil +} + +func (n *EVPNNLRI) AFI() uint16 { + return AFI_L2VPN +} + +func (n *EVPNNLRI) SAFI() uint8 { + return SAFI_EVPN +} + +func (n *EVPNNLRI) Len(options ...*MarshallingOption) int { + return int(n.Length) + 2 +} + +func (n *EVPNNLRI) String() string { + if n.RouteTypeData != nil { + return n.RouteTypeData.String() + } + return fmt.Sprintf("%d:%d", n.RouteType, n.Length) +} + +func (n *EVPNNLRI) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint8 `json:"type"` + Value EVPNRouteTypeInterface `json:"value"` + }{ + Type: n.RouteType, + Value: n.RouteTypeData, + }) +} + +func (n *EVPNNLRI) RD() RouteDistinguisherInterface { + return n.RouteTypeData.rd() +} + +func NewEVPNNLRI(routeType uint8, routeTypeData EVPNRouteTypeInterface) *EVPNNLRI { + var l uint8 + if routeTypeData != nil { + l = uint8(routeTypeData.Len()) + } + return &EVPNNLRI{ + RouteType: routeType, + Length: l, + RouteTypeData: routeTypeData, + } +} + +type EncapNLRI struct { + IPAddrPrefixDefault + addrlen uint8 +} + +func (n *EncapNLRI) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if n.addrlen == 0 { + n.addrlen = 4 + } + f := RF_IPv4_ENCAP + if n.addrlen == 16 { + f = RF_IPv6_ENCAP + } + if IsAddPathEnabled(true, f, options) { + var err error + data, err = n.decodePathIdentifier(data) + if err != nil { + return err + } + } + if len(data) < 4 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "prefix misses length field") + } + n.Length = data[0] + if n.addrlen == 0 { + n.addrlen = 4 + } + return n.decodePrefix(data[1:], n.Length, n.addrlen) +} + +func (n *EncapNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + var buf []byte + f := RF_IPv4_ENCAP + if n.addrlen == 16 { + f = RF_IPv6_ENCAP + } + if IsAddPathEnabled(false, f, options) { + var err error + buf, err = n.serializeIdentifier() + if err != nil { + return nil, err + } + } + if n.Prefix.To4() != nil { + buf = append(buf, net.IPv4len*8) + n.Prefix = n.Prefix.To4() + } else { + buf = append(buf, net.IPv6len*8) + } + n.Length = buf[len(buf)-1] + pbuf, err := n.serializePrefix(n.Length) + if err != nil { + return nil, err + } + return append(buf, pbuf...), nil +} + +func (n *EncapNLRI) String() string { + return n.Prefix.String() +} + +func (n *EncapNLRI) AFI() uint16 { + return AFI_IP +} + +func (n *EncapNLRI) SAFI() uint8 { + return SAFI_ENCAPSULATION +} + +func (n *EncapNLRI) Len(options ...*MarshallingOption) int { + return 1 + len(n.Prefix) +} + +func NewEncapNLRI(endpoint string) *EncapNLRI { + return &EncapNLRI{ + IPAddrPrefixDefault{Length: 32, Prefix: net.ParseIP(endpoint).To4()}, + 4, + } +} + +type Encapv6NLRI struct { + EncapNLRI +} + +func (n *Encapv6NLRI) AFI() uint16 { + return AFI_IP6 +} + +func NewEncapv6NLRI(endpoint string) *Encapv6NLRI { + return &Encapv6NLRI{ + EncapNLRI{ + IPAddrPrefixDefault{Length: 128, Prefix: net.ParseIP(endpoint)}, + 16, + }, + } +} + +type BGPFlowSpecType uint8 + +const ( + FLOW_SPEC_TYPE_UNKNOWN BGPFlowSpecType = iota + FLOW_SPEC_TYPE_DST_PREFIX + FLOW_SPEC_TYPE_SRC_PREFIX + FLOW_SPEC_TYPE_IP_PROTO + FLOW_SPEC_TYPE_PORT + FLOW_SPEC_TYPE_DST_PORT + FLOW_SPEC_TYPE_SRC_PORT + FLOW_SPEC_TYPE_ICMP_TYPE + FLOW_SPEC_TYPE_ICMP_CODE + FLOW_SPEC_TYPE_TCP_FLAG + FLOW_SPEC_TYPE_PKT_LEN + FLOW_SPEC_TYPE_DSCP + FLOW_SPEC_TYPE_FRAGMENT + FLOW_SPEC_TYPE_LABEL + FLOW_SPEC_TYPE_ETHERNET_TYPE // 14 + FLOW_SPEC_TYPE_SRC_MAC + FLOW_SPEC_TYPE_DST_MAC + FLOW_SPEC_TYPE_LLC_DSAP + FLOW_SPEC_TYPE_LLC_SSAP + FLOW_SPEC_TYPE_LLC_CONTROL + FLOW_SPEC_TYPE_SNAP + FLOW_SPEC_TYPE_VID + FLOW_SPEC_TYPE_COS + FLOW_SPEC_TYPE_INNER_VID + FLOW_SPEC_TYPE_INNER_COS +) + +var FlowSpecNameMap = map[BGPFlowSpecType]string{ + FLOW_SPEC_TYPE_UNKNOWN: "unknown", + FLOW_SPEC_TYPE_DST_PREFIX: "destination", + FLOW_SPEC_TYPE_SRC_PREFIX: "source", + FLOW_SPEC_TYPE_IP_PROTO: "protocol", + FLOW_SPEC_TYPE_PORT: "port", + FLOW_SPEC_TYPE_DST_PORT: "destination-port", + FLOW_SPEC_TYPE_SRC_PORT: "source-port", + FLOW_SPEC_TYPE_ICMP_TYPE: "icmp-type", + FLOW_SPEC_TYPE_ICMP_CODE: "icmp-code", + FLOW_SPEC_TYPE_TCP_FLAG: "tcp-flags", + FLOW_SPEC_TYPE_PKT_LEN: "packet-length", + FLOW_SPEC_TYPE_DSCP: "dscp", + FLOW_SPEC_TYPE_FRAGMENT: "fragment", + FLOW_SPEC_TYPE_LABEL: "label", + FLOW_SPEC_TYPE_ETHERNET_TYPE: "ether-type", + FLOW_SPEC_TYPE_SRC_MAC: "source-mac", + FLOW_SPEC_TYPE_DST_MAC: "destination-mac", + FLOW_SPEC_TYPE_LLC_DSAP: "llc-dsap", + FLOW_SPEC_TYPE_LLC_SSAP: "llc-ssap", + FLOW_SPEC_TYPE_LLC_CONTROL: "llc-control", + FLOW_SPEC_TYPE_SNAP: "snap", + FLOW_SPEC_TYPE_VID: "vid", + FLOW_SPEC_TYPE_COS: "cos", + FLOW_SPEC_TYPE_INNER_VID: "inner-vid", + FLOW_SPEC_TYPE_INNER_COS: "inner-cos", +} + +var FlowSpecValueMap = map[string]BGPFlowSpecType{ + FlowSpecNameMap[FLOW_SPEC_TYPE_DST_PREFIX]: FLOW_SPEC_TYPE_DST_PREFIX, + FlowSpecNameMap[FLOW_SPEC_TYPE_SRC_PREFIX]: FLOW_SPEC_TYPE_SRC_PREFIX, + FlowSpecNameMap[FLOW_SPEC_TYPE_IP_PROTO]: FLOW_SPEC_TYPE_IP_PROTO, + FlowSpecNameMap[FLOW_SPEC_TYPE_PORT]: FLOW_SPEC_TYPE_PORT, + FlowSpecNameMap[FLOW_SPEC_TYPE_DST_PORT]: FLOW_SPEC_TYPE_DST_PORT, + FlowSpecNameMap[FLOW_SPEC_TYPE_SRC_PORT]: FLOW_SPEC_TYPE_SRC_PORT, + FlowSpecNameMap[FLOW_SPEC_TYPE_ICMP_TYPE]: FLOW_SPEC_TYPE_ICMP_TYPE, + FlowSpecNameMap[FLOW_SPEC_TYPE_ICMP_CODE]: FLOW_SPEC_TYPE_ICMP_CODE, + FlowSpecNameMap[FLOW_SPEC_TYPE_TCP_FLAG]: FLOW_SPEC_TYPE_TCP_FLAG, + FlowSpecNameMap[FLOW_SPEC_TYPE_PKT_LEN]: FLOW_SPEC_TYPE_PKT_LEN, + FlowSpecNameMap[FLOW_SPEC_TYPE_DSCP]: FLOW_SPEC_TYPE_DSCP, + FlowSpecNameMap[FLOW_SPEC_TYPE_FRAGMENT]: FLOW_SPEC_TYPE_FRAGMENT, + FlowSpecNameMap[FLOW_SPEC_TYPE_LABEL]: FLOW_SPEC_TYPE_LABEL, + FlowSpecNameMap[FLOW_SPEC_TYPE_ETHERNET_TYPE]: FLOW_SPEC_TYPE_ETHERNET_TYPE, + FlowSpecNameMap[FLOW_SPEC_TYPE_SRC_MAC]: FLOW_SPEC_TYPE_SRC_MAC, + FlowSpecNameMap[FLOW_SPEC_TYPE_DST_MAC]: FLOW_SPEC_TYPE_DST_MAC, + FlowSpecNameMap[FLOW_SPEC_TYPE_LLC_DSAP]: FLOW_SPEC_TYPE_LLC_DSAP, + FlowSpecNameMap[FLOW_SPEC_TYPE_LLC_SSAP]: FLOW_SPEC_TYPE_LLC_SSAP, + FlowSpecNameMap[FLOW_SPEC_TYPE_LLC_CONTROL]: FLOW_SPEC_TYPE_LLC_CONTROL, + FlowSpecNameMap[FLOW_SPEC_TYPE_SNAP]: FLOW_SPEC_TYPE_SNAP, + FlowSpecNameMap[FLOW_SPEC_TYPE_VID]: FLOW_SPEC_TYPE_VID, + FlowSpecNameMap[FLOW_SPEC_TYPE_COS]: FLOW_SPEC_TYPE_COS, + FlowSpecNameMap[FLOW_SPEC_TYPE_INNER_VID]: FLOW_SPEC_TYPE_INNER_VID, + FlowSpecNameMap[FLOW_SPEC_TYPE_INNER_COS]: FLOW_SPEC_TYPE_INNER_COS, +} + +// Joins the given and args into a single string and normalize it. +// Example: +// args := []string{" & <=80", " tcp != udp ", " =! SA & =U! F", " = is-fragment+last-fragment"} +// fmt.Printf("%q", normalizeFlowSpecOpValues(args)) +// >>> ["<=80" "tcp" "!=udp" "=!SA" "&=U" "!F" "=is-fragment+last-fragment"] +func normalizeFlowSpecOpValues(args []string) []string { + // Extracts keywords from the given args. + sub := "" + subs := make([]string, 0) + for _, s := range _regexpFlowSpecOperator.FindAllString(strings.Join(args, " "), -1) { + sub += s + if _regexpFlowSpecOperatorValue.MatchString(s) { + subs = append(subs, sub) + sub = "" + } + } + + // RFC5575 says "It should be unset in the first operator byte of a + // sequence". + if len(subs) > 0 { + subs[0] = strings.TrimPrefix(subs[0], "&") + } + + return subs +} + +// Parses the FlowSpec numeric operator using the given submatch which should be +// the return value of func (*Regexp) FindStringSubmatch. +func parseFlowSpecNumericOperator(submatch []string) (operator uint8, err error) { + if submatch[1] == "&" { + operator = DEC_NUM_OP_AND + } + value, ok := DECNumOpValueMap[submatch[2]] + if !ok { + return 0, fmt.Errorf("invalid numeric operator: %s%s", submatch[1], submatch[2]) + } + operator |= uint8(value) + return operator, nil +} + +// Parses the pairs of operator and value for the FlowSpec numeric type. The +// given validationFunc is applied to evaluate whether the parsed value is +// valid or not (e.g., if exceeds range or not). +// Note: Each of the args should be formatted in single pair of operator and +// value before calling this function. +// e.g.) "&==100", ">=200" or "&<300" +func parseFlowSpecNumericOpValues(typ BGPFlowSpecType, args []string, validationFunc func(uint64) error) (FlowSpecComponentInterface, error) { + argsLen := len(args) + items := make([]*FlowSpecComponentItem, 0, argsLen) + for idx, arg := range args { + m := _regexpFlowSpecNumericType.FindStringSubmatch(arg) + if len(m) < 4 { + return nil, fmt.Errorf("invalid argument for %s: %s in %q", typ.String(), arg, args) + } + operator, err := parseFlowSpecNumericOperator(m) + if err != nil { + return nil, err + } + // "true" and "false" is operator, but here handles them as value. + var value uint64 + switch m[3] { + case "true", "false": + if idx != argsLen-1 { + return nil, fmt.Errorf("%s should be the last of each rule", m[3]) + } + operator = uint8(DECNumOpValueMap[m[3]]) + default: + if value, err = strconv.ParseUint(m[3], 10, 64); err != nil { + return nil, fmt.Errorf("invalid numeric value: %s", m[3]) + } + if err = validationFunc(value); err != nil { + return nil, err + } + } + items = append(items, NewFlowSpecComponentItem(operator, value)) + } + + // Marks end-of-list bit + items[argsLen-1].Op |= uint8(DEC_NUM_OP_END) + + return NewFlowSpecComponent(typ, items), nil +} + +func flowSpecNumeric1ByteParser(_ RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + args = normalizeFlowSpecOpValues(args) + + f := func(i uint64) error { + if i <= 0xff { // 1 byte + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecNumeric2BytesParser(_ RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + args = normalizeFlowSpecOpValues(args) + + f := func(i uint64) error { + if i <= 0xffff { // 2 bytes + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +// Parses the FlowSpec bitmask operand using the given submatch which should be +// the return value of func (*Regexp) FindStringSubmatch. +func parseFlowSpecBitmaskOperand(submatch []string) (operand uint8, err error) { + if submatch[1] == "&" { + operand = BITMASK_FLAG_OP_AND + } + value, ok := BitmaskFlagOpValueMap[submatch[2]] + if !ok { + return 0, fmt.Errorf("invalid bitmask operand: %s%s", submatch[1], submatch[2]) + } + operand |= uint8(value) + return operand, nil +} + +func flowSpecPrefixParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + // args[0]: IP Prefix or IP Address (suppose prefix length is 32) + // args[1]: Offset in bit (IPv6 only) + // + // Example: + // - IPv4 Prefix + // args := []string{"192.168.0.0/24"} + // - IPv4 Address + // args := []string{"192.168.0.1"} + // - IPv6 Prefix + // args := []string{"2001:db8:1::/64"} + // - IPv6 Prefix with offset + // args := []string{"0:db8:1::/64/16"} + // args := []string{"0:db8:1::/64", "16"} + // - IPv6 Address + // args := []string{"2001:db8:1::1"} + // - IPv6 Address with offset + // args := []string{"0:db8:1::1", "16"} + afi, _ := RouteFamilyToAfiSafi(rf) + switch afi { + case AFI_IP: + if len(args) > 1 { + return nil, fmt.Errorf("cannot specify offset for ipv4 prefix") + } + invalidIPv4PrefixError := fmt.Errorf("invalid ipv4 prefix: %s", args[0]) + m := _regexpFindIPv4Prefix.FindStringSubmatch(args[0]) + if len(m) < 4 { + return nil, invalidIPv4PrefixError + } + prefix := net.ParseIP(m[1]) + if prefix.To4() == nil { + return nil, invalidIPv4PrefixError + } + var prefixLen uint64 = 32 + if m[3] != "" { + var err error + prefixLen, err = strconv.ParseUint(m[3], 10, 8) + if err != nil || prefixLen > 32 { + return nil, invalidIPv4PrefixError + } + } + switch typ { + case FLOW_SPEC_TYPE_DST_PREFIX: + return NewFlowSpecDestinationPrefix(NewIPAddrPrefix(uint8(prefixLen), prefix.String())), nil + case FLOW_SPEC_TYPE_SRC_PREFIX: + return NewFlowSpecSourcePrefix(NewIPAddrPrefix(uint8(prefixLen), prefix.String())), nil + } + return nil, fmt.Errorf("invalid traffic filtering rule type: %s", typ.String()) + case AFI_IP6: + if len(args) > 2 { + return nil, fmt.Errorf("invalid arguments for ipv6 prefix: %q", args) + } + invalidIPv6PrefixError := fmt.Errorf("invalid ipv6 prefix: %s", args[0]) + m := _regexpFindIPv6Prefix.FindStringSubmatch(args[0]) + if len(m) < 4 { + return nil, invalidIPv6PrefixError + } + prefix := net.ParseIP(m[1]) + if prefix.To16() == nil { + return nil, invalidIPv6PrefixError + } + var prefixLen uint64 = 128 + if m[3] != "" { + var err error + prefixLen, err = strconv.ParseUint(m[3], 10, 8) + if err != nil || prefixLen > 128 { + return nil, invalidIPv6PrefixError + } + } + var offset uint64 + if len(args) == 1 && m[5] != "" { + var err error + offset, err = strconv.ParseUint(m[5], 10, 8) + if err != nil || offset > 128 { + return nil, fmt.Errorf("invalid ipv6 prefix offset: %s", m[5]) + } + } else if len(args) == 2 { + if m[5] != "" { + return nil, fmt.Errorf("multiple ipv6 prefix offset arguments detected: %q", args) + } + var err error + offset, err = strconv.ParseUint(args[1], 10, 8) + if err != nil || offset > 128 { + return nil, fmt.Errorf("invalid ipv6 prefix offset: %s", args[1]) + } + } + switch typ { + case FLOW_SPEC_TYPE_DST_PREFIX: + return NewFlowSpecDestinationPrefix6(NewIPv6AddrPrefix(uint8(prefixLen), prefix.String()), uint8(offset)), nil + case FLOW_SPEC_TYPE_SRC_PREFIX: + return NewFlowSpecSourcePrefix6(NewIPv6AddrPrefix(uint8(prefixLen), prefix.String()), uint8(offset)), nil + } + return nil, fmt.Errorf("invalid traffic filtering rule type: %s", typ.String()) + } + return nil, fmt.Errorf("invalid address family: %s", rf.String()) +} + +func flowSpecIpProtoParser(_ RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + // args: List of pairs of Operator and IP protocol type + // + // Example: + // - TCP or UDP + // args := []string{"tcp", "==udp"} + // - Not TCP and not UDP + // args := []string{"!=tcp", "&!=udp"} + args = normalizeFlowSpecOpValues(args) + s := strings.Join(args, " ") + for i, name := range ProtocolNameMap { + s = strings.Replace(s, name, fmt.Sprintf("%d", i), -1) + } + args = strings.Split(s, " ") + + f := func(i uint64) error { + if i <= 0xff { // 1 byte + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecTcpFlagParser(_ RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + // args: List of pairs of Operand and TCP Flags + // + // Example: + // - SYN or SYN/ACK + // args := []string{"==S", "==SA"} + // - Not FIN and not URG + // args := []string{"!=F", "&!=U"} + args = normalizeFlowSpecOpValues(args) + + argsLen := len(args) + items := make([]*FlowSpecComponentItem, 0, argsLen) + + for _, arg := range args { + m := _regexpFlowSpecTCPFlag.FindStringSubmatch(arg) + if len(m) < 6 { + return nil, fmt.Errorf("invalid argument for %s: %s in %q", typ.String(), arg, args) + } else if mLast := m[len(m)-1]; mLast != "" || m[3] != "" { + return nil, fmt.Errorf("invalid argument for %s: %s in %q", typ.String(), arg, args) + } + operand, err := parseFlowSpecBitmaskOperand(m) + if err != nil { + return nil, err + } + var value uint64 + for flag, name := range TCPFlagNameMap { + if strings.Contains(m[4], name) { + value |= uint64(flag) + } + } + items = append(items, NewFlowSpecComponentItem(operand, value)) + } + + // Marks end-of-list bit + items[argsLen-1].Op |= BITMASK_FLAG_OP_END + + return NewFlowSpecComponent(typ, items), nil +} + +func flowSpecDscpParser(_ RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + args = normalizeFlowSpecOpValues(args) + + f := func(i uint64) error { + if i < 64 { // 6 bits + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecFragmentParser(_ RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + // args: List of pairs of Operator and Fragment flags + // + // Example: + // - is-fragment or last-fragment + // args := []string{"==is-fragment", "==last-fragment"} + // - is-fragment and last-fragment (exact match) + // args := []string{"==is-fragment+last-fragment"} + args = normalizeFlowSpecOpValues(args) + + argsLen := len(args) + items := make([]*FlowSpecComponentItem, 0, argsLen) + + for _, arg := range args { + m := _regexpFlowSpecFragment.FindStringSubmatch(arg) + if len(m) < 4 { + return nil, fmt.Errorf("invalid argument for %s: %s in %q", typ.String(), arg, args) + } else if mLast := m[len(m)-1]; mLast != "" { + return nil, fmt.Errorf("invalid argument for %s: %s in %q", typ.String(), arg, args) + } + operand, err := parseFlowSpecBitmaskOperand(m) + if err != nil { + return nil, err + } + var value uint64 + // Example: + // m[3] = "first-fragment+last-fragment" + for flag, name := range FragmentFlagNameMap { + if strings.Contains(m[3], name) { + value |= uint64(flag) + } + } + items = append(items, NewFlowSpecComponentItem(operand, value)) + } + + // Marks end-of-list bit + items[argsLen-1].Op |= BITMASK_FLAG_OP_END + + return NewFlowSpecComponent(typ, items), nil +} + +func flowSpecLabelParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + afi, _ := RouteFamilyToAfiSafi(rf) + if afi == AFI_IP { + return nil, fmt.Errorf("%s is not supported for ipv4", typ.String()) + } + + args = normalizeFlowSpecOpValues(args) + + f := func(i uint64) error { + if i <= 0xfffff { // 20 bits + return nil + } + return fmt.Errorf("flow label range exceeded") + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecEtherTypeParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + // args: List of pairs of Operator and Ether Types + // + // Example: + // - ARP or IPv4 + // args := []string{"==arp", "==ipv4"} + // - Not IPv4 and not IPv6 + // args := []string{"!=ipv4", "&!=ipv6"} + if rf != RF_FS_L2_VPN { + return nil, fmt.Errorf("%s is supported for only l2vpn", typ.String()) + } + + args = normalizeFlowSpecOpValues(args) + s := strings.Join(args, " ") + for i, name := range EthernetTypeNameMap { + s = strings.Replace(s, name, fmt.Sprintf("%d", i), -1) + } + args = strings.Split(s, " ") + + f := func(i uint64) error { + if i <= 0xffff { // 2 bytes + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecMacParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + // args[0]: MAC address + if rf != RF_FS_L2_VPN { + return nil, fmt.Errorf("%s is supported for only l2vpn", typ.String()) + } + + mac, err := net.ParseMAC(args[0]) + if err != nil { + return nil, fmt.Errorf("invalid mac address: %s", args[0]) + } + + switch typ { + case FLOW_SPEC_TYPE_DST_MAC: + return NewFlowSpecDestinationMac(mac), nil + case FLOW_SPEC_TYPE_SRC_MAC: + return NewFlowSpecSourceMac(mac), nil + } + return nil, fmt.Errorf("invalid traffic filtering rule type: %s", typ.String()) +} + +func flowSpecLlcParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + if rf != RF_FS_L2_VPN { + return nil, fmt.Errorf("%s is supported for only l2vpn", typ.String()) + } + + return flowSpecNumeric1ByteParser(rf, typ, args) +} + +func flowSpecSnapParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + if rf != RF_FS_L2_VPN { + return nil, fmt.Errorf("%s is supported for only l2vpn", typ.String()) + } + + args = normalizeFlowSpecOpValues(args) + + f := func(i uint64) error { + if i <= 0xffffffffff { // 5 bytes + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecVlanIDParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + if rf != RF_FS_L2_VPN { + return nil, fmt.Errorf("%s is supported for only l2vpn", typ.String()) + } + + args = normalizeFlowSpecOpValues(args) + s := strings.Join(args, " ") + for i, name := range EthernetTypeNameMap { + s = strings.Replace(s, name, fmt.Sprintf("%d", i), -1) + } + args = strings.Split(s, " ") + + f := func(i uint64) error { + if i <= 4095 { // 12 bits + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +func flowSpecVlanCosParser(rf RouteFamily, typ BGPFlowSpecType, args []string) (FlowSpecComponentInterface, error) { + if rf != RF_FS_L2_VPN { + return nil, fmt.Errorf("%s is supported for only l2vpn", typ.String()) + } + + args = normalizeFlowSpecOpValues(args) + s := strings.Join(args, " ") + for i, name := range EthernetTypeNameMap { + s = strings.Replace(s, name, fmt.Sprintf("%d", i), -1) + } + args = strings.Split(s, " ") + + f := func(i uint64) error { + if i <= 7 { // 3 bits + return nil + } + return fmt.Errorf("%s range exceeded", typ.String()) + } + + return parseFlowSpecNumericOpValues(typ, args, f) +} + +var flowSpecParserMap = map[BGPFlowSpecType]func(RouteFamily, BGPFlowSpecType, []string) (FlowSpecComponentInterface, error){ + FLOW_SPEC_TYPE_DST_PREFIX: flowSpecPrefixParser, + FLOW_SPEC_TYPE_SRC_PREFIX: flowSpecPrefixParser, + FLOW_SPEC_TYPE_IP_PROTO: flowSpecIpProtoParser, + FLOW_SPEC_TYPE_PORT: flowSpecNumeric2BytesParser, + FLOW_SPEC_TYPE_DST_PORT: flowSpecNumeric2BytesParser, + FLOW_SPEC_TYPE_SRC_PORT: flowSpecNumeric2BytesParser, + FLOW_SPEC_TYPE_ICMP_TYPE: flowSpecNumeric1ByteParser, + FLOW_SPEC_TYPE_ICMP_CODE: flowSpecNumeric1ByteParser, + FLOW_SPEC_TYPE_TCP_FLAG: flowSpecTcpFlagParser, + FLOW_SPEC_TYPE_PKT_LEN: flowSpecNumeric2BytesParser, + FLOW_SPEC_TYPE_DSCP: flowSpecDscpParser, + FLOW_SPEC_TYPE_FRAGMENT: flowSpecFragmentParser, + FLOW_SPEC_TYPE_LABEL: flowSpecLabelParser, + FLOW_SPEC_TYPE_ETHERNET_TYPE: flowSpecEtherTypeParser, + FLOW_SPEC_TYPE_DST_MAC: flowSpecMacParser, + FLOW_SPEC_TYPE_SRC_MAC: flowSpecMacParser, + FLOW_SPEC_TYPE_LLC_DSAP: flowSpecLlcParser, + FLOW_SPEC_TYPE_LLC_SSAP: flowSpecLlcParser, + FLOW_SPEC_TYPE_LLC_CONTROL: flowSpecLlcParser, + FLOW_SPEC_TYPE_SNAP: flowSpecSnapParser, + FLOW_SPEC_TYPE_VID: flowSpecVlanIDParser, + FLOW_SPEC_TYPE_COS: flowSpecVlanCosParser, + FLOW_SPEC_TYPE_INNER_VID: flowSpecVlanIDParser, + FLOW_SPEC_TYPE_INNER_COS: flowSpecVlanCosParser, +} + +func extractFlowSpecArgs(args []string) map[BGPFlowSpecType][]string { + m := make(map[BGPFlowSpecType][]string, len(FlowSpecValueMap)) + var typ BGPFlowSpecType + for _, arg := range args { + if t, ok := FlowSpecValueMap[arg]; ok { + typ = t + m[typ] = make([]string, 0) + } else { + m[typ] = append(m[typ], arg) + } + } + return m +} + +func ParseFlowSpecComponents(rf RouteFamily, arg string) ([]FlowSpecComponentInterface, error) { + _, safi := RouteFamilyToAfiSafi(rf) + switch safi { + case SAFI_FLOW_SPEC_UNICAST, SAFI_FLOW_SPEC_VPN: + // Valid + default: + return nil, fmt.Errorf("invalid address family: %s", rf.String()) + } + + typeArgs := extractFlowSpecArgs(strings.Split(arg, " ")) + rules := make([]FlowSpecComponentInterface, 0, len(typeArgs)) + for typ, args := range typeArgs { + parser, ok := flowSpecParserMap[typ] + if !ok { + return nil, fmt.Errorf("unsupported traffic filtering rule type: %s", typ.String()) + } + if len(args) == 0 { + return nil, fmt.Errorf("specify traffic filtering rules for %s", typ.String()) + } + rule, err := parser(rf, typ, args) + if err != nil { + return nil, err + } + rules = append(rules, rule) + } + return rules, nil +} + +func (t BGPFlowSpecType) String() string { + name, ok := FlowSpecNameMap[t] + if !ok { + return fmt.Sprintf("%s(%d)", FlowSpecNameMap[FLOW_SPEC_TYPE_UNKNOWN], t) + } + return name +} + +type FlowSpecComponentInterface interface { + DecodeFromBytes([]byte, ...*MarshallingOption) error + Serialize(...*MarshallingOption) ([]byte, error) + Len(...*MarshallingOption) int + Type() BGPFlowSpecType + String() string +} + +type flowSpecPrefix struct { + Prefix AddrPrefixInterface + typ BGPFlowSpecType +} + +func (p *flowSpecPrefix) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + p.typ = BGPFlowSpecType(data[0]) + return p.Prefix.DecodeFromBytes(data[1:], options...) +} + +func (p *flowSpecPrefix) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := []byte{byte(p.Type())} + bbuf, err := p.Prefix.Serialize(options...) + if err != nil { + return nil, err + } + return append(buf, bbuf...), nil +} + +func (p *flowSpecPrefix) Len(options ...*MarshallingOption) int { + buf, _ := p.Serialize(options...) + return len(buf) +} + +func (p *flowSpecPrefix) Type() BGPFlowSpecType { + return p.typ +} + +func (p *flowSpecPrefix) String() string { + return fmt.Sprintf("[%s: %s]", p.Type(), p.Prefix.String()) +} + +func (p *flowSpecPrefix) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPFlowSpecType `json:"type"` + Value AddrPrefixInterface `json:"value"` + }{ + Type: p.Type(), + Value: p.Prefix, + }) +} + +type flowSpecPrefix6 struct { + Prefix AddrPrefixInterface + Offset uint8 + typ BGPFlowSpecType +} + +// draft-ietf-idr-flow-spec-v6-06 +// <type (1 octet), prefix length (1 octet), prefix offset(1 octet), prefix> +func (p *flowSpecPrefix6) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + p.typ = BGPFlowSpecType(data[0]) + p.Offset = data[2] + prefix := append([]byte{data[1]}, data[3:]...) + return p.Prefix.DecodeFromBytes(prefix, options...) +} + +func (p *flowSpecPrefix6) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := []byte{byte(p.Type())} + bbuf, err := p.Prefix.Serialize(options...) + if err != nil { + return nil, err + } + buf = append(buf, bbuf[0]) + buf = append(buf, p.Offset) + return append(buf, bbuf[1:]...), nil +} + +func (p *flowSpecPrefix6) Len(options ...*MarshallingOption) int { + buf, _ := p.Serialize(options...) + return len(buf) +} + +func (p *flowSpecPrefix6) Type() BGPFlowSpecType { + return p.typ +} + +func (p *flowSpecPrefix6) String() string { + return fmt.Sprintf("[%s: %s/%d]", p.Type(), p.Prefix.String(), p.Offset) +} + +func (p *flowSpecPrefix6) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPFlowSpecType `json:"type"` + Value AddrPrefixInterface `json:"value"` + Offset uint8 `json:"offset"` + }{ + Type: p.Type(), + Value: p.Prefix, + Offset: p.Offset, + }) +} + +type FlowSpecDestinationPrefix struct { + flowSpecPrefix +} + +func NewFlowSpecDestinationPrefix(prefix AddrPrefixInterface) *FlowSpecDestinationPrefix { + return &FlowSpecDestinationPrefix{flowSpecPrefix{prefix, FLOW_SPEC_TYPE_DST_PREFIX}} +} + +type FlowSpecSourcePrefix struct { + flowSpecPrefix +} + +func NewFlowSpecSourcePrefix(prefix AddrPrefixInterface) *FlowSpecSourcePrefix { + return &FlowSpecSourcePrefix{flowSpecPrefix{prefix, FLOW_SPEC_TYPE_SRC_PREFIX}} +} + +type FlowSpecDestinationPrefix6 struct { + flowSpecPrefix6 +} + +func NewFlowSpecDestinationPrefix6(prefix AddrPrefixInterface, offset uint8) *FlowSpecDestinationPrefix6 { + return &FlowSpecDestinationPrefix6{flowSpecPrefix6{prefix, offset, FLOW_SPEC_TYPE_DST_PREFIX}} +} + +type FlowSpecSourcePrefix6 struct { + flowSpecPrefix6 +} + +func NewFlowSpecSourcePrefix6(prefix AddrPrefixInterface, offset uint8) *FlowSpecSourcePrefix6 { + return &FlowSpecSourcePrefix6{flowSpecPrefix6{prefix, offset, FLOW_SPEC_TYPE_SRC_PREFIX}} +} + +type flowSpecMac struct { + Mac net.HardwareAddr + typ BGPFlowSpecType +} + +func (p *flowSpecMac) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if len(data) < 2 || len(data) < 2+int(data[1]) { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all mac bits available") + } + p.typ = BGPFlowSpecType(data[0]) + p.Mac = net.HardwareAddr(data[2 : 2+int(data[1])]) + return nil +} + +func (p *flowSpecMac) Serialize(options ...*MarshallingOption) ([]byte, error) { + if len(p.Mac) == 0 { + return nil, fmt.Errorf("mac unset") + } + buf := []byte{byte(p.Type()), byte(len(p.Mac))} + return append(buf, []byte(p.Mac)...), nil +} + +func (p *flowSpecMac) Len(options ...*MarshallingOption) int { + return 2 + len(p.Mac) +} + +func (p *flowSpecMac) Type() BGPFlowSpecType { + return p.typ +} + +func (p *flowSpecMac) String() string { + return fmt.Sprintf("[%s: %s]", p.Type(), p.Mac.String()) +} + +func (p *flowSpecMac) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPFlowSpecType `json:"type"` + Value string `json:"value"` + }{ + Type: p.Type(), + Value: p.Mac.String(), + }) +} + +type FlowSpecSourceMac struct { + flowSpecMac +} + +func NewFlowSpecSourceMac(mac net.HardwareAddr) *FlowSpecSourceMac { + return &FlowSpecSourceMac{flowSpecMac{Mac: mac, typ: FLOW_SPEC_TYPE_SRC_MAC}} +} + +type FlowSpecDestinationMac struct { + flowSpecMac +} + +func NewFlowSpecDestinationMac(mac net.HardwareAddr) *FlowSpecDestinationMac { + return &FlowSpecDestinationMac{flowSpecMac{Mac: mac, typ: FLOW_SPEC_TYPE_DST_MAC}} +} + +type FlowSpecComponentItem struct { + Op uint8 `json:"op"` + Value uint64 `json:"value"` +} + +func (v *FlowSpecComponentItem) Len() int { + return 1 << ((uint32(v.Op) >> 4) & 0x3) +} + +func (v *FlowSpecComponentItem) Serialize() ([]byte, error) { + if v.Op > math.MaxUint8 { + return nil, fmt.Errorf("invalid op size: %d", v.Op) + } + + order := uint32(math.Log2(float64(v.Len()))) + buf := make([]byte, 1+(1<<order)) + buf[0] = byte(uint32(v.Op) | order<<4) + switch order { + case 0: + buf[1] = byte(v.Value) + case 1: + binary.BigEndian.PutUint16(buf[1:], uint16(v.Value)) + case 2: + binary.BigEndian.PutUint32(buf[1:], uint32(v.Value)) + case 3: + binary.BigEndian.PutUint64(buf[1:], uint64(v.Value)) + default: + return nil, fmt.Errorf("invalid value size(too big): %d", v.Value) + } + return buf, nil +} + +func NewFlowSpecComponentItem(op uint8, value uint64) *FlowSpecComponentItem { + v := &FlowSpecComponentItem{op, value} + order := uint32(math.Log2(float64(v.Len()))) + // we don't know if not initialized properly or initialized to + // zero... + if order == 0 { + order = func() uint32 { + for i := 0; i < 3; i++ { + if v.Value < (1 << ((1 << uint(i)) * 8)) { + return uint32(i) + } + } + // return invalid order + return 4 + }() + } + if order > 3 { + return nil + } + v.Op = uint8(uint32(v.Op) | order<<4) + return v +} + +type FlowSpecComponent struct { + Items []*FlowSpecComponentItem + typ BGPFlowSpecType +} + +func (p *FlowSpecComponent) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + p.typ = BGPFlowSpecType(data[0]) + data = data[1:] + p.Items = make([]*FlowSpecComponentItem, 0) + for { + if len(data) < 2 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all flowspec component bytes available") + } + op := data[0] + end := op & 0x80 + l := 1 << ((op >> 4) & 0x3) // (min, max) = (1, 8) + v := make([]byte, 8) + copy(v[8-l:], data[1:1+l]) + i := binary.BigEndian.Uint64(v) + item := &FlowSpecComponentItem{op, i} + p.Items = append(p.Items, item) + if end > 0 { + break + } + data = data[1+l:] + } + return nil +} + +func (p *FlowSpecComponent) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := []byte{byte(p.Type())} + for _, v := range p.Items { + bbuf, err := v.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + } + return buf, nil +} + +func (p *FlowSpecComponent) Len(options ...*MarshallingOption) int { + l := 1 + for _, item := range p.Items { + l += item.Len() + 1 + } + return l +} + +func (p *FlowSpecComponent) Type() BGPFlowSpecType { + return p.typ +} + +func formatRaw(op uint8, value uint64) string { + return fmt.Sprintf("op:%b,value:%d", op, value) +} + +func formatNumeric(op uint8, value uint64) string { + cmpFlag := DECNumOp(op & 0x7) // lower 3 bits + if cmpFlag == DEC_NUM_OP_TRUE || cmpFlag == DEC_NUM_OP_FALSE { + // Omit value field + return DECNumOp(op).String() + } + return fmt.Sprint(DECNumOp(op).String(), value) +} + +func formatProto(op uint8, value uint64) string { + cmpFlag := DECNumOp(op & 0x7) // lower 3 bits + if cmpFlag == DEC_NUM_OP_TRUE || cmpFlag == DEC_NUM_OP_FALSE { + // Omit value field + return DECNumOp(op).String() + } + return fmt.Sprint(DECNumOp(op).String(), Protocol(value).String()) +} + +func formatTCPFlag(op uint8, value uint64) string { + return fmt.Sprint(BitmaskFlagOp(op).String(), TCPFlag(value).String()) +} + +func formatFragment(op uint8, value uint64) string { + return fmt.Sprint(BitmaskFlagOp(op).String(), FragmentFlag(value).String()) +} + +func formatEtherType(op uint8, value uint64) string { + cmpFlag := DECNumOp(op & 0x7) // lower 3 bits + if cmpFlag == DEC_NUM_OP_TRUE || cmpFlag == DEC_NUM_OP_FALSE { + // Omit value field + return DECNumOp(op).String() + } + return fmt.Sprint(DECNumOp(op).String(), EthernetType(value).String()) +} + +var flowSpecFormatMap = map[BGPFlowSpecType]func(op uint8, value uint64) string{ + FLOW_SPEC_TYPE_UNKNOWN: formatRaw, + FLOW_SPEC_TYPE_IP_PROTO: formatProto, + FLOW_SPEC_TYPE_PORT: formatNumeric, + FLOW_SPEC_TYPE_DST_PORT: formatNumeric, + FLOW_SPEC_TYPE_SRC_PORT: formatNumeric, + FLOW_SPEC_TYPE_ICMP_TYPE: formatNumeric, + FLOW_SPEC_TYPE_ICMP_CODE: formatNumeric, + FLOW_SPEC_TYPE_TCP_FLAG: formatTCPFlag, + FLOW_SPEC_TYPE_PKT_LEN: formatNumeric, + FLOW_SPEC_TYPE_DSCP: formatNumeric, + FLOW_SPEC_TYPE_FRAGMENT: formatFragment, + FLOW_SPEC_TYPE_LABEL: formatNumeric, + FLOW_SPEC_TYPE_ETHERNET_TYPE: formatEtherType, + FLOW_SPEC_TYPE_LLC_DSAP: formatNumeric, + FLOW_SPEC_TYPE_LLC_SSAP: formatNumeric, + FLOW_SPEC_TYPE_LLC_CONTROL: formatNumeric, + FLOW_SPEC_TYPE_SNAP: formatNumeric, + FLOW_SPEC_TYPE_VID: formatNumeric, + FLOW_SPEC_TYPE_COS: formatNumeric, + FLOW_SPEC_TYPE_INNER_VID: formatNumeric, + FLOW_SPEC_TYPE_INNER_COS: formatNumeric, +} + +func (p *FlowSpecComponent) String() string { + f := flowSpecFormatMap[FLOW_SPEC_TYPE_UNKNOWN] + if _, ok := flowSpecFormatMap[p.typ]; ok { + f = flowSpecFormatMap[p.typ] + } + + items := make([]string, 0, len(p.Items)) + for _, i := range p.Items { + items = append(items, f(i.Op, i.Value)) + } + // Removes leading and tailing spaces + value := strings.TrimSpace(strings.Join(items, "")) + + return fmt.Sprintf("[%s: %s]", p.typ, value) +} + +func (p *FlowSpecComponent) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPFlowSpecType `json:"type"` + Value []*FlowSpecComponentItem `json:"value"` + }{ + Type: p.Type(), + Value: p.Items, + }) +} + +func NewFlowSpecComponent(typ BGPFlowSpecType, items []*FlowSpecComponentItem) *FlowSpecComponent { + // Set end-of-list bit on the last item and unset them on the others. + for i, v := range items { + if i == len(items)-1 { + v.Op |= 0x80 + } else { + v.Op &^= 0x80 + } + + } + return &FlowSpecComponent{ + Items: items, + typ: typ, + } +} + +type FlowSpecUnknown struct { + Value []byte +} + +func (p *FlowSpecUnknown) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + p.Value = data + return nil +} + +func (p *FlowSpecUnknown) Serialize(options ...*MarshallingOption) ([]byte, error) { + return p.Value, nil +} + +func (p *FlowSpecUnknown) Len(options ...*MarshallingOption) int { + return len(p.Value) +} + +func (p *FlowSpecUnknown) Type() BGPFlowSpecType { + if len(p.Value) > 0 { + return BGPFlowSpecType(p.Value[0]) + } + return FLOW_SPEC_TYPE_UNKNOWN +} + +func (p *FlowSpecUnknown) String() string { + return fmt.Sprintf("[unknown:%v]", p.Value) +} + +func (p *FlowSpecUnknown) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPFlowSpecType `json:"type"` + Value string `json:"value"` + }{ + Type: p.Type(), + Value: string(p.Value), + }) +} + +type FlowSpecNLRI struct { + PrefixDefault + Value []FlowSpecComponentInterface + rf RouteFamily + rd RouteDistinguisherInterface +} + +func (n *FlowSpecNLRI) AFI() uint16 { + afi, _ := RouteFamilyToAfiSafi(n.rf) + return afi +} + +func (n *FlowSpecNLRI) SAFI() uint8 { + _, safi := RouteFamilyToAfiSafi(n.rf) + return safi +} + +func (n *FlowSpecNLRI) RD() RouteDistinguisherInterface { + return n.rd +} + +func (n *FlowSpecNLRI) decodeFromBytes(rf RouteFamily, data []byte, options ...*MarshallingOption) error { + if IsAddPathEnabled(true, rf, options) { + var err error + data, err = n.decodePathIdentifier(data) + if err != nil { + return err + } + } + var length int + if (data[0]>>4) == 0xf && len(data) > 2 { + length = int(binary.BigEndian.Uint16(data[0:2])) + data = data[2:] + } else if len(data) > 1 { + length = int(data[0]) + data = data[1:] + } else { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all flowspec component bytes available") + } + + n.rf = rf + + if n.SAFI() == SAFI_FLOW_SPEC_VPN { + if length < 8 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all flowspec component bytes available") + } + n.rd = GetRouteDistinguisher(data[:8]) + data = data[8:] + length -= 8 + } + + for l := length; l > 0; { + if len(data) == 0 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all flowspec component bytes available") + } + t := BGPFlowSpecType(data[0]) + var i FlowSpecComponentInterface + switch t { + case FLOW_SPEC_TYPE_DST_PREFIX: + switch { + case rf>>16 == AFI_IP: + i = NewFlowSpecDestinationPrefix(NewIPAddrPrefix(0, "")) + case rf>>16 == AFI_IP6: + i = NewFlowSpecDestinationPrefix6(NewIPv6AddrPrefix(0, ""), 0) + default: + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid address family: %v", rf)) + } + case FLOW_SPEC_TYPE_SRC_PREFIX: + switch { + case rf>>16 == AFI_IP: + i = NewFlowSpecSourcePrefix(NewIPAddrPrefix(0, "")) + case rf>>16 == AFI_IP6: + i = NewFlowSpecSourcePrefix6(NewIPv6AddrPrefix(0, ""), 0) + default: + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid address family: %v", rf)) + } + case FLOW_SPEC_TYPE_SRC_MAC: + switch rf { + case RF_FS_L2_VPN: + i = NewFlowSpecSourceMac(nil) + default: + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid address family: %v", rf)) + } + case FLOW_SPEC_TYPE_DST_MAC: + switch rf { + case RF_FS_L2_VPN: + i = NewFlowSpecDestinationMac(nil) + default: + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Invalid address family: %v", rf)) + } + case FLOW_SPEC_TYPE_IP_PROTO, FLOW_SPEC_TYPE_PORT, FLOW_SPEC_TYPE_DST_PORT, FLOW_SPEC_TYPE_SRC_PORT, + FLOW_SPEC_TYPE_ICMP_TYPE, FLOW_SPEC_TYPE_ICMP_CODE, FLOW_SPEC_TYPE_TCP_FLAG, FLOW_SPEC_TYPE_PKT_LEN, + FLOW_SPEC_TYPE_DSCP, FLOW_SPEC_TYPE_FRAGMENT, FLOW_SPEC_TYPE_LABEL, FLOW_SPEC_TYPE_ETHERNET_TYPE, + FLOW_SPEC_TYPE_LLC_DSAP, FLOW_SPEC_TYPE_LLC_SSAP, FLOW_SPEC_TYPE_LLC_CONTROL, FLOW_SPEC_TYPE_SNAP, + FLOW_SPEC_TYPE_VID, FLOW_SPEC_TYPE_COS, FLOW_SPEC_TYPE_INNER_VID, FLOW_SPEC_TYPE_INNER_COS: + i = NewFlowSpecComponent(t, nil) + default: + i = &FlowSpecUnknown{} + } + + err := i.DecodeFromBytes(data, options...) + if err != nil { + i = &FlowSpecUnknown{data} + } + l -= i.Len(options...) + data = data[i.Len(options...):] + n.Value = append(n.Value, i) + } + + // Sort Traffic Filtering Rules in types order to avoid the unordered rules + // are determined different. + sort.SliceStable(n.Value, func(i, j int) bool { return n.Value[i].Type() < n.Value[j].Type() }) + + return nil +} + +func (n *FlowSpecNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0, 32) + if n.SAFI() == SAFI_FLOW_SPEC_VPN { + if n.rd == nil { + return nil, fmt.Errorf("RD is nil") + } + b, err := n.rd.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, b...) + } + for _, v := range n.Value { + b, err := v.Serialize(options...) + if err != nil { + return nil, err + } + buf = append(buf, b...) + } + length := n.Len(options...) + if length > 0xfff { + return nil, fmt.Errorf("Too large: %d", length) + } else if length < 0xf0 { + length -= 1 + buf = append([]byte{byte(length)}, buf...) + } else { + length -= 2 + b := make([]byte, 2) + binary.BigEndian.PutUint16(buf, uint16(length)) + buf = append(b, buf...) + } + + if IsAddPathEnabled(false, n.rf, options) { + id, err := n.serializeIdentifier() + if err != nil { + return nil, err + } + return append(id, buf...), nil + } + return buf, nil +} + +func (n *FlowSpecNLRI) Len(options ...*MarshallingOption) int { + l := 0 + if n.SAFI() == SAFI_FLOW_SPEC_VPN { + l += n.RD().Len() + } + for _, v := range n.Value { + l += v.Len(options...) + } + if l < 0xf0 { + return l + 1 + } else { + return l + 2 + } +} + +func (n *FlowSpecNLRI) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + if n.SAFI() == SAFI_FLOW_SPEC_VPN { + buf.WriteString(fmt.Sprintf("[rd: %s]", n.rd)) + } + for _, v := range n.Value { + buf.WriteString(v.String()) + } + return buf.String() +} + +func (n *FlowSpecNLRI) MarshalJSON() ([]byte, error) { + if n.rd != nil { + return json.Marshal(struct { + RD RouteDistinguisherInterface `json:"rd"` + Value []FlowSpecComponentInterface `json:"value"` + }{ + RD: n.rd, + Value: n.Value, + }) + } + return json.Marshal(struct { + Value []FlowSpecComponentInterface `json:"value"` + }{ + Value: n.Value, + }) + +} + +// +// CompareFlowSpecNLRI(n, m) returns +// -1 when m has precedence +// 0 when n and m have same precedence +// 1 when n has precedence +// +func CompareFlowSpecNLRI(n, m *FlowSpecNLRI) (int, error) { + family := AfiSafiToRouteFamily(n.AFI(), n.SAFI()) + if family != AfiSafiToRouteFamily(m.AFI(), m.SAFI()) { + return 0, fmt.Errorf("address family mismatch") + } + longer := n.Value + shorter := m.Value + invert := 1 + if n.SAFI() == SAFI_FLOW_SPEC_VPN { + k, _ := n.Serialize() + l, _ := m.Serialize() + if result := bytes.Compare(k, l); result != 0 { + return result, nil + } + } + if len(n.Value) < len(m.Value) { + longer = m.Value + shorter = n.Value + invert = -1 + } + for idx, v := range longer { + if len(shorter) < idx+1 { + return invert, nil + } + w := shorter[idx] + if v.Type() < w.Type() { + return invert, nil + } else if v.Type() > w.Type() { + return invert * -1, nil + } else if v.Type() == FLOW_SPEC_TYPE_DST_PREFIX || v.Type() == FLOW_SPEC_TYPE_SRC_PREFIX { + // RFC5575 5.1 + // + // For IP prefix values (IP destination and source prefix) precedence is + // given to the lowest IP value of the common prefix length; if the + // common prefix is equal, then the most specific prefix has precedence. + var p, q *IPAddrPrefixDefault + var pCommon, qCommon uint64 + if n.AFI() == AFI_IP { + if v.Type() == FLOW_SPEC_TYPE_DST_PREFIX { + p = &v.(*FlowSpecDestinationPrefix).Prefix.(*IPAddrPrefix).IPAddrPrefixDefault + q = &w.(*FlowSpecDestinationPrefix).Prefix.(*IPAddrPrefix).IPAddrPrefixDefault + } else { + p = &v.(*FlowSpecSourcePrefix).Prefix.(*IPAddrPrefix).IPAddrPrefixDefault + q = &w.(*FlowSpecSourcePrefix).Prefix.(*IPAddrPrefix).IPAddrPrefixDefault + } + min := p.Length + if q.Length < p.Length { + min = q.Length + } + pCommon = uint64(binary.BigEndian.Uint32([]byte(p.Prefix.To4())) >> (32 - min)) + qCommon = uint64(binary.BigEndian.Uint32([]byte(q.Prefix.To4())) >> (32 - min)) + } else if n.AFI() == AFI_IP6 { + if v.Type() == FLOW_SPEC_TYPE_DST_PREFIX { + p = &v.(*FlowSpecDestinationPrefix6).Prefix.(*IPv6AddrPrefix).IPAddrPrefixDefault + q = &w.(*FlowSpecDestinationPrefix6).Prefix.(*IPv6AddrPrefix).IPAddrPrefixDefault + } else { + p = &v.(*FlowSpecSourcePrefix6).Prefix.(*IPv6AddrPrefix).IPAddrPrefixDefault + q = &w.(*FlowSpecSourcePrefix6).Prefix.(*IPv6AddrPrefix).IPAddrPrefixDefault + } + min := uint(p.Length) + if q.Length < p.Length { + min = uint(q.Length) + } + var mask uint + if min-64 > 0 { + mask = min - 64 + } + pCommon = binary.BigEndian.Uint64([]byte(p.Prefix.To16()[:8])) >> mask + qCommon = binary.BigEndian.Uint64([]byte(q.Prefix.To16()[:8])) >> mask + if pCommon == qCommon && mask == 0 { + mask = 64 - min + pCommon = binary.BigEndian.Uint64([]byte(p.Prefix.To16()[8:])) >> mask + qCommon = binary.BigEndian.Uint64([]byte(q.Prefix.To16()[8:])) >> mask + } + } + + if pCommon < qCommon { + return invert, nil + } else if pCommon > qCommon { + return invert * -1, nil + } else if p.Length > q.Length { + return invert, nil + } else if p.Length < q.Length { + return invert * -1, nil + } + + } else { + // RFC5575 5.1 + // + // For all other component types, unless otherwise specified, the + // comparison is performed by comparing the component data as a binary + // string using the memcmp() function as defined by the ISO C standard. + // For strings of different lengths, the common prefix is compared. If + // equal, the longest string is considered to have higher precedence + // than the shorter one. + p, _ := v.Serialize() + q, _ := w.Serialize() + min := len(p) + if len(q) < len(p) { + min = len(q) + } + if result := bytes.Compare(p[:min], q[:min]); result < 0 { + return invert, nil + } else if result > 0 { + return invert * -1, nil + } else if len(p) > len(q) { + return invert, nil + } else if len(q) > len(p) { + return invert * -1, nil + } + } + } + return 0, nil +} + +type FlowSpecIPv4Unicast struct { + FlowSpecNLRI +} + +func (n *FlowSpecIPv4Unicast) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + return n.decodeFromBytes(AfiSafiToRouteFamily(n.AFI(), n.SAFI()), data, options...) +} + +func NewFlowSpecIPv4Unicast(value []FlowSpecComponentInterface) *FlowSpecIPv4Unicast { + sort.SliceStable(value, func(i, j int) bool { return value[i].Type() < value[j].Type() }) + return &FlowSpecIPv4Unicast{ + FlowSpecNLRI: FlowSpecNLRI{ + Value: value, + rf: RF_FS_IPv4_UC, + }, + } +} + +type FlowSpecIPv4VPN struct { + FlowSpecNLRI +} + +func (n *FlowSpecIPv4VPN) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + return n.decodeFromBytes(AfiSafiToRouteFamily(n.AFI(), n.SAFI()), data, options...) +} + +func NewFlowSpecIPv4VPN(rd RouteDistinguisherInterface, value []FlowSpecComponentInterface) *FlowSpecIPv4VPN { + sort.SliceStable(value, func(i, j int) bool { return value[i].Type() < value[j].Type() }) + return &FlowSpecIPv4VPN{ + FlowSpecNLRI: FlowSpecNLRI{ + Value: value, + rf: RF_FS_IPv4_VPN, + rd: rd, + }, + } +} + +type FlowSpecIPv6Unicast struct { + FlowSpecNLRI +} + +func (n *FlowSpecIPv6Unicast) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + return n.decodeFromBytes(AfiSafiToRouteFamily(n.AFI(), n.SAFI()), data, options...) +} + +func NewFlowSpecIPv6Unicast(value []FlowSpecComponentInterface) *FlowSpecIPv6Unicast { + sort.SliceStable(value, func(i, j int) bool { return value[i].Type() < value[j].Type() }) + return &FlowSpecIPv6Unicast{ + FlowSpecNLRI: FlowSpecNLRI{ + Value: value, + rf: RF_FS_IPv6_UC, + }, + } +} + +type FlowSpecIPv6VPN struct { + FlowSpecNLRI +} + +func (n *FlowSpecIPv6VPN) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + return n.decodeFromBytes(AfiSafiToRouteFamily(n.AFI(), n.SAFI()), data, options...) +} + +func NewFlowSpecIPv6VPN(rd RouteDistinguisherInterface, value []FlowSpecComponentInterface) *FlowSpecIPv6VPN { + sort.SliceStable(value, func(i, j int) bool { return value[i].Type() < value[j].Type() }) + return &FlowSpecIPv6VPN{ + FlowSpecNLRI: FlowSpecNLRI{ + Value: value, + rf: RF_FS_IPv6_VPN, + rd: rd, + }, + } +} + +type FlowSpecL2VPN struct { + FlowSpecNLRI +} + +func (n *FlowSpecL2VPN) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + return n.decodeFromBytes(AfiSafiToRouteFamily(n.AFI(), n.SAFI()), data) +} + +func NewFlowSpecL2VPN(rd RouteDistinguisherInterface, value []FlowSpecComponentInterface) *FlowSpecL2VPN { + sort.SliceStable(value, func(i, j int) bool { return value[i].Type() < value[j].Type() }) + return &FlowSpecL2VPN{ + FlowSpecNLRI: FlowSpecNLRI{ + Value: value, + rf: RF_FS_L2_VPN, + rd: rd, + }, + } +} + +type OpaqueNLRI struct { + PrefixDefault + Length uint16 + Key []byte + Value []byte +} + +func (n *OpaqueNLRI) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if len(data) < 2 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all OpaqueNLRI bytes available") + } + if IsAddPathEnabled(true, RF_OPAQUE, options) { + var err error + data, err = n.decodePathIdentifier(data) + if err != nil { + return err + } + } + n.Length = binary.BigEndian.Uint16(data[0:2]) + if len(data)-2 < int(n.Length) { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all OpaqueNLRI bytes available") + } + n.Key = data[2 : 2+n.Length] + n.Value = data[2+n.Length:] + return nil +} + +func (n *OpaqueNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + if len(n.Key) > math.MaxUint16 { + return nil, fmt.Errorf("Key length too big") + } + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf, uint16(len(n.Key))) + buf = append(buf, n.Key...) + buf = append(buf, n.Value...) + if IsAddPathEnabled(false, RF_OPAQUE, options) { + id, err := n.serializeIdentifier() + if err != nil { + return nil, err + } + return append(id, buf...), nil + } + return buf, nil +} + +func (n *OpaqueNLRI) AFI() uint16 { + return AFI_OPAQUE +} + +func (n *OpaqueNLRI) SAFI() uint8 { + return SAFI_KEY_VALUE +} + +func (n *OpaqueNLRI) Len(options ...*MarshallingOption) int { + return 2 + len(n.Key) + len(n.Value) +} + +func (n *OpaqueNLRI) String() string { + return fmt.Sprintf("%s", n.Key) +} + +func (n *OpaqueNLRI) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Key string `json:"key"` + Value string `json:"value"` + }{ + Key: string(n.Key), + Value: string(n.Value), + }) +} + +func NewOpaqueNLRI(key, value []byte) *OpaqueNLRI { + return &OpaqueNLRI{ + Key: key, + Value: value, + } +} + +func AfiSafiToRouteFamily(afi uint16, safi uint8) RouteFamily { + return RouteFamily(int(afi)<<16 | int(safi)) +} + +func RouteFamilyToAfiSafi(rf RouteFamily) (uint16, uint8) { + return uint16(int(rf) >> 16), uint8(int(rf) & 0xff) +} + +type RouteFamily int + +func (f RouteFamily) String() string { + if n, y := AddressFamilyNameMap[f]; y { + return n + } + return fmt.Sprintf("UnknownFamily(%d)", f) +} + +const ( + RF_IPv4_UC RouteFamily = AFI_IP<<16 | SAFI_UNICAST + RF_IPv6_UC RouteFamily = AFI_IP6<<16 | SAFI_UNICAST + RF_IPv4_MC RouteFamily = AFI_IP<<16 | SAFI_MULTICAST + RF_IPv6_MC RouteFamily = AFI_IP6<<16 | SAFI_MULTICAST + RF_IPv4_VPN RouteFamily = AFI_IP<<16 | SAFI_MPLS_VPN + RF_IPv6_VPN RouteFamily = AFI_IP6<<16 | SAFI_MPLS_VPN + RF_IPv4_VPN_MC RouteFamily = AFI_IP<<16 | SAFI_MPLS_VPN_MULTICAST + RF_IPv6_VPN_MC RouteFamily = AFI_IP6<<16 | SAFI_MPLS_VPN_MULTICAST + RF_IPv4_MPLS RouteFamily = AFI_IP<<16 | SAFI_MPLS_LABEL + RF_IPv6_MPLS RouteFamily = AFI_IP6<<16 | SAFI_MPLS_LABEL + RF_VPLS RouteFamily = AFI_L2VPN<<16 | SAFI_VPLS + RF_EVPN RouteFamily = AFI_L2VPN<<16 | SAFI_EVPN + RF_RTC_UC RouteFamily = AFI_IP<<16 | SAFI_ROUTE_TARGET_CONSTRAINTS + RF_IPv4_ENCAP RouteFamily = AFI_IP<<16 | SAFI_ENCAPSULATION + RF_IPv6_ENCAP RouteFamily = AFI_IP6<<16 | SAFI_ENCAPSULATION + RF_FS_IPv4_UC RouteFamily = AFI_IP<<16 | SAFI_FLOW_SPEC_UNICAST + RF_FS_IPv4_VPN RouteFamily = AFI_IP<<16 | SAFI_FLOW_SPEC_VPN + RF_FS_IPv6_UC RouteFamily = AFI_IP6<<16 | SAFI_FLOW_SPEC_UNICAST + RF_FS_IPv6_VPN RouteFamily = AFI_IP6<<16 | SAFI_FLOW_SPEC_VPN + RF_FS_L2_VPN RouteFamily = AFI_L2VPN<<16 | SAFI_FLOW_SPEC_VPN + RF_OPAQUE RouteFamily = AFI_OPAQUE<<16 | SAFI_KEY_VALUE +) + +var AddressFamilyNameMap = map[RouteFamily]string{ + RF_IPv4_UC: "ipv4-unicast", + RF_IPv6_UC: "ipv6-unicast", + RF_IPv4_MC: "ipv4-multicast", + RF_IPv6_MC: "ipv6-multicast", + RF_IPv4_MPLS: "ipv4-labelled-unicast", + RF_IPv6_MPLS: "ipv6-labelled-unicast", + RF_IPv4_VPN: "l3vpn-ipv4-unicast", + RF_IPv6_VPN: "l3vpn-ipv6-unicast", + RF_IPv4_VPN_MC: "l3vpn-ipv4-multicast", + RF_IPv6_VPN_MC: "l3vpn-ipv6-multicast", + RF_VPLS: "l2vpn-vpls", + RF_EVPN: "l2vpn-evpn", + RF_RTC_UC: "rtc", + RF_IPv4_ENCAP: "ipv4-encap", + RF_IPv6_ENCAP: "ipv6-encap", + RF_FS_IPv4_UC: "ipv4-flowspec", + RF_FS_IPv4_VPN: "l3vpn-ipv4-flowspec", + RF_FS_IPv6_UC: "ipv6-flowspec", + RF_FS_IPv6_VPN: "l3vpn-ipv6-flowspec", + RF_FS_L2_VPN: "l2vpn-flowspec", + RF_OPAQUE: "opaque", +} + +var AddressFamilyValueMap = map[string]RouteFamily{ + AddressFamilyNameMap[RF_IPv4_UC]: RF_IPv4_UC, + AddressFamilyNameMap[RF_IPv6_UC]: RF_IPv6_UC, + AddressFamilyNameMap[RF_IPv4_MC]: RF_IPv4_MC, + AddressFamilyNameMap[RF_IPv6_MC]: RF_IPv6_MC, + AddressFamilyNameMap[RF_IPv4_MPLS]: RF_IPv4_MPLS, + AddressFamilyNameMap[RF_IPv6_MPLS]: RF_IPv6_MPLS, + AddressFamilyNameMap[RF_IPv4_VPN]: RF_IPv4_VPN, + AddressFamilyNameMap[RF_IPv6_VPN]: RF_IPv6_VPN, + AddressFamilyNameMap[RF_IPv4_VPN_MC]: RF_IPv4_VPN_MC, + AddressFamilyNameMap[RF_IPv6_VPN_MC]: RF_IPv6_VPN_MC, + AddressFamilyNameMap[RF_VPLS]: RF_VPLS, + AddressFamilyNameMap[RF_EVPN]: RF_EVPN, + AddressFamilyNameMap[RF_RTC_UC]: RF_RTC_UC, + AddressFamilyNameMap[RF_IPv4_ENCAP]: RF_IPv4_ENCAP, + AddressFamilyNameMap[RF_IPv6_ENCAP]: RF_IPv6_ENCAP, + AddressFamilyNameMap[RF_FS_IPv4_UC]: RF_FS_IPv4_UC, + AddressFamilyNameMap[RF_FS_IPv4_VPN]: RF_FS_IPv4_VPN, + AddressFamilyNameMap[RF_FS_IPv6_UC]: RF_FS_IPv6_UC, + AddressFamilyNameMap[RF_FS_IPv6_VPN]: RF_FS_IPv6_VPN, + AddressFamilyNameMap[RF_FS_L2_VPN]: RF_FS_L2_VPN, + AddressFamilyNameMap[RF_OPAQUE]: RF_OPAQUE, +} + +func GetRouteFamily(name string) (RouteFamily, error) { + if v, ok := AddressFamilyValueMap[name]; ok { + return v, nil + } + return RouteFamily(0), fmt.Errorf("%s isn't a valid route family name", name) +} + +func NewPrefixFromRouteFamily(afi uint16, safi uint8, prefixStr ...string) (prefix AddrPrefixInterface, err error) { + family := AfiSafiToRouteFamily(afi, safi) + + f := func(s string) AddrPrefixInterface { + addr, net, _ := net.ParseCIDR(s) + len, _ := net.Mask.Size() + switch family { + case RF_IPv4_UC, RF_IPv4_MC: + return NewIPAddrPrefix(uint8(len), addr.String()) + } + return NewIPv6AddrPrefix(uint8(len), addr.String()) + } + + switch family { + case RF_IPv4_UC, RF_IPv4_MC: + if len(prefixStr) > 0 { + prefix = f(prefixStr[0]) + } else { + prefix = NewIPAddrPrefix(0, "") + } + case RF_IPv6_UC, RF_IPv6_MC: + if len(prefixStr) > 0 { + prefix = f(prefixStr[0]) + } else { + prefix = NewIPv6AddrPrefix(0, "") + } + case RF_IPv4_VPN: + prefix = NewLabeledVPNIPAddrPrefix(0, "", *NewMPLSLabelStack(), nil) + case RF_IPv6_VPN: + prefix = NewLabeledVPNIPv6AddrPrefix(0, "", *NewMPLSLabelStack(), nil) + case RF_IPv4_MPLS: + prefix = NewLabeledIPAddrPrefix(0, "", *NewMPLSLabelStack()) + case RF_IPv6_MPLS: + prefix = NewLabeledIPv6AddrPrefix(0, "", *NewMPLSLabelStack()) + case RF_EVPN: + prefix = NewEVPNNLRI(0, nil) + case RF_RTC_UC: + prefix = &RouteTargetMembershipNLRI{} + case RF_IPv4_ENCAP: + prefix = NewEncapNLRI("") + case RF_IPv6_ENCAP: + prefix = NewEncapv6NLRI("") + case RF_FS_IPv4_UC: + prefix = &FlowSpecIPv4Unicast{FlowSpecNLRI{rf: RF_FS_IPv4_UC}} + case RF_FS_IPv4_VPN: + prefix = &FlowSpecIPv4VPN{FlowSpecNLRI{rf: RF_FS_IPv4_VPN}} + case RF_FS_IPv6_UC: + prefix = &FlowSpecIPv6Unicast{FlowSpecNLRI{rf: RF_FS_IPv6_UC}} + case RF_FS_IPv6_VPN: + prefix = &FlowSpecIPv6VPN{FlowSpecNLRI{rf: RF_FS_IPv6_VPN}} + case RF_FS_L2_VPN: + prefix = &FlowSpecL2VPN{FlowSpecNLRI{rf: RF_FS_L2_VPN}} + case RF_OPAQUE: + prefix = &OpaqueNLRI{} + default: + err = fmt.Errorf("unknown route family. AFI: %d, SAFI: %d", afi, safi) + } + return prefix, err +} + +type BGPAttrFlag uint8 + +const ( + BGP_ATTR_FLAG_EXTENDED_LENGTH BGPAttrFlag = 1 << 4 + BGP_ATTR_FLAG_PARTIAL BGPAttrFlag = 1 << 5 + BGP_ATTR_FLAG_TRANSITIVE BGPAttrFlag = 1 << 6 + BGP_ATTR_FLAG_OPTIONAL BGPAttrFlag = 1 << 7 +) + +func (f BGPAttrFlag) String() string { + strs := make([]string, 0, 4) + if f&BGP_ATTR_FLAG_EXTENDED_LENGTH > 0 { + strs = append(strs, "EXTENDED_LENGTH") + } + if f&BGP_ATTR_FLAG_PARTIAL > 0 { + strs = append(strs, "PARTIAL") + } + if f&BGP_ATTR_FLAG_TRANSITIVE > 0 { + strs = append(strs, "TRANSITIVE") + } + if f&BGP_ATTR_FLAG_OPTIONAL > 0 { + strs = append(strs, "OPTIONAL") + } + return strings.Join(strs, "|") +} + +type BGPAttrType uint8 + +const ( + _ BGPAttrType = iota + BGP_ATTR_TYPE_ORIGIN + BGP_ATTR_TYPE_AS_PATH + BGP_ATTR_TYPE_NEXT_HOP + BGP_ATTR_TYPE_MULTI_EXIT_DISC + BGP_ATTR_TYPE_LOCAL_PREF + BGP_ATTR_TYPE_ATOMIC_AGGREGATE + BGP_ATTR_TYPE_AGGREGATOR + BGP_ATTR_TYPE_COMMUNITIES + BGP_ATTR_TYPE_ORIGINATOR_ID + BGP_ATTR_TYPE_CLUSTER_LIST + _ + _ + _ + BGP_ATTR_TYPE_MP_REACH_NLRI // = 14 + BGP_ATTR_TYPE_MP_UNREACH_NLRI + BGP_ATTR_TYPE_EXTENDED_COMMUNITIES + BGP_ATTR_TYPE_AS4_PATH + BGP_ATTR_TYPE_AS4_AGGREGATOR + _ + _ + _ + BGP_ATTR_TYPE_PMSI_TUNNEL // = 22 + BGP_ATTR_TYPE_TUNNEL_ENCAP + _ + BGP_ATTR_TYPE_IP6_EXTENDED_COMMUNITIES // = 25 + BGP_ATTR_TYPE_AIGP // = 26 + BGP_ATTR_TYPE_LARGE_COMMUNITY BGPAttrType = 32 +) + +// NOTIFICATION Error Code RFC 4271 4.5. +const ( + _ = iota + BGP_ERROR_MESSAGE_HEADER_ERROR + BGP_ERROR_OPEN_MESSAGE_ERROR + BGP_ERROR_UPDATE_MESSAGE_ERROR + BGP_ERROR_HOLD_TIMER_EXPIRED + BGP_ERROR_FSM_ERROR + BGP_ERROR_CEASE + BGP_ERROR_ROUTE_REFRESH_MESSAGE_ERROR +) + +// NOTIFICATION Error Subcode for BGP_ERROR_MESSAGE_HEADER_ERROR +const ( + _ = iota + BGP_ERROR_SUB_CONNECTION_NOT_SYNCHRONIZED + BGP_ERROR_SUB_BAD_MESSAGE_LENGTH + BGP_ERROR_SUB_BAD_MESSAGE_TYPE +) + +// NOTIFICATION Error Subcode for BGP_ERROR_OPEN_MESSAGE_ERROR +const ( + _ = iota + BGP_ERROR_SUB_UNSUPPORTED_VERSION_NUMBER + BGP_ERROR_SUB_BAD_PEER_AS + BGP_ERROR_SUB_BAD_BGP_IDENTIFIER + BGP_ERROR_SUB_UNSUPPORTED_OPTIONAL_PARAMETER + BGP_ERROR_SUB_DEPRECATED_AUTHENTICATION_FAILURE + BGP_ERROR_SUB_UNACCEPTABLE_HOLD_TIME + BGP_ERROR_SUB_UNSUPPORTED_CAPABILITY +) + +// NOTIFICATION Error Subcode for BGP_ERROR_UPDATE_MESSAGE_ERROR +const ( + _ = iota + BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST + BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE + BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE + BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR + BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR + BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE + BGP_ERROR_SUB_DEPRECATED_ROUTING_LOOP + BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE + BGP_ERROR_SUB_OPTIONAL_ATTRIBUTE_ERROR + BGP_ERROR_SUB_INVALID_NETWORK_FIELD + BGP_ERROR_SUB_MALFORMED_AS_PATH +) + +// NOTIFICATION Error Subcode for BGP_ERROR_HOLD_TIMER_EXPIRED +const ( + _ = iota + BGP_ERROR_SUB_HOLD_TIMER_EXPIRED +) + +// NOTIFICATION Error Subcode for BGP_ERROR_FSM_ERROR +const ( + _ = iota + BGP_ERROR_SUB_RECEIVE_UNEXPECTED_MESSAGE_IN_OPENSENT_STATE + BGP_ERROR_SUB_RECEIVE_UNEXPECTED_MESSAGE_IN_OPENCONFIRM_STATE + BGP_ERROR_SUB_RECEIVE_UNEXPECTED_MESSAGE_IN_ESTABLISHED_STATE +) + +// NOTIFICATION Error Subcode for BGP_ERROR_CEASE (RFC 4486) +const ( + _ = iota + BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED + BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN + BGP_ERROR_SUB_PEER_DECONFIGURED + BGP_ERROR_SUB_ADMINISTRATIVE_RESET + BGP_ERROR_SUB_CONNECTION_REJECTED + BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE + BGP_ERROR_SUB_CONNECTION_COLLISION_RESOLUTION + BGP_ERROR_SUB_OUT_OF_RESOURCES + BGP_ERROR_SUB_HARD_RESET //draft-ietf-idr-bgp-gr-notification-07 +) + +// Constants for BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN and BGP_ERROR_SUB_ADMINISTRATIVE_RESET +const ( + BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX = 128 +) + +// NOTIFICATION Error Subcode for BGP_ERROR_ROUTE_REFRESH +const ( + _ = iota + BGP_ERROR_SUB_INVALID_MESSAGE_LENGTH +) + +type NotificationErrorCode uint16 + +func (c NotificationErrorCode) String() string { + code := uint8(uint16(c) >> 8) + subcode := uint8(uint16(c) & 0xff) + UNDEFINED := "undefined" + codeStr := UNDEFINED + subcodeList := []string{} + switch code { + case BGP_ERROR_MESSAGE_HEADER_ERROR: + codeStr = "header" + subcodeList = []string{ + UNDEFINED, + "connection not synchronized", + "bad message length", + "bad message type"} + case BGP_ERROR_OPEN_MESSAGE_ERROR: + codeStr = "open" + subcodeList = []string{ + UNDEFINED, + "unsupported version number", + "bad peer as", + "bad bgp identifier", + "unsupported optional parameter", + "deprecated authentication failure", + "unacceptable hold time", + "unsupported capability"} + case BGP_ERROR_UPDATE_MESSAGE_ERROR: + codeStr = "update" + subcodeList = []string{ + UNDEFINED, + "malformed attribute list", + "unrecognized well known attribute", + "missing well known attribute", + "attribute flags error", + "attribute length error", + "invalid origin attribute", + "deprecated routing loop", + "invalid next hop attribute", + "optional attribute error", + "invalid network field", + "sub malformed as path"} + case BGP_ERROR_HOLD_TIMER_EXPIRED: + codeStr = "hold timer expired" + subcodeList = []string{ + UNDEFINED, + "hold timer expired"} + case BGP_ERROR_FSM_ERROR: + codeStr = "fsm" + subcodeList = []string{ + UNDEFINED, + "receive unexpected message in opensent state", + "receive unexpected message in openconfirm state", + "receive unexpected message in established state"} + case BGP_ERROR_CEASE: + codeStr = "cease" + subcodeList = []string{ + UNDEFINED, + "maximum number of prefixes reached", + "administrative shutdown", + "peer deconfigured", + "administrative reset", + "connection rejected", + "other configuration change", + "connection collision resolution", + "out of resources"} + case BGP_ERROR_ROUTE_REFRESH_MESSAGE_ERROR: + codeStr = "route refresh" + subcodeList = []string{"invalid message length"} + } + subcodeStr := func(idx uint8, l []string) string { + if len(l) == 0 || int(idx) > len(l)-1 { + return UNDEFINED + } + return l[idx] + }(subcode, subcodeList) + return fmt.Sprintf("code %v(%v) subcode %v(%v)", code, codeStr, subcode, subcodeStr) +} + +func NewNotificationErrorCode(code, subcode uint8) NotificationErrorCode { + return NotificationErrorCode(uint16(code)<<8 | uint16(subcode)) +} + +var PathAttrFlags map[BGPAttrType]BGPAttrFlag = map[BGPAttrType]BGPAttrFlag{ + BGP_ATTR_TYPE_ORIGIN: BGP_ATTR_FLAG_TRANSITIVE, + BGP_ATTR_TYPE_AS_PATH: BGP_ATTR_FLAG_TRANSITIVE, + BGP_ATTR_TYPE_NEXT_HOP: BGP_ATTR_FLAG_TRANSITIVE, + BGP_ATTR_TYPE_MULTI_EXIT_DISC: BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_LOCAL_PREF: BGP_ATTR_FLAG_TRANSITIVE, + BGP_ATTR_TYPE_ATOMIC_AGGREGATE: BGP_ATTR_FLAG_TRANSITIVE, + BGP_ATTR_TYPE_AGGREGATOR: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_COMMUNITIES: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_ORIGINATOR_ID: BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_CLUSTER_LIST: BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_MP_REACH_NLRI: BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_MP_UNREACH_NLRI: BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_EXTENDED_COMMUNITIES: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_AS4_PATH: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_AS4_AGGREGATOR: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_PMSI_TUNNEL: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_TUNNEL_ENCAP: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_IP6_EXTENDED_COMMUNITIES: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_AIGP: BGP_ATTR_FLAG_OPTIONAL, + BGP_ATTR_TYPE_LARGE_COMMUNITY: BGP_ATTR_FLAG_TRANSITIVE | BGP_ATTR_FLAG_OPTIONAL, +} + +// getPathAttrFlags returns BGP Path Attribute flags value from its type and +// length (byte length of value field). +func getPathAttrFlags(typ BGPAttrType, length int) BGPAttrFlag { + flags := PathAttrFlags[typ] + if length > 255 { + flags |= BGP_ATTR_FLAG_EXTENDED_LENGTH + } + return flags +} + +type PathAttributeInterface interface { + DecodeFromBytes([]byte, ...*MarshallingOption) error + Serialize(...*MarshallingOption) ([]byte, error) + Len(...*MarshallingOption) int + GetFlags() BGPAttrFlag + GetType() BGPAttrType + String() string + MarshalJSON() ([]byte, error) + Flat() map[string]string +} + +type PathAttribute struct { + Flags BGPAttrFlag + Type BGPAttrType + Length uint16 // length of Value +} + +func (p *PathAttribute) Len(options ...*MarshallingOption) int { + if p.Flags&BGP_ATTR_FLAG_EXTENDED_LENGTH != 0 { + return 4 + int(p.Length) + } + return 3 + int(p.Length) +} + +func (p *PathAttribute) GetFlags() BGPAttrFlag { + return p.Flags +} + +func (p *PathAttribute) GetType() BGPAttrType { + return p.Type +} + +func (p *PathAttribute) DecodeFromBytes(data []byte, options ...*MarshallingOption) (value []byte, err error) { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + if len(data) < 2 { + return nil, NewMessageError(eCode, eSubCode, data, "attribute header length is short") + } + p.Flags = BGPAttrFlag(data[0]) + p.Type = BGPAttrType(data[1]) + if eMsg := validatePathAttributeFlags(p.Type, p.Flags); eMsg != "" { + return nil, NewMessageError(eCode, BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR, data, eMsg) + } + + if p.Flags&BGP_ATTR_FLAG_EXTENDED_LENGTH != 0 { + if len(data) < 4 { + return nil, NewMessageError(eCode, eSubCode, data, "attribute header length is short") + } + p.Length = binary.BigEndian.Uint16(data[2:4]) + data = data[4:] + } else { + if len(data) < 3 { + return nil, NewMessageError(eCode, eSubCode, data, "attribute header length is short") + } + p.Length = uint16(data[2]) + data = data[3:] + } + if len(data) < int(p.Length) { + return nil, NewMessageError(eCode, eSubCode, data, "attribute value length is short") + } + + return data[:p.Length], nil +} + +func (p *PathAttribute) Serialize(value []byte, options ...*MarshallingOption) ([]byte, error) { + // Note: Do not update "p.Flags" and "p.Length" to avoid data race. + flags := p.Flags + length := uint16(len(value)) + if flags&BGP_ATTR_FLAG_EXTENDED_LENGTH == 0 && length > 255 { + flags |= BGP_ATTR_FLAG_EXTENDED_LENGTH + } + var buf []byte + if flags&BGP_ATTR_FLAG_EXTENDED_LENGTH != 0 { + buf = append(make([]byte, 4), value...) + binary.BigEndian.PutUint16(buf[2:4], length) + } else { + buf = append(make([]byte, 3), value...) + buf[2] = byte(length) + } + buf[0] = uint8(flags) + buf[1] = uint8(p.Type) + return buf, nil +} + +type PathAttributeOrigin struct { + PathAttribute + Value uint8 +} + +func (p *PathAttributeOrigin) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 1 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "Origin attribute length is incorrect") + } + p.Value = value[0] + return nil +} + +func (p *PathAttributeOrigin) Serialize(options ...*MarshallingOption) ([]byte, error) { + return p.PathAttribute.Serialize([]byte{p.Value}, options...) +} + +func (p *PathAttributeOrigin) String() string { + typ := "-" + switch p.Value { + case BGP_ORIGIN_ATTR_TYPE_IGP: + typ = "i" + case BGP_ORIGIN_ATTR_TYPE_EGP: + typ = "e" + case BGP_ORIGIN_ATTR_TYPE_INCOMPLETE: + typ = "?" + } + return fmt.Sprintf("{Origin: %s}", typ) +} + +func (p *PathAttributeOrigin) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value uint8 `json:"value"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeOrigin(value uint8) *PathAttributeOrigin { + t := BGP_ATTR_TYPE_ORIGIN + return &PathAttributeOrigin{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 1, + }, + Value: value, + } +} + +type AsPathParamFormat struct { + start string + end string + separator string +} + +var asPathParamFormatMap = map[uint8]*AsPathParamFormat{ + BGP_ASPATH_ATTR_TYPE_SET: {"{", "}", ","}, + BGP_ASPATH_ATTR_TYPE_SEQ: {"", "", " "}, + BGP_ASPATH_ATTR_TYPE_CONFED_SET: {"(", ")", " "}, + BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: {"[", "]", ","}, +} + +type AsPathParamInterface interface { + GetType() uint8 + GetAS() []uint32 + Serialize() ([]byte, error) + DecodeFromBytes([]byte) error + Len() int + ASLen() int + MarshalJSON() ([]byte, error) + String() string +} + +func AsPathString(aspath *PathAttributeAsPath) string { + s := bytes.NewBuffer(make([]byte, 0, 64)) + for i, param := range aspath.Value { + segType := param.GetType() + asList := param.GetAS() + if i != 0 { + s.WriteString(" ") + } + + sep := " " + switch segType { + case BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + s.WriteString("(") + case BGP_ASPATH_ATTR_TYPE_CONFED_SET: + s.WriteString("[") + sep = "," + case BGP_ASPATH_ATTR_TYPE_SET: + s.WriteString("{") + sep = "," + } + for j, as := range asList { + s.WriteString(fmt.Sprintf("%d", as)) + if j != len(asList)-1 { + s.WriteString(sep) + } + } + switch segType { + case BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + s.WriteString(")") + case BGP_ASPATH_ATTR_TYPE_CONFED_SET: + s.WriteString("]") + case BGP_ASPATH_ATTR_TYPE_SET: + s.WriteString("}") + } + } + return s.String() +} + +type AsPathParam struct { + Type uint8 + Num uint8 + AS []uint16 +} + +func (a *AsPathParam) GetType() uint8 { + return a.Type +} + +func (a *AsPathParam) GetAS() []uint32 { + nums := make([]uint32, 0, len(a.AS)) + for _, as := range a.AS { + nums = append(nums, uint32(as)) + } + return nums +} + +func (a *AsPathParam) Serialize() ([]byte, error) { + buf := make([]byte, 2+len(a.AS)*2) + buf[0] = uint8(a.Type) + buf[1] = a.Num + for j, as := range a.AS { + binary.BigEndian.PutUint16(buf[2+j*2:], as) + } + return buf, nil +} + +func (a *AsPathParam) DecodeFromBytes(data []byte) error { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH) + if len(data) < 2 { + return NewMessageError(eCode, eSubCode, nil, "AS param header length is short") + } + a.Type = data[0] + a.Num = data[1] + data = data[2:] + if len(data) < int(a.Num*2) { + return NewMessageError(eCode, eSubCode, nil, "AS param data length is short") + } + for i := 0; i < int(a.Num); i++ { + a.AS = append(a.AS, binary.BigEndian.Uint16(data)) + data = data[2:] + } + return nil +} + +func (a *AsPathParam) Len() int { + return 2 + len(a.AS)*2 +} + +func (a *AsPathParam) ASLen() int { + switch a.Type { + case BGP_ASPATH_ATTR_TYPE_SEQ: + return len(a.AS) + case BGP_ASPATH_ATTR_TYPE_SET: + return 1 + case BGP_ASPATH_ATTR_TYPE_CONFED_SET, BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + return 0 + } + return 0 +} + +func (a *AsPathParam) String() string { + format, ok := asPathParamFormatMap[a.Type] + if !ok { + return fmt.Sprintf("%v", a.AS) + } + aspath := make([]string, 0, len(a.AS)) + for _, asn := range a.AS { + aspath = append(aspath, fmt.Sprintf("%d", asn)) + } + s := bytes.NewBuffer(make([]byte, 0, 32)) + s.WriteString(format.start) + s.WriteString(strings.Join(aspath, format.separator)) + s.WriteString(format.end) + return s.String() +} + +func (a *AsPathParam) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint8 `json:"segment_type"` + Num uint8 `json:"num"` + AS []uint16 `json:"asns"` + }{ + Type: a.Type, + Num: a.Num, + AS: a.AS, + }) +} + +func NewAsPathParam(segType uint8, as []uint16) *AsPathParam { + return &AsPathParam{ + Type: segType, + Num: uint8(len(as)), + AS: as, + } +} + +type As4PathParam struct { + Type uint8 + Num uint8 + AS []uint32 +} + +func (a *As4PathParam) GetType() uint8 { + return a.Type +} + +func (a *As4PathParam) GetAS() []uint32 { + return a.AS +} + +func (a *As4PathParam) Serialize() ([]byte, error) { + buf := make([]byte, 2+len(a.AS)*4) + buf[0] = a.Type + buf[1] = a.Num + for j, as := range a.AS { + binary.BigEndian.PutUint32(buf[2+j*4:], as) + } + return buf, nil +} + +func (a *As4PathParam) DecodeFromBytes(data []byte) error { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH) + if len(data) < 2 { + return NewMessageError(eCode, eSubCode, nil, "AS4 param header length is short") + } + a.Type = data[0] + a.Num = data[1] + data = data[2:] + if len(data) < int(a.Num)*4 { + return NewMessageError(eCode, eSubCode, nil, "AS4 param data length is short") + } + for i := 0; i < int(a.Num); i++ { + a.AS = append(a.AS, binary.BigEndian.Uint32(data)) + data = data[4:] + } + return nil +} + +func (a *As4PathParam) Len() int { + return 2 + len(a.AS)*4 +} + +func (a *As4PathParam) ASLen() int { + switch a.Type { + case BGP_ASPATH_ATTR_TYPE_SEQ: + return len(a.AS) + case BGP_ASPATH_ATTR_TYPE_SET: + return 1 + case BGP_ASPATH_ATTR_TYPE_CONFED_SET, BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + return 0 + } + return 0 +} + +func (a *As4PathParam) String() string { + format, ok := asPathParamFormatMap[a.Type] + if !ok { + return fmt.Sprintf("%v", a.AS) + } + aspath := make([]string, 0, len(a.AS)) + for _, asn := range a.AS { + aspath = append(aspath, fmt.Sprintf("%d", asn)) + } + s := bytes.NewBuffer(make([]byte, 0, 32)) + s.WriteString(format.start) + s.WriteString(strings.Join(aspath, format.separator)) + s.WriteString(format.end) + return s.String() +} + +func (a *As4PathParam) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type uint8 `json:"segment_type"` + Num uint8 `json:"num"` + AS []uint32 `json:"asns"` + }{ + Type: a.Type, + Num: a.Num, + AS: a.AS, + }) +} + +func NewAs4PathParam(segType uint8, as []uint32) *As4PathParam { + return &As4PathParam{ + Type: segType, + Num: uint8(len(as)), + AS: as, + } +} + +type PathAttributeAsPath struct { + PathAttribute + Value []AsPathParamInterface +} + +func (p *PathAttributeAsPath) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length == 0 { + // ibgp or something + return nil + } + isAs4, err := validateAsPathValueBytes(value) + if err != nil { + err.(*MessageError).Data, _ = p.PathAttribute.Serialize(value, options...) + return err + } + for len(value) > 0 { + var tuple AsPathParamInterface + if isAs4 { + tuple = &As4PathParam{} + } else { + tuple = &AsPathParam{} + } + err := tuple.DecodeFromBytes(value) + if err != nil { + return err + } + p.Value = append(p.Value, tuple) + value = value[tuple.Len():] + } + return nil +} + +func (p *PathAttributeAsPath) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0) + for _, v := range p.Value { + vbuf, err := v.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, vbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeAsPath) String() string { + params := make([]string, 0, len(p.Value)) + for _, param := range p.Value { + params = append(params, param.String()) + } + return strings.Join(params, " ") +} + +func (p *PathAttributeAsPath) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []AsPathParamInterface `json:"as_paths"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeAsPath(value []AsPathParamInterface) *PathAttributeAsPath { + var l int + for _, v := range value { + l += v.Len() + } + t := BGP_ATTR_TYPE_AS_PATH + return &PathAttributeAsPath{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: value, + } +} + +type PathAttributeNextHop struct { + PathAttribute + Value net.IP +} + +func (p *PathAttributeNextHop) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 4 && p.Length != 16 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "nexthop length isn't correct") + } + p.Value = value + return nil +} + +func (p *PathAttributeNextHop) Serialize(options ...*MarshallingOption) ([]byte, error) { + return p.PathAttribute.Serialize(p.Value, options...) +} + +func (p *PathAttributeNextHop) String() string { + return fmt.Sprintf("{Nexthop: %s}", p.Value) +} + +func (p *PathAttributeNextHop) MarshalJSON() ([]byte, error) { + value := "0.0.0.0" + if p.Value != nil { + value = p.Value.String() + } + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value string `json:"nexthop"` + }{ + Type: p.GetType(), + Value: value, + }) +} + +func NewPathAttributeNextHop(value string) *PathAttributeNextHop { + t := BGP_ATTR_TYPE_NEXT_HOP + return &PathAttributeNextHop{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 4, + }, + Value: net.ParseIP(value).To4(), + } +} + +type PathAttributeMultiExitDisc struct { + PathAttribute + Value uint32 +} + +func (p *PathAttributeMultiExitDisc) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 4 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "med length isn't correct") + } + p.Value = binary.BigEndian.Uint32(value) + return nil +} + +func (p *PathAttributeMultiExitDisc) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, p.Value) + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeMultiExitDisc) String() string { + return fmt.Sprintf("{Med: %d}", p.Value) +} + +func (p *PathAttributeMultiExitDisc) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value uint32 `json:"metric"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeMultiExitDisc(value uint32) *PathAttributeMultiExitDisc { + t := BGP_ATTR_TYPE_MULTI_EXIT_DISC + return &PathAttributeMultiExitDisc{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 4, + }, + Value: value, + } +} + +type PathAttributeLocalPref struct { + PathAttribute + Value uint32 +} + +func (p *PathAttributeLocalPref) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 4 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "local pref length isn't correct") + } + p.Value = binary.BigEndian.Uint32(value) + return nil +} + +func (p *PathAttributeLocalPref) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, p.Value) + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeLocalPref) String() string { + return fmt.Sprintf("{LocalPref: %d}", p.Value) +} + +func (p *PathAttributeLocalPref) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value uint32 `json:"value"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeLocalPref(value uint32) *PathAttributeLocalPref { + t := BGP_ATTR_TYPE_LOCAL_PREF + return &PathAttributeLocalPref{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 4, + }, + Value: value, + } +} + +type PathAttributeAtomicAggregate struct { + PathAttribute +} + +func (p *PathAttributeAtomicAggregate) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + _, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "atomic aggregate should have no value") + } + return nil +} + +func (p *PathAttributeAtomicAggregate) Serialize(options ...*MarshallingOption) ([]byte, error) { + return p.PathAttribute.Serialize(nil, options...) +} + +func (p *PathAttributeAtomicAggregate) String() string { + return "{AtomicAggregate}" +} + +func (p *PathAttributeAtomicAggregate) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + }{ + Type: p.GetType(), + }) +} + +func NewPathAttributeAtomicAggregate() *PathAttributeAtomicAggregate { + t := BGP_ATTR_TYPE_ATOMIC_AGGREGATE + return &PathAttributeAtomicAggregate{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 0, + }, + } +} + +type PathAttributeAggregatorParam struct { + AS uint32 + Askind reflect.Kind + Address net.IP +} + +type PathAttributeAggregator struct { + PathAttribute + Value PathAttributeAggregatorParam +} + +func (p *PathAttributeAggregator) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + switch p.Length { + case 6: + p.Value.Askind = reflect.Uint16 + p.Value.AS = uint32(binary.BigEndian.Uint16(value[0:2])) + p.Value.Address = value[2:] + case 8: + p.Value.Askind = reflect.Uint32 + p.Value.AS = binary.BigEndian.Uint32(value[0:4]) + p.Value.Address = value[4:] + default: + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "aggregator length isn't correct") + } + return nil +} + +func (p *PathAttributeAggregator) Serialize(options ...*MarshallingOption) ([]byte, error) { + var buf []byte + switch p.Value.Askind { + case reflect.Uint16: + buf = make([]byte, 6) + binary.BigEndian.PutUint16(buf, uint16(p.Value.AS)) + copy(buf[2:], p.Value.Address) + case reflect.Uint32: + buf = make([]byte, 8) + binary.BigEndian.PutUint32(buf, p.Value.AS) + copy(buf[4:], p.Value.Address) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeAggregator) String() string { + return fmt.Sprintf("{Aggregate: {AS: %d, Address: %s}}", p.Value.AS, p.Value.Address) +} + +func (p *PathAttributeAggregator) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + AS uint32 `json:"as"` + Address string `json:"address"` + }{ + Type: p.GetType(), + AS: p.Value.AS, + Address: p.Value.Address.String(), + }) +} + +func NewPathAttributeAggregator(as interface{}, address string) *PathAttributeAggregator { + v := reflect.ValueOf(as) + asKind := v.Kind() + var l uint16 + switch asKind { + case reflect.Uint16: + l = 6 + case reflect.Uint32: + l = 8 + default: + // Invalid type + return nil + } + t := BGP_ATTR_TYPE_AGGREGATOR + return &PathAttributeAggregator{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: l, + }, + Value: PathAttributeAggregatorParam{ + AS: uint32(v.Uint()), + Askind: asKind, + Address: net.ParseIP(address).To4(), + }, + } +} + +type PathAttributeCommunities struct { + PathAttribute + Value []uint32 +} + +func (p *PathAttributeCommunities) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length%4 != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "communities length isn't correct") + } + for len(value) >= 4 { + p.Value = append(p.Value, binary.BigEndian.Uint32(value)) + value = value[4:] + } + return nil +} + +func (p *PathAttributeCommunities) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, len(p.Value)*4) + for i, v := range p.Value { + binary.BigEndian.PutUint32(buf[i*4:], v) + } + return p.PathAttribute.Serialize(buf, options...) +} + +type WellKnownCommunity uint32 + +const ( + COMMUNITY_INTERNET WellKnownCommunity = 0x00000000 + COMMUNITY_PLANNED_SHUT WellKnownCommunity = 0xffff0000 + COMMUNITY_ACCEPT_OWN WellKnownCommunity = 0xffff0001 + COMMUNITY_ROUTE_FILTER_TRANSLATED_v4 WellKnownCommunity = 0xffff0002 + COMMUNITY_ROUTE_FILTER_v4 WellKnownCommunity = 0xffff0003 + COMMUNITY_ROUTE_FILTER_TRANSLATED_v6 WellKnownCommunity = 0xffff0004 + COMMUNITY_ROUTE_FILTER_v6 WellKnownCommunity = 0xffff0005 + COMMUNITY_LLGR_STALE WellKnownCommunity = 0xffff0006 + COMMUNITY_NO_LLGR WellKnownCommunity = 0xffff0007 + COMMUNITY_BLACKHOLE WellKnownCommunity = 0xffff029a + COMMUNITY_NO_EXPORT WellKnownCommunity = 0xffffff01 + COMMUNITY_NO_ADVERTISE WellKnownCommunity = 0xffffff02 + COMMUNITY_NO_EXPORT_SUBCONFED WellKnownCommunity = 0xffffff03 + COMMUNITY_NO_PEER WellKnownCommunity = 0xffffff04 +) + +var WellKnownCommunityNameMap = map[WellKnownCommunity]string{ + COMMUNITY_INTERNET: "internet", + COMMUNITY_PLANNED_SHUT: "planned-shut", + COMMUNITY_ACCEPT_OWN: "accept-own", + COMMUNITY_ROUTE_FILTER_TRANSLATED_v4: "route-filter-translated-v4", + COMMUNITY_ROUTE_FILTER_v4: "route-filter-v4", + COMMUNITY_ROUTE_FILTER_TRANSLATED_v6: "route-filter-translated-v6", + COMMUNITY_ROUTE_FILTER_v6: "route-filter-v6", + COMMUNITY_LLGR_STALE: "llgr-stale", + COMMUNITY_NO_LLGR: "no-llgr", + COMMUNITY_BLACKHOLE: "blackhole", + COMMUNITY_NO_EXPORT: "no-export", + COMMUNITY_NO_ADVERTISE: "no-advertise", + COMMUNITY_NO_EXPORT_SUBCONFED: "no-export-subconfed", + COMMUNITY_NO_PEER: "no-peer", +} + +var WellKnownCommunityValueMap = map[string]WellKnownCommunity{ + WellKnownCommunityNameMap[COMMUNITY_INTERNET]: COMMUNITY_INTERNET, + WellKnownCommunityNameMap[COMMUNITY_PLANNED_SHUT]: COMMUNITY_PLANNED_SHUT, + WellKnownCommunityNameMap[COMMUNITY_ACCEPT_OWN]: COMMUNITY_ACCEPT_OWN, + WellKnownCommunityNameMap[COMMUNITY_ROUTE_FILTER_TRANSLATED_v4]: COMMUNITY_ROUTE_FILTER_TRANSLATED_v4, + WellKnownCommunityNameMap[COMMUNITY_ROUTE_FILTER_v4]: COMMUNITY_ROUTE_FILTER_v4, + WellKnownCommunityNameMap[COMMUNITY_ROUTE_FILTER_TRANSLATED_v6]: COMMUNITY_ROUTE_FILTER_TRANSLATED_v6, + WellKnownCommunityNameMap[COMMUNITY_ROUTE_FILTER_v6]: COMMUNITY_ROUTE_FILTER_v6, + WellKnownCommunityNameMap[COMMUNITY_LLGR_STALE]: COMMUNITY_LLGR_STALE, + WellKnownCommunityNameMap[COMMUNITY_NO_LLGR]: COMMUNITY_NO_LLGR, + WellKnownCommunityNameMap[COMMUNITY_NO_EXPORT]: COMMUNITY_NO_EXPORT, + WellKnownCommunityNameMap[COMMUNITY_BLACKHOLE]: COMMUNITY_BLACKHOLE, + WellKnownCommunityNameMap[COMMUNITY_NO_ADVERTISE]: COMMUNITY_NO_ADVERTISE, + WellKnownCommunityNameMap[COMMUNITY_NO_EXPORT_SUBCONFED]: COMMUNITY_NO_EXPORT_SUBCONFED, + WellKnownCommunityNameMap[COMMUNITY_NO_PEER]: COMMUNITY_NO_PEER, +} + +func (p *PathAttributeCommunities) String() string { + l := []string{} + for _, v := range p.Value { + n, ok := WellKnownCommunityNameMap[WellKnownCommunity(v)] + if ok { + l = append(l, n) + } else { + l = append(l, fmt.Sprintf("%d:%d", (0xffff0000&v)>>16, 0xffff&v)) + } + } + return fmt.Sprintf("{Communities: %s}", strings.Join(l, ", ")) +} + +func (p *PathAttributeCommunities) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []uint32 `json:"communities"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeCommunities(value []uint32) *PathAttributeCommunities { + l := len(value) * 4 + t := BGP_ATTR_TYPE_COMMUNITIES + return &PathAttributeCommunities{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: value, + } +} + +type PathAttributeOriginatorId struct { + PathAttribute + Value net.IP +} + +func (p *PathAttributeOriginatorId) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 4 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "originator id length isn't correct") + } + p.Value = value + return nil +} + +func (p *PathAttributeOriginatorId) String() string { + return fmt.Sprintf("{Originator: %s}", p.Value) +} + +func (p *PathAttributeOriginatorId) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value string `json:"value"` + }{ + Type: p.GetType(), + Value: p.Value.String(), + }) +} + +func (p *PathAttributeOriginatorId) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 4) + copy(buf, p.Value) + return p.PathAttribute.Serialize(buf, options...) +} + +func NewPathAttributeOriginatorId(value string) *PathAttributeOriginatorId { + t := BGP_ATTR_TYPE_ORIGINATOR_ID + return &PathAttributeOriginatorId{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 4, + }, + Value: net.ParseIP(value).To4(), + } +} + +type PathAttributeClusterList struct { + PathAttribute + Value []net.IP +} + +func (p *PathAttributeClusterList) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length%4 != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "clusterlist length isn't correct") + } + for len(value) >= 4 { + p.Value = append(p.Value, value[:4]) + value = value[4:] + } + return nil +} + +func (p *PathAttributeClusterList) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, len(p.Value)*4) + for i, v := range p.Value { + copy(buf[i*4:], v) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeClusterList) String() string { + return fmt.Sprintf("{ClusterList: %v}", p.Value) +} + +func (p *PathAttributeClusterList) MarshalJSON() ([]byte, error) { + value := make([]string, 0, len(p.Value)) + for _, v := range p.Value { + value = append(value, v.String()) + } + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []string `json:"value"` + }{ + Type: p.GetType(), + Value: value, + }) +} + +func NewPathAttributeClusterList(value []string) *PathAttributeClusterList { + l := len(value) * 4 + list := make([]net.IP, len(value)) + for i, v := range value { + list[i] = net.ParseIP(v).To4() + } + t := BGP_ATTR_TYPE_CLUSTER_LIST + return &PathAttributeClusterList{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: list, + } +} + +type PathAttributeMpReachNLRI struct { + PathAttribute + Nexthop net.IP + LinkLocalNexthop net.IP + AFI uint16 + SAFI uint8 + Value []AddrPrefixInterface +} + +func (p *PathAttributeMpReachNLRI) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + eData, _ := p.PathAttribute.Serialize(value, options...) + if p.Length < 3 { + return NewMessageError(eCode, eSubCode, value, "mpreach header length is short") + } + afi := binary.BigEndian.Uint16(value[0:2]) + safi := value[2] + p.AFI = afi + p.SAFI = safi + _, err = NewPrefixFromRouteFamily(afi, safi) + if err != nil { + return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, eData, err.Error()) + } + nexthoplen := int(value[3]) + if len(value) < 4+nexthoplen { + return NewMessageError(eCode, eSubCode, value, "mpreach nexthop length is short") + } + nexthopbin := value[4 : 4+nexthoplen] + if nexthoplen > 0 { + v4addrlen := 4 + v6addrlen := 16 + offset := 0 + if safi == SAFI_MPLS_VPN { + offset = 8 + } + switch nexthoplen { + case 2 * (offset + v6addrlen): + p.LinkLocalNexthop = nexthopbin[offset+v6addrlen+offset : 2*(offset+v6addrlen)] + fallthrough + case offset + v6addrlen: + p.Nexthop = nexthopbin[offset : offset+v6addrlen] + case offset + v4addrlen: + p.Nexthop = nexthopbin[offset : offset+v4addrlen] + default: + return NewMessageError(eCode, eSubCode, value, "mpreach nexthop length is incorrect") + } + } + value = value[4+nexthoplen:] + // skip reserved + if len(value) == 0 { + return NewMessageError(eCode, eSubCode, value, "no skip byte") + } + value = value[1:] + addpathLen := 0 + if IsAddPathEnabled(true, AfiSafiToRouteFamily(afi, safi), options) { + addpathLen = 4 + } + for len(value) > 0 { + prefix, err := NewPrefixFromRouteFamily(afi, safi) + if err != nil { + return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, eData, err.Error()) + } + err = prefix.DecodeFromBytes(value, options...) + if err != nil { + return err + } + if prefix.Len(options...)+addpathLen > len(value) { + return NewMessageError(eCode, eSubCode, value, "prefix length is incorrect") + } + value = value[prefix.Len(options...)+addpathLen:] + p.Value = append(p.Value, prefix) + } + return nil +} + +func (p *PathAttributeMpReachNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + afi := p.AFI + safi := p.SAFI + nexthoplen := 4 + if afi == AFI_IP6 || p.Nexthop.To4() == nil { + nexthoplen = 16 + } + offset := 0 + switch safi { + case SAFI_MPLS_VPN: + offset = 8 + nexthoplen += offset + case SAFI_FLOW_SPEC_VPN, SAFI_FLOW_SPEC_UNICAST: + nexthoplen = 0 + } + if p.LinkLocalNexthop != nil { + nexthoplen *= 2 + } + buf := make([]byte, 4+nexthoplen) + binary.BigEndian.PutUint16(buf[0:], afi) + buf[2] = safi + buf[3] = uint8(nexthoplen) + if nexthoplen != 0 { + if p.Nexthop.To4() == nil { + copy(buf[4+offset:], p.Nexthop.To16()) + if p.LinkLocalNexthop != nil { + copy(buf[4+offset+16:], p.LinkLocalNexthop.To16()) + } + } else { + copy(buf[4+offset:], p.Nexthop) + } + } + buf = append(buf, make([]byte, 1)...) + for _, prefix := range p.Value { + pbuf, err := prefix.Serialize(options...) + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeMpReachNLRI) MarshalJSON() ([]byte, error) { + nexthop := p.Nexthop.String() + if p.Nexthop == nil { + switch p.AFI { + case AFI_IP: + nexthop = "0.0.0.0" + case AFI_IP6: + nexthop = "::" + default: + nexthop = "fictitious" + } + } + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Nexthop string `json:"nexthop"` + AFI uint16 `json:"afi"` + SAFI uint8 `json:"safi"` + Value []AddrPrefixInterface `json:"value"` + }{ + Type: p.GetType(), + Nexthop: nexthop, + AFI: p.AFI, + SAFI: p.SAFI, + Value: p.Value, + }) +} + +func (p *PathAttributeMpReachNLRI) String() string { + return fmt.Sprintf("{MpReach(%s): {Nexthop: %s, NLRIs: %s}}", AfiSafiToRouteFamily(p.AFI, p.SAFI), p.Nexthop, p.Value) +} + +func NewPathAttributeMpReachNLRI(nexthop string, nlri []AddrPrefixInterface) *PathAttributeMpReachNLRI { + // AFI(2) + SAFI(1) + NexthopLength(1) + Nexthop(variable) + // + Reserved(1) + NLRI(variable) + l := 5 + var afi uint16 + var safi uint8 + if len(nlri) > 0 { + afi = nlri[0].AFI() + safi = nlri[0].SAFI() + } + nh := net.ParseIP(nexthop) + if nh.To4() != nil && afi != AFI_IP6 { + nh = nh.To4() + switch safi { + case SAFI_MPLS_VPN: + l += 12 + case SAFI_FLOW_SPEC_VPN, SAFI_FLOW_SPEC_UNICAST: + // Should not have Nexthop + default: + l += 4 + } + } else { + switch safi { + case SAFI_MPLS_VPN: + l += 24 + case SAFI_FLOW_SPEC_VPN, SAFI_FLOW_SPEC_UNICAST: + // Should not have Nexthop + default: + l += 16 + } + } + var nlriLen int + for _, n := range nlri { + l += n.Len() + nBuf, _ := n.Serialize() + nlriLen += len(nBuf) + } + t := BGP_ATTR_TYPE_MP_REACH_NLRI + return &PathAttributeMpReachNLRI{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Nexthop: nh, + AFI: afi, + SAFI: safi, + Value: nlri, + } +} + +type PathAttributeMpUnreachNLRI struct { + PathAttribute + AFI uint16 + SAFI uint8 + Value []AddrPrefixInterface +} + +func (p *PathAttributeMpUnreachNLRI) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + eData, _ := p.PathAttribute.Serialize(value, options...) + if p.Length < 3 { + return NewMessageError(eCode, eSubCode, value, "unreach header length is incorrect") + } + afi := binary.BigEndian.Uint16(value[0:2]) + safi := value[2] + _, err = NewPrefixFromRouteFamily(afi, safi) + if err != nil { + return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, eData, err.Error()) + } + value = value[3:] + p.AFI = afi + p.SAFI = safi + addpathLen := 0 + if IsAddPathEnabled(true, AfiSafiToRouteFamily(afi, safi), options) { + addpathLen = 4 + } + for len(value) > 0 { + prefix, err := NewPrefixFromRouteFamily(afi, safi) + if err != nil { + return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, eData, err.Error()) + } + err = prefix.DecodeFromBytes(value, options...) + if err != nil { + return err + } + if prefix.Len(options...)+addpathLen > len(value) { + return NewMessageError(eCode, eSubCode, eData, "prefix length is incorrect") + } + value = value[prefix.Len(options...)+addpathLen:] + p.Value = append(p.Value, prefix) + } + return nil +} + +func (p *PathAttributeMpUnreachNLRI) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 3) + binary.BigEndian.PutUint16(buf, p.AFI) + buf[2] = p.SAFI + for _, prefix := range p.Value { + pbuf, err := prefix.Serialize(options...) + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeMpUnreachNLRI) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + AFI uint16 `json:"afi"` + SAFI uint8 `json:"safi"` + Value []AddrPrefixInterface `json:"value"` + }{ + Type: p.GetType(), + AFI: p.AFI, + SAFI: p.SAFI, + Value: p.Value, + }) +} + +func (p *PathAttributeMpUnreachNLRI) String() string { + if len(p.Value) > 0 { + return fmt.Sprintf("{MpUnreach(%s): {NLRIs: %s}}", AfiSafiToRouteFamily(p.AFI, p.SAFI), p.Value) + } + return fmt.Sprintf("{MpUnreach(%s): End-of-Rib}", AfiSafiToRouteFamily(p.AFI, p.SAFI)) +} + +func NewPathAttributeMpUnreachNLRI(nlri []AddrPrefixInterface) *PathAttributeMpUnreachNLRI { + // AFI(2) + SAFI(1) + NLRI(variable) + l := 3 + var afi uint16 + var safi uint8 + if len(nlri) > 0 { + afi = nlri[0].AFI() + safi = nlri[0].SAFI() + } + for _, n := range nlri { + l += n.Len() + } + t := BGP_ATTR_TYPE_MP_UNREACH_NLRI + return &PathAttributeMpUnreachNLRI{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + AFI: afi, + SAFI: safi, + Value: nlri, + } +} + +type ExtendedCommunityInterface interface { + Serialize() ([]byte, error) + String() string + GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) + MarshalJSON() ([]byte, error) + Flat() map[string]string +} + +type TwoOctetAsSpecificExtended struct { + SubType ExtendedCommunityAttrSubType + AS uint16 + LocalAdmin uint32 + IsTransitive bool +} + +func (e *TwoOctetAsSpecificExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + if e.IsTransitive { + buf[0] = byte(EC_TYPE_TRANSITIVE_TWO_OCTET_AS_SPECIFIC) + } else { + buf[0] = byte(EC_TYPE_NON_TRANSITIVE_TWO_OCTET_AS_SPECIFIC) + } + buf[1] = byte(e.SubType) + binary.BigEndian.PutUint16(buf[2:], e.AS) + binary.BigEndian.PutUint32(buf[4:], e.LocalAdmin) + return buf, nil +} + +func (e *TwoOctetAsSpecificExtended) String() string { + return fmt.Sprintf("%d:%d", e.AS, e.LocalAdmin) +} + +func (e *TwoOctetAsSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.String(), + }) +} + +func (e *TwoOctetAsSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + t := EC_TYPE_TRANSITIVE_TWO_OCTET_AS_SPECIFIC + if !e.IsTransitive { + t = EC_TYPE_NON_TRANSITIVE_TWO_OCTET_AS_SPECIFIC + } + return t, e.SubType +} + +func NewTwoOctetAsSpecificExtended(subtype ExtendedCommunityAttrSubType, as uint16, localAdmin uint32, isTransitive bool) *TwoOctetAsSpecificExtended { + return &TwoOctetAsSpecificExtended{ + SubType: subtype, + AS: as, + LocalAdmin: localAdmin, + IsTransitive: isTransitive, + } +} + +type IPv4AddressSpecificExtended struct { + SubType ExtendedCommunityAttrSubType + IPv4 net.IP + LocalAdmin uint16 + IsTransitive bool +} + +func (e *IPv4AddressSpecificExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + if e.IsTransitive { + buf[0] = byte(EC_TYPE_TRANSITIVE_IP4_SPECIFIC) + } else { + buf[0] = byte(EC_TYPE_NON_TRANSITIVE_IP4_SPECIFIC) + } + buf[1] = byte(e.SubType) + copy(buf[2:6], e.IPv4) + binary.BigEndian.PutUint16(buf[6:], e.LocalAdmin) + return buf, nil +} + +func (e *IPv4AddressSpecificExtended) String() string { + return fmt.Sprintf("%s:%d", e.IPv4.String(), e.LocalAdmin) +} + +func (e *IPv4AddressSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.String(), + }) +} + +func (e *IPv4AddressSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + t := EC_TYPE_TRANSITIVE_IP4_SPECIFIC + if !e.IsTransitive { + t = EC_TYPE_NON_TRANSITIVE_IP4_SPECIFIC + } + return t, e.SubType +} + +func NewIPv4AddressSpecificExtended(subtype ExtendedCommunityAttrSubType, ip string, localAdmin uint16, isTransitive bool) *IPv4AddressSpecificExtended { + ipv4 := net.ParseIP(ip) + if ipv4.To4() == nil { + return nil + } + return &IPv4AddressSpecificExtended{ + SubType: subtype, + IPv4: ipv4.To4(), + LocalAdmin: localAdmin, + IsTransitive: isTransitive, + } +} + +type IPv6AddressSpecificExtended struct { + SubType ExtendedCommunityAttrSubType + IPv6 net.IP + LocalAdmin uint16 + IsTransitive bool +} + +func (e *IPv6AddressSpecificExtended) Serialize() ([]byte, error) { + buf := make([]byte, 20) + if e.IsTransitive { + buf[0] = byte(EC_TYPE_TRANSITIVE_IP6_SPECIFIC) + } else { + buf[0] = byte(EC_TYPE_NON_TRANSITIVE_IP6_SPECIFIC) + } + buf[1] = byte(e.SubType) + copy(buf[2:18], e.IPv6) + binary.BigEndian.PutUint16(buf[18:], e.LocalAdmin) + return buf, nil +} + +func (e *IPv6AddressSpecificExtended) String() string { + return fmt.Sprintf("%s:%d", e.IPv6.String(), e.LocalAdmin) +} + +func (e *IPv6AddressSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.String(), + }) +} + +func (e *IPv6AddressSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + t := EC_TYPE_TRANSITIVE_IP6_SPECIFIC + if !e.IsTransitive { + t = EC_TYPE_NON_TRANSITIVE_IP6_SPECIFIC + } + return t, e.SubType +} + +func NewIPv6AddressSpecificExtended(subtype ExtendedCommunityAttrSubType, ip string, localAdmin uint16, isTransitive bool) *IPv6AddressSpecificExtended { + ipv6 := net.ParseIP(ip) + if ipv6.To16() == nil { + return nil + } + return &IPv6AddressSpecificExtended{ + SubType: subtype, + IPv6: ipv6.To16(), + LocalAdmin: localAdmin, + IsTransitive: isTransitive, + } +} + +type FourOctetAsSpecificExtended struct { + SubType ExtendedCommunityAttrSubType + AS uint32 + LocalAdmin uint16 + IsTransitive bool +} + +func (e *FourOctetAsSpecificExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + if e.IsTransitive { + buf[0] = byte(EC_TYPE_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC) + } else { + buf[0] = byte(EC_TYPE_NON_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC) + } + buf[1] = byte(e.SubType) + binary.BigEndian.PutUint32(buf[2:], e.AS) + binary.BigEndian.PutUint16(buf[6:], e.LocalAdmin) + return buf, nil +} + +func (e *FourOctetAsSpecificExtended) String() string { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, e.AS) + asUpper := binary.BigEndian.Uint16(buf[0:2]) + asLower := binary.BigEndian.Uint16(buf[2:]) + return fmt.Sprintf("%d.%d:%d", asUpper, asLower, e.LocalAdmin) +} + +func (e *FourOctetAsSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.String(), + }) +} + +func (e *FourOctetAsSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + t := EC_TYPE_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC + if !e.IsTransitive { + t = EC_TYPE_NON_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC + } + return t, e.SubType +} + +func NewFourOctetAsSpecificExtended(subtype ExtendedCommunityAttrSubType, as uint32, localAdmin uint16, isTransitive bool) *FourOctetAsSpecificExtended { + return &FourOctetAsSpecificExtended{ + SubType: subtype, + AS: as, + LocalAdmin: localAdmin, + IsTransitive: isTransitive, + } +} + +func ParseExtendedCommunity(subtype ExtendedCommunityAttrSubType, com string) (ExtendedCommunityInterface, error) { + if subtype == EC_SUBTYPE_ORIGIN_VALIDATION { + var state ValidationState + switch com { + case VALIDATION_STATE_VALID.String(): + state = VALIDATION_STATE_VALID + case VALIDATION_STATE_NOT_FOUND.String(): + state = VALIDATION_STATE_NOT_FOUND + case VALIDATION_STATE_INVALID.String(): + state = VALIDATION_STATE_INVALID + default: + return nil, fmt.Errorf("invalid validation state") + } + return &ValidationExtended{ + State: state, + }, nil + } + elems, err := parseRdAndRt(com) + if err != nil { + return nil, err + } + localAdmin, _ := strconv.ParseUint(elems[10], 10, 32) + ip := net.ParseIP(elems[1]) + isTransitive := true + switch { + case ip.To4() != nil: + return NewIPv4AddressSpecificExtended(subtype, elems[1], uint16(localAdmin), isTransitive), nil + case ip.To16() != nil: + return NewIPv6AddressSpecificExtended(subtype, elems[1], uint16(localAdmin), isTransitive), nil + case elems[6] == "" && elems[7] == "": + asn, _ := strconv.ParseUint(elems[8], 10, 16) + return NewTwoOctetAsSpecificExtended(subtype, uint16(asn), uint32(localAdmin), isTransitive), nil + default: + fst, _ := strconv.ParseUint(elems[7], 10, 16) + snd, _ := strconv.ParseUint(elems[8], 10, 16) + asn := fst<<16 | snd + return NewFourOctetAsSpecificExtended(subtype, uint32(asn), uint16(localAdmin), isTransitive), nil + } +} + +func ParseRouteTarget(rt string) (ExtendedCommunityInterface, error) { + return ParseExtendedCommunity(EC_SUBTYPE_ROUTE_TARGET, rt) +} + +func SerializeExtendedCommunities(comms []ExtendedCommunityInterface) ([][]byte, error) { + var bufs [][]byte + var err error + for _, c := range comms { + buf, err := c.Serialize() + if err != nil { + return nil, err + } + bufs = append(bufs, buf) + } + return bufs, err +} + +type ValidationState uint8 + +const ( + VALIDATION_STATE_VALID ValidationState = 0 + VALIDATION_STATE_NOT_FOUND ValidationState = 1 + VALIDATION_STATE_INVALID ValidationState = 2 +) + +func (s ValidationState) String() string { + switch s { + case VALIDATION_STATE_VALID: + return "valid" + case VALIDATION_STATE_NOT_FOUND: + return "not-found" + case VALIDATION_STATE_INVALID: + return "invalid" + } + return fmt.Sprintf("unknown validation state(%d)", s) +} + +type ValidationExtended struct { + State ValidationState +} + +func (e *ValidationExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + typ, subType := e.GetTypes() + buf[0] = byte(typ) + buf[1] = byte(subType) + buf[7] = byte(e.State) + return buf, nil +} + +func (e *ValidationExtended) String() string { + return e.State.String() +} + +func (e *ValidationExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_NON_TRANSITIVE_OPAQUE, EC_SUBTYPE_ORIGIN_VALIDATION +} + +func (e *ValidationExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + SubType ExtendedCommunityAttrSubType `json:"subtype"` + State ValidationState `json:"value"` + }{ + Type: t, + SubType: s, + State: e.State, + }) +} + +func NewValidationExtended(state ValidationState) *ValidationExtended { + return &ValidationExtended{ + State: state, + } +} + +type ColorExtended struct { + Color uint32 +} + +func (e *ColorExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + typ, subType := e.GetTypes() + buf[0] = byte(typ) + buf[1] = byte(subType) + binary.BigEndian.PutUint32(buf[4:8], uint32(e.Color)) + return buf, nil +} + +func (e *ColorExtended) String() string { + return fmt.Sprintf("%d", e.Color) +} + +func (e *ColorExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_TRANSITIVE_OPAQUE, EC_SUBTYPE_COLOR +} + +func (e *ColorExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + SubType ExtendedCommunityAttrSubType `json:"subtype"` + Color uint32 `json:"color"` + }{ + Type: t, + SubType: s, + Color: e.Color, + }) +} + +func NewColorExtended(color uint32) *ColorExtended { + return &ColorExtended{ + Color: color, + } +} + +type EncapExtended struct { + TunnelType TunnelType +} + +func (e *EncapExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + typ, subType := e.GetTypes() + buf[0] = byte(typ) + buf[1] = byte(subType) + binary.BigEndian.PutUint16(buf[6:8], uint16(e.TunnelType)) + return buf, nil +} + +func (e *EncapExtended) String() string { + switch e.TunnelType { + case TUNNEL_TYPE_L2TP3: + return "L2TPv3 over IP" + case TUNNEL_TYPE_GRE: + return "GRE" + case TUNNEL_TYPE_IP_IN_IP: + return "IP in IP" + case TUNNEL_TYPE_VXLAN: + return "VXLAN" + case TUNNEL_TYPE_NVGRE: + return "NVGRE" + case TUNNEL_TYPE_MPLS: + return "MPLS" + case TUNNEL_TYPE_MPLS_IN_GRE: + return "MPLS in GRE" + case TUNNEL_TYPE_VXLAN_GRE: + return "VXLAN GRE" + case TUNNEL_TYPE_MPLS_IN_UDP: + return "MPLS in UDP" + default: + return fmt.Sprintf("tunnel: %d", e.TunnelType) + } +} + +func (e *EncapExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_TRANSITIVE_OPAQUE, EC_SUBTYPE_ENCAPSULATION +} + +func (e *EncapExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + SubType ExtendedCommunityAttrSubType `json:"subtype"` + TunnelType TunnelType `json:"tunnel_type"` + }{ + Type: t, + SubType: s, + TunnelType: e.TunnelType, + }) +} + +func NewEncapExtended(tunnelType TunnelType) *EncapExtended { + return &EncapExtended{ + TunnelType: tunnelType, + } +} + +type DefaultGatewayExtended struct { +} + +func (e *DefaultGatewayExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + typ, subType := e.GetTypes() + buf[0] = byte(typ) + buf[1] = byte(subType) + return buf, nil +} + +func (e *DefaultGatewayExtended) String() string { + return "default-gateway" +} + +func (e *DefaultGatewayExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_TRANSITIVE_OPAQUE, EC_SUBTYPE_DEFAULT_GATEWAY +} + +func (e *DefaultGatewayExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + SubType ExtendedCommunityAttrSubType `json:"subtype"` + }{ + Type: t, + SubType: s, + }) +} + +func NewDefaultGatewayExtended() *DefaultGatewayExtended { + return &DefaultGatewayExtended{} +} + +type OpaqueExtended struct { + IsTransitive bool + Value []byte +} + +func (e *OpaqueExtended) Serialize() ([]byte, error) { + if len(e.Value) != 7 { + return nil, fmt.Errorf("invalid value length for opaque extended community: %d", len(e.Value)) + } + buf := make([]byte, 8) + if e.IsTransitive { + buf[0] = byte(EC_TYPE_TRANSITIVE_OPAQUE) + } else { + buf[0] = byte(EC_TYPE_NON_TRANSITIVE_OPAQUE) + } + copy(buf[1:], e.Value) + return buf, nil +} + +func (e *OpaqueExtended) String() string { + buf := make([]byte, 8) + copy(buf[1:], e.Value) + return fmt.Sprintf("%d", binary.BigEndian.Uint64(buf)) +} + +func (e *OpaqueExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + var subType ExtendedCommunityAttrSubType + if len(e.Value) > 0 { + // Use the first byte of value as the sub type + subType = ExtendedCommunityAttrSubType(e.Value[0]) + } + if e.IsTransitive { + return EC_TYPE_TRANSITIVE_OPAQUE, subType + } + return EC_TYPE_NON_TRANSITIVE_OPAQUE, subType +} + +func (e *OpaqueExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value []byte `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.Value, + }) +} + +func NewOpaqueExtended(isTransitive bool, value []byte) *OpaqueExtended { + v := make([]byte, 7) + copy(v, value) + return &OpaqueExtended{ + IsTransitive: isTransitive, + Value: v, + } +} + +func parseOpaqueExtended(data []byte) (ExtendedCommunityInterface, error) { + typ := ExtendedCommunityAttrType(data[0]) + isTransitive := false + switch typ { + case EC_TYPE_TRANSITIVE_OPAQUE: + isTransitive = true + case EC_TYPE_NON_TRANSITIVE_OPAQUE: + // isTransitive = false + default: + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("invalid opaque extended community type: %d", data[0])) + } + subType := ExtendedCommunityAttrSubType(data[1]) + + if isTransitive { + switch subType { + case EC_SUBTYPE_COLOR: + return &ColorExtended{ + Color: binary.BigEndian.Uint32(data[4:8]), + }, nil + case EC_SUBTYPE_ENCAPSULATION: + return &EncapExtended{ + TunnelType: TunnelType(binary.BigEndian.Uint16(data[6:8])), + }, nil + case EC_SUBTYPE_DEFAULT_GATEWAY: + return &DefaultGatewayExtended{}, nil + } + } else { + switch subType { + case EC_SUBTYPE_ORIGIN_VALIDATION: + return &ValidationExtended{ + State: ValidationState(data[7]), + }, nil + } + } + return NewOpaqueExtended(isTransitive, data[1:8]), nil +} + +type ESILabelExtended struct { + Label uint32 + IsSingleActive bool +} + +func (e *ESILabelExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_EVPN) + buf[1] = byte(EC_SUBTYPE_ESI_LABEL) + if e.IsSingleActive { + buf[2] = byte(1) + } + buf[3] = 0 + buf[4] = 0 + buf[5] = byte((e.Label >> 16) & 0xff) + buf[6] = byte((e.Label >> 8) & 0xff) + buf[7] = byte(e.Label & 0xff) + return buf, nil +} + +func (e *ESILabelExtended) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + buf.WriteString(fmt.Sprintf("esi-label: %d", e.Label)) + if e.IsSingleActive { + buf.WriteString(", single-active") + } + return buf.String() +} + +func (e *ESILabelExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Label uint32 `json:"label"` + IsSingleActive bool `json:"is_single_active"` + }{ + Type: t, + Subtype: s, + Label: e.Label, + IsSingleActive: e.IsSingleActive, + }) +} + +func (e *ESILabelExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_EVPN, EC_SUBTYPE_ESI_LABEL +} + +func NewESILabelExtended(label uint32, isSingleActive bool) *ESILabelExtended { + return &ESILabelExtended{ + Label: label, + IsSingleActive: isSingleActive, + } +} + +type ESImportRouteTarget struct { + ESImport net.HardwareAddr +} + +func (e *ESImportRouteTarget) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_EVPN) + buf[1] = byte(EC_SUBTYPE_ES_IMPORT) + copy(buf[2:], e.ESImport) + return buf, nil +} + +func (e *ESImportRouteTarget) String() string { + return fmt.Sprintf("es-import rt: %s", e.ESImport.String()) +} + +func (e *ESImportRouteTarget) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.ESImport.String(), + }) +} + +func (e *ESImportRouteTarget) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_EVPN, EC_SUBTYPE_ES_IMPORT +} + +func NewESImportRouteTarget(mac string) *ESImportRouteTarget { + esImport, err := net.ParseMAC(mac) + if err != nil { + return nil + } + return &ESImportRouteTarget{ + ESImport: esImport, + } +} + +type MacMobilityExtended struct { + Sequence uint32 + IsSticky bool +} + +func (e *MacMobilityExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_EVPN) + buf[1] = byte(EC_SUBTYPE_MAC_MOBILITY) + if e.IsSticky { + buf[2] = byte(1) + } + binary.BigEndian.PutUint32(buf[4:], e.Sequence) + return buf, nil +} + +func (e *MacMobilityExtended) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + buf.WriteString(fmt.Sprintf("mac-mobility: %d", e.Sequence)) + if e.IsSticky { + buf.WriteString(", sticky") + } + return buf.String() +} + +func (e *MacMobilityExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Sequence uint32 `json:"sequence"` + IsSticky bool `json:"is_sticky"` + }{ + Type: t, + Subtype: s, + Sequence: e.Sequence, + IsSticky: e.IsSticky, + }) +} + +func (e *MacMobilityExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_EVPN, EC_SUBTYPE_MAC_MOBILITY +} + +func NewMacMobilityExtended(seq uint32, isSticky bool) *MacMobilityExtended { + return &MacMobilityExtended{ + Sequence: seq, + IsSticky: isSticky, + } +} + +type RouterMacExtended struct { + Mac net.HardwareAddr +} + +func (e *RouterMacExtended) Serialize() ([]byte, error) { + buf := make([]byte, 2, 8) + buf[0] = byte(EC_TYPE_EVPN) + buf[1] = byte(EC_SUBTYPE_ROUTER_MAC) + buf = append(buf, e.Mac...) + return buf, nil +} + +func (e *RouterMacExtended) String() string { + return fmt.Sprintf("router's mac: %s", e.Mac.String()) +} + +func (e *RouterMacExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Mac string `json:"mac"` + }{ + Type: t, + Subtype: s, + Mac: e.Mac.String(), + }) +} + +func (e *RouterMacExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_EVPN, EC_SUBTYPE_ROUTER_MAC +} + +func NewRoutersMacExtended(mac string) *RouterMacExtended { + hw, err := net.ParseMAC(mac) + if err != nil { + return nil + } + return &RouterMacExtended{ + Mac: hw, + } +} + +func parseEvpnExtended(data []byte) (ExtendedCommunityInterface, error) { + if ExtendedCommunityAttrType(data[0]) != EC_TYPE_EVPN { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("ext comm type is not EC_TYPE_EVPN: %d", data[0])) + } + subType := ExtendedCommunityAttrSubType(data[1]) + switch subType { + case EC_SUBTYPE_ESI_LABEL: + var isSingleActive bool + if data[2] > 0 { + isSingleActive = true + } + label := uint32(data[5])<<16 | uint32(data[6])<<8 | uint32(data[7]) + return &ESILabelExtended{ + IsSingleActive: isSingleActive, + Label: label, + }, nil + case EC_SUBTYPE_ES_IMPORT: + return &ESImportRouteTarget{ + ESImport: net.HardwareAddr(data[2:8]), + }, nil + case EC_SUBTYPE_MAC_MOBILITY: + var isSticky bool + if data[2] > 0 { + isSticky = true + } + seq := binary.BigEndian.Uint32(data[4:8]) + return &MacMobilityExtended{ + Sequence: seq, + IsSticky: isSticky, + }, nil + case EC_SUBTYPE_ROUTER_MAC: + return &RouterMacExtended{ + Mac: net.HardwareAddr(data[2:8]), + }, nil + } + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("unknown evpn subtype: %d", subType)) +} + +type TrafficRateExtended struct { + AS uint16 + Rate float32 +} + +func (e *TrafficRateExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_TRAFFIC_RATE) + binary.BigEndian.PutUint16(buf[2:4], e.AS) + binary.BigEndian.PutUint32(buf[4:8], math.Float32bits(e.Rate)) + return buf, nil +} + +func (e *TrafficRateExtended) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + if e.Rate == 0 { + buf.WriteString("discard") + } else { + buf.WriteString(fmt.Sprintf("rate: %f", e.Rate)) + } + if e.AS != 0 { + buf.WriteString(fmt.Sprintf("(as: %d)", e.AS)) + } + return buf.String() +} + +func (e *TrafficRateExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + As uint16 `json:"as"` + Rate float32 `json:"rate"` + }{t, s, e.AS, e.Rate}) +} + +func (e *TrafficRateExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL, EC_SUBTYPE_FLOWSPEC_TRAFFIC_RATE +} + +func NewTrafficRateExtended(as uint16, rate float32) *TrafficRateExtended { + return &TrafficRateExtended{ + AS: as, + Rate: rate, + } +} + +type TrafficActionExtended struct { + Terminal bool + Sample bool +} + +func (e *TrafficActionExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_TRAFFIC_ACTION) + if e.Terminal { + buf[7] = 0x01 + } + if e.Sample { + buf[7] = buf[7] | 0x2 + } + return buf, nil +} + +func (e *TrafficActionExtended) String() string { + ss := make([]string, 0, 2) + if e.Terminal { + ss = append(ss, "terminal") + } + if e.Sample { + ss = append(ss, "sample") + } + return fmt.Sprintf("action: %s", strings.Join(ss, "-")) +} + +func (e *TrafficActionExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Terminal bool `json:"terminal"` + Sample bool `json:"sample"` + }{t, s, e.Terminal, e.Sample}) +} + +func (e *TrafficActionExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL, EC_SUBTYPE_FLOWSPEC_TRAFFIC_ACTION +} + +func NewTrafficActionExtended(terminal bool, sample bool) *TrafficActionExtended { + return &TrafficActionExtended{ + Terminal: terminal, + Sample: sample, + } +} + +type RedirectTwoOctetAsSpecificExtended struct { + TwoOctetAsSpecificExtended +} + +func (e *RedirectTwoOctetAsSpecificExtended) Serialize() ([]byte, error) { + buf, err := e.TwoOctetAsSpecificExtended.Serialize() + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_REDIRECT) + return buf, err +} + +func (e *RedirectTwoOctetAsSpecificExtended) String() string { + return fmt.Sprintf("redirect: %s", e.TwoOctetAsSpecificExtended.String()) +} + +func (e *RedirectTwoOctetAsSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{t, s, e.TwoOctetAsSpecificExtended.String()}) +} + +func (e *RedirectTwoOctetAsSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL, EC_SUBTYPE_FLOWSPEC_REDIRECT +} + +func NewRedirectTwoOctetAsSpecificExtended(as uint16, localAdmin uint32) *RedirectTwoOctetAsSpecificExtended { + return &RedirectTwoOctetAsSpecificExtended{*NewTwoOctetAsSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, as, localAdmin, false)} +} + +type RedirectIPv4AddressSpecificExtended struct { + IPv4AddressSpecificExtended +} + +func (e *RedirectIPv4AddressSpecificExtended) Serialize() ([]byte, error) { + buf, err := e.IPv4AddressSpecificExtended.Serialize() + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_REDIRECT) + return buf, err +} + +func (e *RedirectIPv4AddressSpecificExtended) String() string { + return fmt.Sprintf("redirect: %s", e.IPv4AddressSpecificExtended.String()) +} + +func (e *RedirectIPv4AddressSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{t, s, e.IPv4AddressSpecificExtended.String()}) +} + +func (e *RedirectIPv4AddressSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2, EC_SUBTYPE_FLOWSPEC_REDIRECT +} + +func NewRedirectIPv4AddressSpecificExtended(ipv4 string, localAdmin uint16) *RedirectIPv4AddressSpecificExtended { + e := NewIPv4AddressSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, ipv4, localAdmin, false) + if e == nil { + return nil + } + return &RedirectIPv4AddressSpecificExtended{*e} +} + +type RedirectIPv6AddressSpecificExtended struct { + IPv6AddressSpecificExtended +} + +func (e *RedirectIPv6AddressSpecificExtended) Serialize() ([]byte, error) { + buf, err := e.IPv6AddressSpecificExtended.Serialize() + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_REDIRECT_IP6) + return buf, err +} + +func (e *RedirectIPv6AddressSpecificExtended) String() string { + return fmt.Sprintf("redirect: %s", e.IPv6AddressSpecificExtended.String()) +} + +func (e *RedirectIPv6AddressSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{t, s, e.IPv6AddressSpecificExtended.String()}) +} + +func (e *RedirectIPv6AddressSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL, EC_SUBTYPE_FLOWSPEC_REDIRECT_IP6 +} + +func NewRedirectIPv6AddressSpecificExtended(ipv6 string, localAdmin uint16) *RedirectIPv6AddressSpecificExtended { + e := NewIPv6AddressSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, ipv6, localAdmin, false) + if e == nil { + return nil + } + return &RedirectIPv6AddressSpecificExtended{*e} +} + +type RedirectFourOctetAsSpecificExtended struct { + FourOctetAsSpecificExtended +} + +func (e *RedirectFourOctetAsSpecificExtended) Serialize() ([]byte, error) { + buf, err := e.FourOctetAsSpecificExtended.Serialize() + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_REDIRECT) + return buf, err +} + +func (e *RedirectFourOctetAsSpecificExtended) String() string { + return fmt.Sprintf("redirect: %s", e.FourOctetAsSpecificExtended.String()) +} + +func (e *RedirectFourOctetAsSpecificExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value string `json:"value"` + }{t, s, e.FourOctetAsSpecificExtended.String()}) +} + +func (e *RedirectFourOctetAsSpecificExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3, EC_SUBTYPE_FLOWSPEC_REDIRECT +} + +func NewRedirectFourOctetAsSpecificExtended(as uint32, localAdmin uint16) *RedirectFourOctetAsSpecificExtended { + return &RedirectFourOctetAsSpecificExtended{*NewFourOctetAsSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, as, localAdmin, false)} +} + +type TrafficRemarkExtended struct { + DSCP uint8 +} + +func (e *TrafficRemarkExtended) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL) + buf[1] = byte(EC_SUBTYPE_FLOWSPEC_TRAFFIC_REMARK) + buf[7] = byte(e.DSCP) + return buf, nil +} + +func (e *TrafficRemarkExtended) String() string { + return fmt.Sprintf("remark: %d", e.DSCP) +} + +func (e *TrafficRemarkExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value uint8 `json:"value"` + }{t, s, e.DSCP}) +} + +func (e *TrafficRemarkExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + return EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL, EC_SUBTYPE_FLOWSPEC_TRAFFIC_REMARK +} + +func NewTrafficRemarkExtended(dscp uint8) *TrafficRemarkExtended { + return &TrafficRemarkExtended{ + DSCP: dscp, + } +} + +func parseFlowSpecExtended(data []byte) (ExtendedCommunityInterface, error) { + typ := ExtendedCommunityAttrType(data[0]) + if typ != EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL && typ != EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2 && typ != EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3 { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("ext comm type is not EC_TYPE_FLOWSPEC: %d", data[0])) + } + subType := ExtendedCommunityAttrSubType(data[1]) + switch subType { + case EC_SUBTYPE_FLOWSPEC_TRAFFIC_RATE: + asn := binary.BigEndian.Uint16(data[2:4]) + bits := binary.BigEndian.Uint32(data[4:8]) + rate := math.Float32frombits(bits) + return NewTrafficRateExtended(asn, rate), nil + case EC_SUBTYPE_FLOWSPEC_TRAFFIC_ACTION: + terminal := data[7]&0x1 == 1 + sample := (data[7]>>1)&0x1 == 1 + return NewTrafficActionExtended(terminal, sample), nil + case EC_SUBTYPE_FLOWSPEC_REDIRECT: + // RFC7674 + switch typ { + case EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL: + as := binary.BigEndian.Uint16(data[2:4]) + localAdmin := binary.BigEndian.Uint32(data[4:8]) + return NewRedirectTwoOctetAsSpecificExtended(as, localAdmin), nil + case EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2: + ipv4 := net.IP(data[2:6]).String() + localAdmin := binary.BigEndian.Uint16(data[6:8]) + return NewRedirectIPv4AddressSpecificExtended(ipv4, localAdmin), nil + case EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3: + as := binary.BigEndian.Uint32(data[2:6]) + localAdmin := binary.BigEndian.Uint16(data[6:8]) + return NewRedirectFourOctetAsSpecificExtended(as, localAdmin), nil + } + case EC_SUBTYPE_FLOWSPEC_TRAFFIC_REMARK: + dscp := data[7] + return NewTrafficRemarkExtended(dscp), nil + case EC_SUBTYPE_FLOWSPEC_REDIRECT_IP6: + ipv6 := net.IP(data[2:18]).String() + localAdmin := binary.BigEndian.Uint16(data[18:20]) + return NewRedirectIPv6AddressSpecificExtended(ipv6, localAdmin), nil + } + return &UnknownExtended{ + Type: ExtendedCommunityAttrType(data[0]), + Value: data[1:8], + }, nil +} + +func parseIP6FlowSpecExtended(data []byte) (ExtendedCommunityInterface, error) { + typ := ExtendedCommunityAttrType(data[0]) + if typ != EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL && typ != EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2 && typ != EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3 { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("ext comm type is not EC_TYPE_FLOWSPEC: %d", data[0])) + } + subType := ExtendedCommunityAttrSubType(data[1]) + switch subType { + case EC_SUBTYPE_FLOWSPEC_REDIRECT_IP6: + // RFC7674 + switch typ { + case EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL: + ipv6 := net.IP(data[2:18]).String() + localAdmin := binary.BigEndian.Uint16(data[18:20]) + return NewRedirectIPv6AddressSpecificExtended(ipv6, localAdmin), nil + } + } + return &UnknownExtended{ + Type: ExtendedCommunityAttrType(data[0]), + Value: data[1:20], + }, nil +} + +type UnknownExtended struct { + Type ExtendedCommunityAttrType + Value []byte +} + +func (e *UnknownExtended) Serialize() ([]byte, error) { + if len(e.Value) != 7 { + return nil, fmt.Errorf("invalid value length for unknown extended community: %d", len(e.Value)) + } + buf := make([]byte, 8) + buf[0] = uint8(e.Type) + copy(buf[1:], e.Value) + return buf, nil +} + +func (e *UnknownExtended) String() string { + buf := make([]byte, 8) + copy(buf[1:], e.Value) + return fmt.Sprintf("%d", binary.BigEndian.Uint64(buf)) +} + +func (e *UnknownExtended) MarshalJSON() ([]byte, error) { + t, s := e.GetTypes() + return json.Marshal(struct { + Type ExtendedCommunityAttrType `json:"type"` + Subtype ExtendedCommunityAttrSubType `json:"subtype"` + Value []byte `json:"value"` + }{ + Type: t, + Subtype: s, + Value: e.Value, + }) +} + +func (e *UnknownExtended) GetTypes() (ExtendedCommunityAttrType, ExtendedCommunityAttrSubType) { + var subType ExtendedCommunityAttrSubType + if len(e.Value) > 0 { + // Use the first byte of value as the sub type + subType = ExtendedCommunityAttrSubType(e.Value[0]) + } + return e.Type, subType +} + +func NewUnknownExtended(typ ExtendedCommunityAttrType, value []byte) *UnknownExtended { + v := make([]byte, 7) + copy(v, value) + return &UnknownExtended{ + Type: typ, + Value: v, + } +} + +type PathAttributeExtendedCommunities struct { + PathAttribute + Value []ExtendedCommunityInterface +} + +func ParseExtended(data []byte) (ExtendedCommunityInterface, error) { + if len(data) < 8 { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all extended community bytes are available") + } + attrType := ExtendedCommunityAttrType(data[0]) + subtype := ExtendedCommunityAttrSubType(data[1]) + transitive := false + switch attrType { + case EC_TYPE_TRANSITIVE_TWO_OCTET_AS_SPECIFIC: + transitive = true + fallthrough + case EC_TYPE_NON_TRANSITIVE_TWO_OCTET_AS_SPECIFIC: + as := binary.BigEndian.Uint16(data[2:4]) + localAdmin := binary.BigEndian.Uint32(data[4:8]) + return NewTwoOctetAsSpecificExtended(subtype, as, localAdmin, transitive), nil + case EC_TYPE_TRANSITIVE_IP4_SPECIFIC: + transitive = true + fallthrough + case EC_TYPE_NON_TRANSITIVE_IP4_SPECIFIC: + ipv4 := net.IP(data[2:6]).String() + localAdmin := binary.BigEndian.Uint16(data[6:8]) + return NewIPv4AddressSpecificExtended(subtype, ipv4, localAdmin, transitive), nil + case EC_TYPE_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC: + transitive = true + fallthrough + case EC_TYPE_NON_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC: + as := binary.BigEndian.Uint32(data[2:6]) + localAdmin := binary.BigEndian.Uint16(data[6:8]) + return NewFourOctetAsSpecificExtended(subtype, as, localAdmin, transitive), nil + case EC_TYPE_TRANSITIVE_OPAQUE: + transitive = true + fallthrough + case EC_TYPE_NON_TRANSITIVE_OPAQUE: + return parseOpaqueExtended(data) + case EC_TYPE_EVPN: + return parseEvpnExtended(data) + case EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL, EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL2, EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL3: + return parseFlowSpecExtended(data) + default: + return &UnknownExtended{ + Type: ExtendedCommunityAttrType(data[0]), + Value: data[1:8], + }, nil + } +} + +func (p *PathAttributeExtendedCommunities) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length%8 != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "extendedcommunities length isn't correct") + } + for len(value) >= 8 { + e, err := ParseExtended(value) + if err != nil { + return err + } + p.Value = append(p.Value, e) + value = value[8:] + } + return nil +} + +func (p *PathAttributeExtendedCommunities) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0) + for _, p := range p.Value { + ebuf, err := p.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, ebuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeExtendedCommunities) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + for idx, v := range p.Value { + buf.WriteString("[") + buf.WriteString(v.String()) + buf.WriteString("]") + if idx < len(p.Value)-1 { + buf.WriteString(", ") + } + } + return fmt.Sprintf("{Extcomms: %s}", buf.String()) +} + +func (p *PathAttributeExtendedCommunities) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []ExtendedCommunityInterface `json:"value"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeExtendedCommunities(value []ExtendedCommunityInterface) *PathAttributeExtendedCommunities { + l := len(value) * 8 + t := BGP_ATTR_TYPE_EXTENDED_COMMUNITIES + return &PathAttributeExtendedCommunities{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: value, + } +} + +type PathAttributeAs4Path struct { + PathAttribute + Value []*As4PathParam +} + +func (p *PathAttributeAs4Path) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + isAs4, err := validateAsPathValueBytes(value) + if err != nil { + return err + } + + if !isAs4 { + return NewMessageError(eCode, eSubCode, nil, "AS4 PATH param is malformed") + } + + for len(value) > 0 { + tuple := &As4PathParam{} + tuple.DecodeFromBytes(value) + p.Value = append(p.Value, tuple) + if len(value) < tuple.Len() { + return NewMessageError(eCode, eSubCode, nil, "AS4 PATH param is malformed") + } + value = value[tuple.Len():] + } + return nil +} + +func (p *PathAttributeAs4Path) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0) + for _, v := range p.Value { + vbuf, err := v.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, vbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeAs4Path) String() string { + params := make([]string, 0, len(p.Value)) + for _, param := range p.Value { + params = append(params, param.String()) + } + return strings.Join(params, " ") +} + +func (p *PathAttributeAs4Path) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []*As4PathParam `json:"as_paths"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeAs4Path(value []*As4PathParam) *PathAttributeAs4Path { + var l int + for _, v := range value { + l += v.Len() + } + t := BGP_ATTR_TYPE_AS4_PATH + return &PathAttributeAs4Path{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: value, + } +} + +type PathAttributeAs4Aggregator struct { + PathAttribute + Value PathAttributeAggregatorParam +} + +func (p *PathAttributeAs4Aggregator) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length != 8 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "AS4 Aggregator length is incorrect") + } + p.Value.AS = binary.BigEndian.Uint32(value[0:4]) + p.Value.Address = value[4:] + return nil +} + +func (p *PathAttributeAs4Aggregator) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 8) + binary.BigEndian.PutUint32(buf[0:], p.Value.AS) + copy(buf[4:], p.Value.Address.To4()) + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeAs4Aggregator) String() string { + return fmt.Sprintf("{As4Aggregator: {AS: %d, Address: %s}}", p.Value.AS, p.Value.Address) +} + +func (p *PathAttributeAs4Aggregator) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + AS uint32 `json:"as"` + Address string `json:"address"` + }{ + Type: p.GetType(), + AS: p.Value.AS, + Address: p.Value.Address.String(), + }) +} + +func NewPathAttributeAs4Aggregator(as uint32, address string) *PathAttributeAs4Aggregator { + t := BGP_ATTR_TYPE_AS4_AGGREGATOR + return &PathAttributeAs4Aggregator{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + Length: 8, + }, + Value: PathAttributeAggregatorParam{ + AS: as, + Address: net.ParseIP(address).To4(), + }, + } +} + +type TunnelEncapSubTLVInterface interface { + Len() int + DecodeFromBytes([]byte) error + Serialize() ([]byte, error) + String() string + MarshalJSON() ([]byte, error) +} + +type TunnelEncapSubTLV struct { + Type EncapSubTLVType + Length uint16 +} + +func (t *TunnelEncapSubTLV) Len() int { + if t.Type >= 0x80 { + return 3 + int(t.Length) + } + return 2 + int(t.Length) +} + +func (t *TunnelEncapSubTLV) DecodeFromBytes(data []byte) (value []byte, err error) { + t.Type = EncapSubTLVType(data[0]) + if t.Type >= 0x80 { + t.Length = binary.BigEndian.Uint16(data[1:3]) + data = data[3:] + } else { + t.Length = uint16(data[1]) + data = data[2:] + } + if len(data) < int(t.Length) { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all TunnelEncapSubTLV bytes available") + } + return data, nil +} + +func (t *TunnelEncapSubTLV) Serialize(value []byte) (buf []byte, err error) { + t.Length = uint16(len(value)) + if t.Type >= 0x80 { + buf = append(make([]byte, 3), value...) + binary.BigEndian.PutUint16(buf[1:3], t.Length) + } else { + buf = append(make([]byte, 2), value...) + buf[1] = uint8(t.Length) + } + buf[0] = uint8(t.Type) + return buf, nil +} + +type TunnelEncapSubTLVUnknown struct { + TunnelEncapSubTLV + Value []byte +} + +func (t *TunnelEncapSubTLVUnknown) DecodeFromBytes(data []byte) error { + value, err := t.TunnelEncapSubTLV.DecodeFromBytes(data) + if err != nil { + return err + } + t.Value = value + return nil +} + +func (t *TunnelEncapSubTLVUnknown) Serialize() ([]byte, error) { + return t.TunnelEncapSubTLV.Serialize(t.Value) +} + +func (t *TunnelEncapSubTLVUnknown) String() string { + return fmt.Sprintf("{Type: %d, Value: %x}", t.Type, t.Value) +} + +func (t *TunnelEncapSubTLVUnknown) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type EncapSubTLVType `json:"type"` + Value []byte `json:"value"` + }{ + Type: t.Type, + Value: t.Value, + }) +} + +func NewTunnelEncapSubTLVUnknown(typ EncapSubTLVType, value []byte) *TunnelEncapSubTLVUnknown { + return &TunnelEncapSubTLVUnknown{ + TunnelEncapSubTLV: TunnelEncapSubTLV{ + Type: typ, + }, + Value: value, + } +} + +type TunnelEncapSubTLVEncapsulation struct { + TunnelEncapSubTLV + Key uint32 // this represent both SessionID for L2TPv3 case and GRE-key for GRE case (RFC5512 4.) + Cookie []byte +} + +func (t *TunnelEncapSubTLVEncapsulation) DecodeFromBytes(data []byte) error { + value, err := t.TunnelEncapSubTLV.DecodeFromBytes(data) + if err != nil { + return err + } + if t.Length < 4 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all TunnelEncapSubTLVEncapsulation bytes available") + } + t.Key = binary.BigEndian.Uint32(value[0:4]) + t.Cookie = value[4:] + return nil +} + +func (t *TunnelEncapSubTLVEncapsulation) Serialize() ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, t.Key) + buf = append(buf, t.Cookie...) + return t.TunnelEncapSubTLV.Serialize(buf) +} + +func (t *TunnelEncapSubTLVEncapsulation) String() string { + return fmt.Sprintf("{Key: %d, Cookie: %x}", t.Key, t.Cookie) +} + +func (t *TunnelEncapSubTLVEncapsulation) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type EncapSubTLVType `json:"type"` + Key uint32 `json:"key"` + Cookie []byte `json:"cookie"` + }{ + Type: t.Type, + Key: t.Key, + Cookie: t.Cookie, + }) +} + +func NewTunnelEncapSubTLVEncapsulation(key uint32, cookie []byte) *TunnelEncapSubTLVEncapsulation { + return &TunnelEncapSubTLVEncapsulation{ + TunnelEncapSubTLV: TunnelEncapSubTLV{ + Type: ENCAP_SUBTLV_TYPE_ENCAPSULATION, + }, + Key: key, + Cookie: cookie, + } +} + +type TunnelEncapSubTLVProtocol struct { + TunnelEncapSubTLV + Protocol uint16 +} + +func (t *TunnelEncapSubTLVProtocol) DecodeFromBytes(data []byte) error { + value, err := t.TunnelEncapSubTLV.DecodeFromBytes(data) + if err != nil { + return err + } + if t.Length < 2 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Not all TunnelEncapSubTLVProtocol bytes available") + } + t.Protocol = binary.BigEndian.Uint16(value[0:2]) + return nil +} + +func (t *TunnelEncapSubTLVProtocol) Serialize() ([]byte, error) { + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf, t.Protocol) + return t.TunnelEncapSubTLV.Serialize(buf) +} + +func (t *TunnelEncapSubTLVProtocol) String() string { + return fmt.Sprintf("{Protocol: %d}", t.Protocol) +} + +func (t *TunnelEncapSubTLVProtocol) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type EncapSubTLVType `json:"type"` + Protocol uint16 `json:"protocol"` + }{ + Type: t.Type, + Protocol: t.Protocol, + }) +} + +func NewTunnelEncapSubTLVProtocol(protocol uint16) *TunnelEncapSubTLVProtocol { + return &TunnelEncapSubTLVProtocol{ + TunnelEncapSubTLV: TunnelEncapSubTLV{ + Type: ENCAP_SUBTLV_TYPE_PROTOCOL, + }, + Protocol: protocol, + } +} + +type TunnelEncapSubTLVColor struct { + TunnelEncapSubTLV + Color uint32 +} + +func (t *TunnelEncapSubTLVColor) DecodeFromBytes(data []byte) error { + value, err := t.TunnelEncapSubTLV.DecodeFromBytes(data) + if err != nil { + return err + } + if t.Length != 8 { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "Invalid TunnelEncapSubTLVColor length") + } + t.Color = binary.BigEndian.Uint32(value[4:8]) + return nil +} + +func (t *TunnelEncapSubTLVColor) Serialize() ([]byte, error) { + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_TRANSITIVE_OPAQUE) + buf[1] = byte(EC_SUBTYPE_COLOR) + binary.BigEndian.PutUint32(buf[4:8], t.Color) + return t.TunnelEncapSubTLV.Serialize(buf) +} + +func (t *TunnelEncapSubTLVColor) String() string { + return fmt.Sprintf("{Color: %d}", t.Color) +} + +func (t *TunnelEncapSubTLVColor) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type EncapSubTLVType `json:"type"` + Color uint32 `json:"color"` + }{ + Type: t.Type, + Color: t.Color, + }) +} + +func NewTunnelEncapSubTLVColor(color uint32) *TunnelEncapSubTLVColor { + return &TunnelEncapSubTLVColor{ + TunnelEncapSubTLV: TunnelEncapSubTLV{ + Type: ENCAP_SUBTLV_TYPE_COLOR, + }, + Color: color, + } +} + +type TunnelEncapTLV struct { + Type TunnelType + Length uint16 + Value []TunnelEncapSubTLVInterface +} + +func (t *TunnelEncapTLV) Len() int { + var l int + for _, v := range t.Value { + l += v.Len() + } + return 4 + l // Type(2) + Length(2) + Value(variable) +} + +func (t *TunnelEncapTLV) DecodeFromBytes(data []byte) error { + t.Type = TunnelType(binary.BigEndian.Uint16(data[0:2])) + t.Length = binary.BigEndian.Uint16(data[2:4]) + data = data[4:] + if len(data) < int(t.Length) { + return NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, fmt.Sprintf("Not all TunnelEncapTLV bytes available")) + } + value := data[:t.Length] + for len(value) > 2 { + subType := EncapSubTLVType(value[0]) + var subTlv TunnelEncapSubTLVInterface + switch subType { + case ENCAP_SUBTLV_TYPE_ENCAPSULATION: + subTlv = &TunnelEncapSubTLVEncapsulation{} + case ENCAP_SUBTLV_TYPE_PROTOCOL: + subTlv = &TunnelEncapSubTLVProtocol{} + case ENCAP_SUBTLV_TYPE_COLOR: + subTlv = &TunnelEncapSubTLVColor{} + default: + subTlv = &TunnelEncapSubTLVUnknown{ + TunnelEncapSubTLV: TunnelEncapSubTLV{ + Type: subType, + }, + } + } + err := subTlv.DecodeFromBytes(value) + if err != nil { + return err + } + t.Value = append(t.Value, subTlv) + value = value[subTlv.Len():] + } + return nil +} + +func (p *TunnelEncapTLV) Serialize() ([]byte, error) { + buf := make([]byte, 4) + for _, t := range p.Value { + tBuf, err := t.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, tBuf...) + } + binary.BigEndian.PutUint16(buf, uint16(p.Type)) + binary.BigEndian.PutUint16(buf[2:], uint16(len(buf)-4)) + return buf, nil +} + +func (p *TunnelEncapTLV) String() string { + tlvList := make([]string, len(p.Value)) + for i, v := range p.Value { + tlvList[i] = v.String() + } + return fmt.Sprintf("{%s: %s}", p.Type, strings.Join(tlvList, ", ")) +} + +func (p *TunnelEncapTLV) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type TunnelType `json:"type"` + Value []TunnelEncapSubTLVInterface `json:"value"` + }{ + Type: p.Type, + Value: p.Value, + }) +} + +func NewTunnelEncapTLV(typ TunnelType, value []TunnelEncapSubTLVInterface) *TunnelEncapTLV { + return &TunnelEncapTLV{ + Type: typ, + Value: value, + } +} + +type PathAttributeTunnelEncap struct { + PathAttribute + Value []*TunnelEncapTLV +} + +func (p *PathAttributeTunnelEncap) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + for len(value) > 4 { + tlv := &TunnelEncapTLV{} + err = tlv.DecodeFromBytes(value) + if err != nil { + return err + } + p.Value = append(p.Value, tlv) + value = value[4+tlv.Length:] + } + return nil +} + +func (p *PathAttributeTunnelEncap) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0) + for _, t := range p.Value { + bbuf, err := t.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeTunnelEncap) String() string { + tlvList := make([]string, len(p.Value)) + for i, v := range p.Value { + tlvList[i] = v.String() + } + return fmt.Sprintf("{TunnelEncap: %s}", strings.Join(tlvList, ", ")) +} + +func (p *PathAttributeTunnelEncap) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []*TunnelEncapTLV `json:"value"` + }{ + Type: p.Type, + Value: p.Value, + }) +} + +func NewPathAttributeTunnelEncap(value []*TunnelEncapTLV) *PathAttributeTunnelEncap { + var l int + for _, v := range value { + l += v.Len() + } + t := BGP_ATTR_TYPE_TUNNEL_ENCAP + return &PathAttributeTunnelEncap{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: value, + } +} + +type PmsiTunnelIDInterface interface { + Len() int + Serialize() ([]byte, error) + String() string +} + +type DefaultPmsiTunnelID struct { + Value []byte +} + +func (i *DefaultPmsiTunnelID) Len() int { + return len(i.Value) +} + +func (i *DefaultPmsiTunnelID) Serialize() ([]byte, error) { + return i.Value, nil +} + +func (i *DefaultPmsiTunnelID) String() string { + return string(i.Value) +} + +func NewDefaultPmsiTunnelID(value []byte) *DefaultPmsiTunnelID { + return &DefaultPmsiTunnelID{ + Value: value, + } +} + +type IngressReplTunnelID struct { + Value net.IP +} + +func (i *IngressReplTunnelID) Len() int { + return len(i.Value) +} + +func (i *IngressReplTunnelID) Serialize() ([]byte, error) { + if i.Value.To4() != nil { + return []byte(i.Value.To4()), nil + } + return []byte(i.Value), nil +} + +func (i *IngressReplTunnelID) String() string { + return i.Value.String() +} + +func NewIngressReplTunnelID(value string) *IngressReplTunnelID { + ip := net.ParseIP(value) + if ip == nil { + return nil + } + return &IngressReplTunnelID{ + Value: ip, + } +} + +type PathAttributePmsiTunnel struct { + PathAttribute + IsLeafInfoRequired bool + TunnelType PmsiTunnelType + Label uint32 + TunnelID PmsiTunnelIDInterface +} + +func (p *PathAttributePmsiTunnel) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + if p.Length < 5 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "PMSI Tunnel length is incorrect") + } + + if (value[0] & 0x01) > 0 { + p.IsLeafInfoRequired = true + } + p.TunnelType = PmsiTunnelType(value[1]) + if p.Label, err = labelDecode(value[2:5]); err != nil { + return err + } + + switch p.TunnelType { + case PMSI_TUNNEL_TYPE_INGRESS_REPL: + p.TunnelID = &IngressReplTunnelID{net.IP(value[5:])} + default: + p.TunnelID = &DefaultPmsiTunnelID{value[5:]} + } + return nil +} + +func (p *PathAttributePmsiTunnel) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 2) + if p.IsLeafInfoRequired { + buf[0] = 0x01 + } + buf[1] = byte(p.TunnelType) + tbuf, err := labelSerialize(p.Label) + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + tbuf, err = p.TunnelID.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, tbuf...) + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributePmsiTunnel) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + buf.WriteString(fmt.Sprintf("{Pmsi: type: %s,", p.TunnelType)) + if p.IsLeafInfoRequired { + buf.WriteString(" leaf-info-required,") + } + buf.WriteString(fmt.Sprintf(" label: %d, tunnel-id: %s}", p.Label, p.TunnelID)) + return buf.String() +} + +func (p *PathAttributePmsiTunnel) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + IsLeafInfoRequired bool `json:"is-leaf-info-required"` + TunnelType uint8 `json:"tunnel-type"` + Label uint32 `json:"label"` + TunnelID string `json:"tunnel-id"` + }{ + Type: p.Type, + IsLeafInfoRequired: p.IsLeafInfoRequired, + TunnelType: uint8(p.TunnelType), + Label: p.Label, + TunnelID: p.TunnelID.String(), + }) +} + +func NewPathAttributePmsiTunnel(typ PmsiTunnelType, isLeafInfoRequired bool, label uint32, id PmsiTunnelIDInterface) *PathAttributePmsiTunnel { + // Flags(1) + TunnelType(1) + Label(3) + TunnelID(variable) + l := 5 + id.Len() + t := BGP_ATTR_TYPE_PMSI_TUNNEL + return &PathAttributePmsiTunnel{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + IsLeafInfoRequired: isLeafInfoRequired, + TunnelType: typ, + Label: label, + TunnelID: id, + } +} + +func ParsePmsiTunnel(args []string) (*PathAttributePmsiTunnel, error) { + // Format: + // "<type>" ["leaf-info-required"] "<label>" "<tunnel-id>" + if len(args) < 3 { + return nil, fmt.Errorf("invalid pmsi tunnel arguments: %s", args) + } + + pmsi := NewPathAttributePmsiTunnel(0, false, 0, nil) + + switch args[0] { + case "ingress-repl": + pmsi.TunnelType = PMSI_TUNNEL_TYPE_INGRESS_REPL + default: + typ, err := strconv.ParseUint(args[0], 10, 8) + if err != nil { + return nil, fmt.Errorf("invalid pmsi tunnel type: %s", args[0]) + } + pmsi.TunnelType = PmsiTunnelType(typ) + } + + indx := 1 + if args[indx] == "leaf-info-required" { + pmsi.IsLeafInfoRequired = true + indx++ + } + + label, err := strconv.ParseUint(args[indx], 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid pmsi tunnel label: %s", args[indx]) + } + pmsi.Label = uint32(label) + indx++ + + switch pmsi.TunnelType { + case PMSI_TUNNEL_TYPE_INGRESS_REPL: + ip := net.ParseIP(args[indx]) + if ip == nil { + return nil, fmt.Errorf("invalid pmsi tunnel identifier: %s", args[indx]) + } + pmsi.TunnelID = &IngressReplTunnelID{Value: ip} + default: + pmsi.TunnelID = &DefaultPmsiTunnelID{Value: []byte(args[indx])} + } + + return pmsi, nil +} + +type PathAttributeIP6ExtendedCommunities struct { + PathAttribute + Value []ExtendedCommunityInterface +} + +func ParseIP6Extended(data []byte) (ExtendedCommunityInterface, error) { + if len(data) < 8 { + return nil, NewMessageError(BGP_ERROR_UPDATE_MESSAGE_ERROR, BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST, nil, "not all extended community bytes are available") + } + attrType := ExtendedCommunityAttrType(data[0]) + subtype := ExtendedCommunityAttrSubType(data[1]) + transitive := false + switch attrType { + case EC_TYPE_TRANSITIVE_IP6_SPECIFIC: + transitive = true + fallthrough + case EC_TYPE_NON_TRANSITIVE_IP6_SPECIFIC: + ipv6 := net.IP(data[2:18]).String() + localAdmin := binary.BigEndian.Uint16(data[18:20]) + return NewIPv6AddressSpecificExtended(subtype, ipv6, localAdmin, transitive), nil + case EC_TYPE_GENERIC_TRANSITIVE_EXPERIMENTAL: + return parseIP6FlowSpecExtended(data) + default: + return &UnknownExtended{ + Type: ExtendedCommunityAttrType(data[0]), + Value: data[1:8], + }, nil + } +} + +func (p *PathAttributeIP6ExtendedCommunities) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data) + if err != nil { + return err + } + if p.Length%20 != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "extendedcommunities length isn't correct") + } + for len(value) >= 20 { + e, err := ParseIP6Extended(value) + if err != nil { + return err + } + p.Value = append(p.Value, e) + value = value[20:] + } + return nil +} + +func (p *PathAttributeIP6ExtendedCommunities) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0) + for _, p := range p.Value { + ebuf, err := p.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, ebuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeIP6ExtendedCommunities) String() string { + var buf []string + for _, v := range p.Value { + buf = append(buf, fmt.Sprintf("[%s]", v.String())) + } + return fmt.Sprintf("{Extcomms: %s}", strings.Join(buf, ",")) +} + +func (p *PathAttributeIP6ExtendedCommunities) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []ExtendedCommunityInterface `json:"value"` + }{ + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeIP6ExtendedCommunities(value []ExtendedCommunityInterface) *PathAttributeIP6ExtendedCommunities { + l := len(value) * 20 + t := BGP_ATTR_TYPE_IP6_EXTENDED_COMMUNITIES + return &PathAttributeIP6ExtendedCommunities{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Value: value, + } +} + +type AigpTLVType uint8 + +const ( + AIGP_TLV_UNKNOWN AigpTLVType = iota + AIGP_TLV_IGP_METRIC +) + +type AigpTLVInterface interface { + Serialize() ([]byte, error) + String() string + MarshalJSON() ([]byte, error) + Type() AigpTLVType + Len() int +} + +type AigpTLVDefault struct { + typ AigpTLVType + Value []byte +} + +func (t *AigpTLVDefault) Serialize() ([]byte, error) { + buf := make([]byte, 3+len(t.Value)) + buf[0] = uint8(t.Type()) + binary.BigEndian.PutUint16(buf[1:], uint16(3+len(t.Value))) + copy(buf[3:], t.Value) + return buf, nil +} + +func (t *AigpTLVDefault) String() string { + return fmt.Sprintf("{Type: %d, Value: %v}", t.Type(), t.Value) +} + +func (t *AigpTLVDefault) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type AigpTLVType `json:"type"` + Value []byte `json:"value"` + }{ + Type: t.Type(), + Value: t.Value, + }) +} + +func (t *AigpTLVDefault) Type() AigpTLVType { + return t.typ +} + +func (t *AigpTLVDefault) Len() int { + return 3 + len(t.Value) // Type(1) + Length(2) + Value(variable) +} + +func NewAigpTLVDefault(typ AigpTLVType, value []byte) *AigpTLVDefault { + return &AigpTLVDefault{ + typ: typ, + Value: value, + } +} + +type AigpTLVIgpMetric struct { + Metric uint64 +} + +func (t *AigpTLVIgpMetric) Serialize() ([]byte, error) { + buf := make([]byte, 11) + buf[0] = uint8(AIGP_TLV_IGP_METRIC) + binary.BigEndian.PutUint16(buf[1:], uint16(11)) + binary.BigEndian.PutUint64(buf[3:], t.Metric) + return buf, nil +} + +func (t *AigpTLVIgpMetric) String() string { + return fmt.Sprintf("{Metric: %d}", t.Metric) +} + +func (t *AigpTLVIgpMetric) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type AigpTLVType `json:"type"` + Metric uint64 `json:"metric"` + }{ + Type: AIGP_TLV_IGP_METRIC, + Metric: t.Metric, + }) +} + +func NewAigpTLVIgpMetric(metric uint64) *AigpTLVIgpMetric { + return &AigpTLVIgpMetric{ + Metric: metric, + } +} + +func (t *AigpTLVIgpMetric) Type() AigpTLVType { + return AIGP_TLV_IGP_METRIC +} + +func (t *AigpTLVIgpMetric) Len() int { + return 11 +} + +type PathAttributeAigp struct { + PathAttribute + Values []AigpTLVInterface +} + +func (p *PathAttributeAigp) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data, options...) + if err != nil { + return err + } + for len(value) > 3 { + typ := value[0] + length := binary.BigEndian.Uint16(value[1:3]) + if len(value) < int(length) { + break + } + v := value[3:length] + switch AigpTLVType(typ) { + case AIGP_TLV_IGP_METRIC: + if len(v) < 8 { + break + } + metric := binary.BigEndian.Uint64(v) + p.Values = append(p.Values, NewAigpTLVIgpMetric(metric)) + default: + p.Values = append(p.Values, NewAigpTLVDefault(AigpTLVType(typ), v)) + } + value = value[length:] + } + if len(value) != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + return NewMessageError(eCode, eSubCode, nil, "Aigp length is incorrect") + } + return nil +} + +func (p *PathAttributeAigp) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0) + for _, t := range p.Values { + bbuf, err := t.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeAigp) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + buf.WriteString("{Aigp: [") + for _, v := range p.Values { + buf.WriteString(v.String()) + } + buf.WriteString("]}") + return buf.String() +} + +func (p *PathAttributeAigp) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []AigpTLVInterface `json:"value"` + }{ + Type: p.GetType(), + Value: p.Values, + }) +} + +func NewPathAttributeAigp(values []AigpTLVInterface) *PathAttributeAigp { + var l int + for _, v := range values { + l += v.Len() + } + t := BGP_ATTR_TYPE_AIGP + return &PathAttributeAigp{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Values: values, + } +} + +type LargeCommunity struct { + ASN uint32 + LocalData1 uint32 + LocalData2 uint32 +} + +func (c *LargeCommunity) Serialize() ([]byte, error) { + buf := make([]byte, 12) + binary.BigEndian.PutUint32(buf, c.ASN) + binary.BigEndian.PutUint32(buf[4:], c.LocalData1) + binary.BigEndian.PutUint32(buf[8:], c.LocalData2) + return buf, nil +} + +func (c *LargeCommunity) String() string { + return fmt.Sprintf("%d:%d:%d", c.ASN, c.LocalData1, c.LocalData2) +} + +func NewLargeCommunity(asn, data1, data2 uint32) *LargeCommunity { + return &LargeCommunity{ + ASN: asn, + LocalData1: data1, + LocalData2: data2, + } +} + +func ParseLargeCommunity(value string) (*LargeCommunity, error) { + elems := strings.Split(value, ":") + if len(elems) != 3 { + return nil, fmt.Errorf("invalid large community format") + } + v := make([]uint32, 0, 3) + for _, elem := range elems { + e, err := strconv.ParseUint(elem, 10, 32) + if err != nil { + return nil, fmt.Errorf("invalid large community format") + } + v = append(v, uint32(e)) + } + return NewLargeCommunity(v[0], v[1], v[2]), nil +} + +type PathAttributeLargeCommunities struct { + PathAttribute + Values []*LargeCommunity +} + +func (p *PathAttributeLargeCommunities) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data) + if err != nil { + return err + } + if p.Length%12 != 0 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return NewMessageError(eCode, eSubCode, nil, "large communities length isn't correct") + } + p.Values = make([]*LargeCommunity, 0, p.Length/12) + for len(value) >= 12 { + asn := binary.BigEndian.Uint32(value[:4]) + data1 := binary.BigEndian.Uint32(value[4:8]) + data2 := binary.BigEndian.Uint32(value[8:12]) + p.Values = append(p.Values, NewLargeCommunity(asn, data1, data2)) + value = value[12:] + } + return nil +} + +func (p *PathAttributeLargeCommunities) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 0, len(p.Values)*12) + for _, t := range p.Values { + bbuf, err := t.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + } + return p.PathAttribute.Serialize(buf, options...) +} + +func (p *PathAttributeLargeCommunities) String() string { + buf := bytes.NewBuffer(make([]byte, 0, 32)) + buf.WriteString("{LargeCommunity: [ ") + ss := []string{} + for _, v := range p.Values { + ss = append(ss, v.String()) + } + buf.WriteString(strings.Join(ss, ", ")) + buf.WriteString("]}") + return buf.String() +} + +func (p *PathAttributeLargeCommunities) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Type BGPAttrType `json:"type"` + Value []*LargeCommunity `json:"value"` + }{ + Type: p.GetType(), + Value: p.Values, + }) +} + +func NewPathAttributeLargeCommunities(values []*LargeCommunity) *PathAttributeLargeCommunities { + l := len(values) * 12 + t := BGP_ATTR_TYPE_LARGE_COMMUNITY + return &PathAttributeLargeCommunities{ + PathAttribute: PathAttribute{ + Flags: getPathAttrFlags(t, l), + Type: t, + Length: uint16(l), + }, + Values: values, + } +} + +type PathAttributeUnknown struct { + PathAttribute + Value []byte +} + +func (p *PathAttributeUnknown) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + value, err := p.PathAttribute.DecodeFromBytes(data) + if err != nil { + return err + } + p.Value = value + return nil +} + +func (p *PathAttributeUnknown) Serialize(options ...*MarshallingOption) ([]byte, error) { + return p.PathAttribute.Serialize(p.Value, options...) +} + +func (p *PathAttributeUnknown) String() string { + return fmt.Sprintf("{Flags: %s, Type: %s, Value: %s}", p.Flags, p.Type, p.Value) +} + +func (p *PathAttributeUnknown) MarshalJSON() ([]byte, error) { + return json.Marshal(struct { + Flags BGPAttrFlag `json:"flags"` + Type BGPAttrType `json:"type"` + Value []byte `json:"value"` + }{ + Flags: p.GetFlags(), + Type: p.GetType(), + Value: p.Value, + }) +} + +func NewPathAttributeUnknown(flags BGPAttrFlag, typ BGPAttrType, value []byte) *PathAttributeUnknown { + l := len(value) + if l > 255 { + flags |= BGP_ATTR_FLAG_EXTENDED_LENGTH + } + return &PathAttributeUnknown{ + PathAttribute: PathAttribute{ + Flags: flags, + Type: typ, + Length: uint16(l), + }, + Value: value, + } +} + +func GetPathAttribute(data []byte) (PathAttributeInterface, error) { + if len(data) < 2 { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR) + return nil, NewMessageError(eCode, eSubCode, data, "attribute type length is short") + } + switch BGPAttrType(data[1]) { + case BGP_ATTR_TYPE_ORIGIN: + return &PathAttributeOrigin{}, nil + case BGP_ATTR_TYPE_AS_PATH: + return &PathAttributeAsPath{}, nil + case BGP_ATTR_TYPE_NEXT_HOP: + return &PathAttributeNextHop{}, nil + case BGP_ATTR_TYPE_MULTI_EXIT_DISC: + return &PathAttributeMultiExitDisc{}, nil + case BGP_ATTR_TYPE_LOCAL_PREF: + return &PathAttributeLocalPref{}, nil + case BGP_ATTR_TYPE_ATOMIC_AGGREGATE: + return &PathAttributeAtomicAggregate{}, nil + case BGP_ATTR_TYPE_AGGREGATOR: + return &PathAttributeAggregator{}, nil + case BGP_ATTR_TYPE_COMMUNITIES: + return &PathAttributeCommunities{}, nil + case BGP_ATTR_TYPE_ORIGINATOR_ID: + return &PathAttributeOriginatorId{}, nil + case BGP_ATTR_TYPE_CLUSTER_LIST: + return &PathAttributeClusterList{}, nil + case BGP_ATTR_TYPE_MP_REACH_NLRI: + return &PathAttributeMpReachNLRI{}, nil + case BGP_ATTR_TYPE_MP_UNREACH_NLRI: + return &PathAttributeMpUnreachNLRI{}, nil + case BGP_ATTR_TYPE_EXTENDED_COMMUNITIES: + return &PathAttributeExtendedCommunities{}, nil + case BGP_ATTR_TYPE_AS4_PATH: + return &PathAttributeAs4Path{}, nil + case BGP_ATTR_TYPE_AS4_AGGREGATOR: + return &PathAttributeAs4Aggregator{}, nil + case BGP_ATTR_TYPE_TUNNEL_ENCAP: + return &PathAttributeTunnelEncap{}, nil + case BGP_ATTR_TYPE_PMSI_TUNNEL: + return &PathAttributePmsiTunnel{}, nil + case BGP_ATTR_TYPE_IP6_EXTENDED_COMMUNITIES: + return &PathAttributeIP6ExtendedCommunities{}, nil + case BGP_ATTR_TYPE_AIGP: + return &PathAttributeAigp{}, nil + case BGP_ATTR_TYPE_LARGE_COMMUNITY: + return &PathAttributeLargeCommunities{}, nil + } + return &PathAttributeUnknown{}, nil +} + +type BGPUpdate struct { + WithdrawnRoutesLen uint16 + WithdrawnRoutes []*IPAddrPrefix + TotalPathAttributeLen uint16 + PathAttributes []PathAttributeInterface + NLRI []*IPAddrPrefix +} + +func (msg *BGPUpdate) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + var strongestError error + + // cache error codes + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + + // check withdrawn route length + if len(data) < 2 { + return NewMessageError(eCode, eSubCode, nil, "message length isn't enough for withdrawn route length") + } + + msg.WithdrawnRoutesLen = binary.BigEndian.Uint16(data[0:2]) + data = data[2:] + + // check withdrawn route + if len(data) < int(msg.WithdrawnRoutesLen) { + return NewMessageError(eCode, eSubCode, nil, "withdrawn route length exceeds message length") + } + + addpathLen := 0 + if IsAddPathEnabled(true, RF_IPv4_UC, options) { + addpathLen = 4 + } + + msg.WithdrawnRoutes = make([]*IPAddrPrefix, 0, msg.WithdrawnRoutesLen) + for routelen := msg.WithdrawnRoutesLen; routelen > 0; { + w := &IPAddrPrefix{} + err := w.DecodeFromBytes(data, options...) + if err != nil { + return err + } + routelen -= uint16(w.Len(options...) + addpathLen) + if len(data) < w.Len(options...)+addpathLen { + return NewMessageError(eCode, eSubCode, nil, "Withdrawn route length is short") + } + data = data[w.Len(options...)+addpathLen:] + msg.WithdrawnRoutes = append(msg.WithdrawnRoutes, w) + } + + // check path total attribute length + if len(data) < 2 { + return NewMessageError(eCode, eSubCode, nil, "message length isn't enough for path total attribute length") + } + + msg.TotalPathAttributeLen = binary.BigEndian.Uint16(data[0:2]) + data = data[2:] + + // check path attribute + if len(data) < int(msg.TotalPathAttributeLen) { + return NewMessageError(eCode, eSubCode, nil, "path total attribute length exceeds message length") + } + + msg.PathAttributes = []PathAttributeInterface{} + for pathlen := msg.TotalPathAttributeLen; pathlen > 0; { + var e error + if pathlen < 3 { + e = NewMessageErrorWithErrorHandling( + eCode, BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR, data, ERROR_HANDLING_TREAT_AS_WITHDRAW, nil, "insufficient data to decode") + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + data = data[pathlen:] + break + } + p, err := GetPathAttribute(data) + if err != nil { + return err + } + + err = p.DecodeFromBytes(data, options...) + if err != nil { + e = err.(*MessageError) + if e.(*MessageError).SubTypeCode == BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR { + e.(*MessageError).ErrorHandling = ERROR_HANDLING_TREAT_AS_WITHDRAW + } else { + e.(*MessageError).ErrorHandling = getErrorHandlingFromPathAttribute(p.GetType()) + e.(*MessageError).ErrorAttribute = &p + } + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + } + pathlen -= uint16(p.Len(options...)) + if len(data) < p.Len(options...) { + e = NewMessageErrorWithErrorHandling( + eCode, BGP_ERROR_SUB_ATTRIBUTE_LENGTH_ERROR, data, ERROR_HANDLING_TREAT_AS_WITHDRAW, nil, "attribute length is short") + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + } + data = data[p.Len(options...):] + if e == nil || e.(*MessageError).ErrorHandling != ERROR_HANDLING_ATTRIBUTE_DISCARD { + msg.PathAttributes = append(msg.PathAttributes, p) + } + } + + msg.NLRI = make([]*IPAddrPrefix, 0) + for restlen := len(data); restlen > 0; { + n := &IPAddrPrefix{} + err := n.DecodeFromBytes(data, options...) + if err != nil { + return err + } + restlen -= n.Len(options...) + addpathLen + if len(data) < n.Len(options...)+addpathLen { + return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, nil, "NLRI length is short") + } + if n.Len(options...) > 32 { + return NewMessageError(eCode, BGP_ERROR_SUB_INVALID_NETWORK_FIELD, nil, "NLRI length is too long") + } + data = data[n.Len(options...)+addpathLen:] + msg.NLRI = append(msg.NLRI, n) + } + + return strongestError +} + +func (msg *BGPUpdate) Serialize(options ...*MarshallingOption) ([]byte, error) { + wbuf := make([]byte, 2) + for _, w := range msg.WithdrawnRoutes { + onewbuf, err := w.Serialize(options...) + if err != nil { + return nil, err + } + wbuf = append(wbuf, onewbuf...) + } + msg.WithdrawnRoutesLen = uint16(len(wbuf) - 2) + binary.BigEndian.PutUint16(wbuf, msg.WithdrawnRoutesLen) + + pbuf := make([]byte, 2) + for _, p := range msg.PathAttributes { + onepbuf, err := p.Serialize(options...) + if err != nil { + return nil, err + } + pbuf = append(pbuf, onepbuf...) + } + msg.TotalPathAttributeLen = uint16(len(pbuf) - 2) + binary.BigEndian.PutUint16(pbuf, msg.TotalPathAttributeLen) + + buf := append(wbuf, pbuf...) + for _, n := range msg.NLRI { + nbuf, err := n.Serialize(options...) + if err != nil { + return nil, err + } + buf = append(buf, nbuf...) + } + return buf, nil +} + +func (msg *BGPUpdate) IsEndOfRib() (bool, RouteFamily) { + if len(msg.WithdrawnRoutes) == 0 && len(msg.NLRI) == 0 { + if len(msg.PathAttributes) == 0 { + return true, RF_IPv4_UC + } else if len(msg.PathAttributes) == 1 && msg.PathAttributes[0].GetType() == BGP_ATTR_TYPE_MP_UNREACH_NLRI { + unreach := msg.PathAttributes[0].(*PathAttributeMpUnreachNLRI) + if len(unreach.Value) == 0 { + return true, AfiSafiToRouteFamily(unreach.AFI, unreach.SAFI) + } + } + } + return false, RouteFamily(0) +} + +func TreatAsWithdraw(msg *BGPUpdate) *BGPUpdate { + withdraw := &BGPUpdate{ + WithdrawnRoutesLen: 0, + WithdrawnRoutes: []*IPAddrPrefix{}, + TotalPathAttributeLen: 0, + PathAttributes: make([]PathAttributeInterface, 0, len(msg.PathAttributes)), + NLRI: []*IPAddrPrefix{}, + } + withdraw.WithdrawnRoutes = append(msg.WithdrawnRoutes, msg.NLRI...) + var unreach []AddrPrefixInterface + + for _, p := range msg.PathAttributes { + switch nlri := p.(type) { + case *PathAttributeMpReachNLRI: + unreach = append(unreach, nlri.Value...) + case *PathAttributeMpUnreachNLRI: + unreach = append(unreach, nlri.Value...) + } + } + if len(unreach) != 0 { + withdraw.PathAttributes = append(withdraw.PathAttributes, NewPathAttributeMpUnreachNLRI(unreach)) + } + return withdraw +} + +func NewBGPUpdateMessage(withdrawnRoutes []*IPAddrPrefix, pathattrs []PathAttributeInterface, nlri []*IPAddrPrefix) *BGPMessage { + return &BGPMessage{ + Header: BGPHeader{Type: BGP_MSG_UPDATE}, + Body: &BGPUpdate{0, withdrawnRoutes, 0, pathattrs, nlri}, + } +} + +func NewEndOfRib(family RouteFamily) *BGPMessage { + if family == RF_IPv4_UC { + return NewBGPUpdateMessage(nil, nil, nil) + } else { + afi, safi := RouteFamilyToAfiSafi(family) + t := BGP_ATTR_TYPE_MP_UNREACH_NLRI + unreach := &PathAttributeMpUnreachNLRI{ + PathAttribute: PathAttribute{ + Flags: PathAttrFlags[t], + Type: t, + }, + AFI: afi, + SAFI: safi, + } + return NewBGPUpdateMessage(nil, []PathAttributeInterface{unreach}, nil) + } +} + +type BGPNotification struct { + ErrorCode uint8 + ErrorSubcode uint8 + Data []byte +} + +func (msg *BGPNotification) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if len(data) < 2 { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "Not all Notificaiton bytes available") + } + msg.ErrorCode = data[0] + msg.ErrorSubcode = data[1] + if len(data) > 2 { + msg.Data = data[2:] + } + return nil +} + +func (msg *BGPNotification) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 2) + buf[0] = msg.ErrorCode + buf[1] = msg.ErrorSubcode + buf = append(buf, msg.Data...) + return buf, nil +} + +func NewBGPNotificationMessage(errcode uint8, errsubcode uint8, data []byte) *BGPMessage { + return &BGPMessage{ + Header: BGPHeader{Type: BGP_MSG_NOTIFICATION}, + Body: &BGPNotification{errcode, errsubcode, data}, + } +} + +type BGPKeepAlive struct { +} + +func (msg *BGPKeepAlive) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + return nil +} + +func (msg *BGPKeepAlive) Serialize(options ...*MarshallingOption) ([]byte, error) { + return nil, nil +} + +func NewBGPKeepAliveMessage() *BGPMessage { + return &BGPMessage{ + Header: BGPHeader{Len: 19, Type: BGP_MSG_KEEPALIVE}, + Body: &BGPKeepAlive{}, + } +} + +type BGPRouteRefresh struct { + AFI uint16 + Demarcation uint8 + SAFI uint8 +} + +func (msg *BGPRouteRefresh) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + if len(data) < 4 { + return NewMessageError(BGP_ERROR_ROUTE_REFRESH_MESSAGE_ERROR, BGP_ERROR_SUB_INVALID_MESSAGE_LENGTH, nil, "Not all RouteRefresh bytes available") + } + msg.AFI = binary.BigEndian.Uint16(data[0:2]) + msg.Demarcation = data[2] + msg.SAFI = data[3] + return nil +} + +func (msg *BGPRouteRefresh) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], msg.AFI) + buf[2] = msg.Demarcation + buf[3] = msg.SAFI + return buf, nil +} + +func NewBGPRouteRefreshMessage(afi uint16, demarcation uint8, safi uint8) *BGPMessage { + return &BGPMessage{ + Header: BGPHeader{Type: BGP_MSG_ROUTE_REFRESH}, + Body: &BGPRouteRefresh{afi, demarcation, safi}, + } +} + +type BGPBody interface { + DecodeFromBytes([]byte, ...*MarshallingOption) error + Serialize(...*MarshallingOption) ([]byte, error) +} + +const ( + BGP_HEADER_LENGTH = 19 + BGP_MAX_MESSAGE_LENGTH = 4096 +) + +type BGPHeader struct { + Marker []byte + Len uint16 + Type uint8 +} + +func (msg *BGPHeader) DecodeFromBytes(data []byte, options ...*MarshallingOption) error { + // minimum BGP message length + if uint16(len(data)) < BGP_HEADER_LENGTH { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "not all BGP message header") + } + + msg.Len = binary.BigEndian.Uint16(data[16:18]) + if int(msg.Len) < BGP_HEADER_LENGTH { + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "unknown message type") + } + + msg.Type = data[18] + return nil +} + +func (msg *BGPHeader) Serialize(options ...*MarshallingOption) ([]byte, error) { + buf := make([]byte, 19) + for i := range buf[:16] { + buf[i] = 0xff + } + binary.BigEndian.PutUint16(buf[16:18], msg.Len) + buf[18] = msg.Type + return buf, nil +} + +type BGPMessage struct { + Header BGPHeader + Body BGPBody +} + +func parseBody(h *BGPHeader, data []byte, options ...*MarshallingOption) (*BGPMessage, error) { + if len(data) < int(h.Len)-BGP_HEADER_LENGTH { + return nil, NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "Not all BGP message bytes available") + } + msg := &BGPMessage{Header: *h} + + switch msg.Header.Type { + case BGP_MSG_OPEN: + msg.Body = &BGPOpen{} + case BGP_MSG_UPDATE: + msg.Body = &BGPUpdate{} + case BGP_MSG_NOTIFICATION: + msg.Body = &BGPNotification{} + case BGP_MSG_KEEPALIVE: + msg.Body = &BGPKeepAlive{} + case BGP_MSG_ROUTE_REFRESH: + msg.Body = &BGPRouteRefresh{} + default: + return nil, NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_TYPE, nil, "unknown message type") + } + err := msg.Body.DecodeFromBytes(data, options...) + return msg, err +} + +func ParseBGPMessage(data []byte, options ...*MarshallingOption) (*BGPMessage, error) { + h := &BGPHeader{} + err := h.DecodeFromBytes(data, options...) + if err != nil { + return nil, err + } + + if int(h.Len) > len(data) { + return nil, NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, nil, "unknown message type") + } + + return parseBody(h, data[19:h.Len], options...) +} + +func ParseBGPBody(h *BGPHeader, data []byte, options ...*MarshallingOption) (*BGPMessage, error) { + return parseBody(h, data, options...) +} + +func (msg *BGPMessage) Serialize(options ...*MarshallingOption) ([]byte, error) { + b, err := msg.Body.Serialize(options...) + if err != nil { + return nil, err + } + if msg.Header.Len == 0 { + if 19+len(b) > BGP_MAX_MESSAGE_LENGTH { + return nil, NewMessageError(0, 0, nil, fmt.Sprintf("too long message length %d", 19+len(b))) + } + msg.Header.Len = 19 + uint16(len(b)) + } + h, err := msg.Header.Serialize(options...) + if err != nil { + return nil, err + } + return append(h, b...), nil +} + +type ErrorHandling int + +const ( + ERROR_HANDLING_NONE ErrorHandling = iota + ERROR_HANDLING_ATTRIBUTE_DISCARD + ERROR_HANDLING_TREAT_AS_WITHDRAW + ERROR_HANDLING_AFISAFI_DISABLE + ERROR_HANDLING_SESSION_RESET +) + +func getErrorHandlingFromPathAttribute(t BGPAttrType) ErrorHandling { + switch t { + case BGP_ATTR_TYPE_ORIGIN: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_AS_PATH: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_AS4_PATH: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_NEXT_HOP: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_MULTI_EXIT_DISC: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_LOCAL_PREF: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_ATOMIC_AGGREGATE: + return ERROR_HANDLING_ATTRIBUTE_DISCARD + case BGP_ATTR_TYPE_AGGREGATOR: + return ERROR_HANDLING_ATTRIBUTE_DISCARD + case BGP_ATTR_TYPE_AS4_AGGREGATOR: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_COMMUNITIES: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_ORIGINATOR_ID: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_CLUSTER_LIST: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_MP_REACH_NLRI: + return ERROR_HANDLING_AFISAFI_DISABLE + case BGP_ATTR_TYPE_MP_UNREACH_NLRI: + return ERROR_HANDLING_AFISAFI_DISABLE + case BGP_ATTR_TYPE_EXTENDED_COMMUNITIES: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_IP6_EXTENDED_COMMUNITIES: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_PMSI_TUNNEL: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_LARGE_COMMUNITY: + return ERROR_HANDLING_TREAT_AS_WITHDRAW + case BGP_ATTR_TYPE_TUNNEL_ENCAP: + return ERROR_HANDLING_ATTRIBUTE_DISCARD + case BGP_ATTR_TYPE_AIGP: + return ERROR_HANDLING_ATTRIBUTE_DISCARD + default: + return ERROR_HANDLING_ATTRIBUTE_DISCARD + } +} + +type MessageError struct { + TypeCode uint8 + SubTypeCode uint8 + Data []byte + Message string + ErrorHandling ErrorHandling + ErrorAttribute *PathAttributeInterface +} + +func NewMessageError(typeCode, subTypeCode uint8, data []byte, msg string) error { + return &MessageError{ + TypeCode: typeCode, + SubTypeCode: subTypeCode, + Data: data, + ErrorHandling: ERROR_HANDLING_SESSION_RESET, + ErrorAttribute: nil, + Message: msg, + } +} + +func NewMessageErrorWithErrorHandling(typeCode, subTypeCode uint8, data []byte, errorHandling ErrorHandling, errorAttribute *PathAttributeInterface, msg string) error { + return &MessageError{ + TypeCode: typeCode, + SubTypeCode: subTypeCode, + Data: data, + ErrorHandling: errorHandling, + ErrorAttribute: errorAttribute, + Message: msg, + } +} + +func (e *MessageError) Error() string { + return e.Message +} + +func (e *MessageError) Stronger(err error) bool { + if err == nil { + return true + } + if msgErr, ok := err.(*MessageError); ok { + return e.ErrorHandling > msgErr.ErrorHandling + } + return false +} + +func (e *TwoOctetAsSpecificExtended) Flat() map[string]string { + if e.SubType == EC_SUBTYPE_ROUTE_TARGET { + return map[string]string{"routeTarget": e.String()} + } + return map[string]string{} +} + +func (e *ColorExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *EncapExtended) Flat() map[string]string { + return map[string]string{"encaspulation": e.TunnelType.String()} +} + +func (e *DefaultGatewayExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *ValidationExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *OpaqueExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *IPv4AddressSpecificExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *IPv6AddressSpecificExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *FourOctetAsSpecificExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *ESILabelExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *ESImportRouteTarget) Flat() map[string]string { + return map[string]string{} +} + +func (e *MacMobilityExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *RouterMacExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *TrafficRateExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *TrafficRemarkExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *RedirectIPv4AddressSpecificExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *RedirectIPv6AddressSpecificExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *RedirectFourOctetAsSpecificExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *UnknownExtended) Flat() map[string]string { + return map[string]string{} +} + +func (e *TrafficActionExtended) Flat() map[string]string { + return map[string]string{} +} + +func (p *PathAttributeExtendedCommunities) Flat() map[string]string { + flat := map[string]string{} + for _, ec := range p.Value { + FlatUpdate(flat, ec.Flat()) + } + return flat +} + +func (p *PathAttribute) Flat() map[string]string { + return map[string]string{} +} + +func (l *LabeledVPNIPAddrPrefix) Flat() map[string]string { + prefixLen := l.IPAddrPrefixDefault.Length - uint8(8*(l.Labels.Len()+l.RD.Len())) + return map[string]string{ + "Prefix": l.IPAddrPrefixDefault.Prefix.String(), + "PrefixLen": fmt.Sprintf("%d", prefixLen), + "NLRI": l.String(), + "Label": l.Labels.String(), + } +} + +func (p *IPAddrPrefixDefault) Flat() map[string]string { + l := strings.Split(p.String(), "/") + if len(l) == 2 { + return map[string]string{ + "Prefix": l[0], + "PrefixLen": l[1], + } + } + return map[string]string{} +} + +func (l *EVPNNLRI) Flat() map[string]string { + return map[string]string{} +} +func (l *RouteTargetMembershipNLRI) Flat() map[string]string { + return map[string]string{} +} +func (l *FlowSpecIPv4Unicast) Flat() map[string]string { + return map[string]string{} +} +func (l *FlowSpecIPv4VPN) Flat() map[string]string { + return map[string]string{} +} +func (l *FlowSpecIPv6Unicast) Flat() map[string]string { + return map[string]string{} +} +func (l *FlowSpecIPv6VPN) Flat() map[string]string { + return map[string]string{} +} +func (l *FlowSpecL2VPN) Flat() map[string]string { + return map[string]string{} +} +func (l *OpaqueNLRI) Flat() map[string]string { + return map[string]string{} +} + +// Update a Flat representation by adding elements of the second +// one. If two elements use same keys, values are separated with +// ';'. In this case, it returns an error but the update has been +// realized. +func FlatUpdate(f1, f2 map[string]string) error { + conflict := false + for k2, v2 := range f2 { + if v1, ok := f1[k2]; ok { + f1[k2] = v1 + ";" + v2 + conflict = true + } else { + f1[k2] = v2 + } + } + if conflict { + return fmt.Errorf("Keys conflict") + } else { + return nil + } +} diff --git a/pkg/packet/bgp/bgp_race_test.go b/pkg/packet/bgp/bgp_race_test.go new file mode 100644 index 00000000..08038200 --- /dev/null +++ b/pkg/packet/bgp/bgp_race_test.go @@ -0,0 +1,46 @@ +// Copyright (C) 2018 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// +build race + +package bgp + +import ( + "testing" + "time" +) + +// Test_RaceCondition detects data races when serialization. +// Currently tests only attributes contained in UPDATE message. +func Test_RaceCondition(t *testing.T) { + m := NewTestBGPUpdateMessage() + updateBody := m.Body.(*BGPUpdate) + + go func(body *BGPUpdate) { + for _, v := range body.WithdrawnRoutes { + v.Serialize() + } + for _, v := range body.PathAttributes { + v.Serialize() + } + for _, v := range body.NLRI { + v.Serialize() + } + }(updateBody) + + time.Sleep(time.Second) + + updateBody.Serialize() +} diff --git a/pkg/packet/bgp/bgp_test.go b/pkg/packet/bgp/bgp_test.go new file mode 100644 index 00000000..a4801f27 --- /dev/null +++ b/pkg/packet/bgp/bgp_test.go @@ -0,0 +1,1194 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgp + +import ( + "bytes" + "encoding/binary" + "net" + "reflect" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func keepalive() *BGPMessage { + return NewBGPKeepAliveMessage() +} + +func notification() *BGPMessage { + return NewBGPNotificationMessage(1, 2, nil) +} + +func refresh() *BGPMessage { + return NewBGPRouteRefreshMessage(1, 2, 10) +} + +var result []string + +func BenchmarkNormalizeFlowSpecOpValues(b *testing.B) { + var r []string + for n := 0; n < b.N; n++ { + r = normalizeFlowSpecOpValues([]string{"&<=80"}) + } + result = r +} + +func Test_Message(t *testing.T) { + l := []*BGPMessage{keepalive(), notification(), refresh(), NewTestBGPOpenMessage(), NewTestBGPUpdateMessage()} + + for _, m1 := range l { + buf1, err := m1.Serialize() + assert.NoError(t, err) + + t.Log("LEN =", len(buf1)) + m2, err := ParseBGPMessage(buf1) + assert.NoError(t, err) + + // FIXME: shouldn't but workaround for some structs. + _, err = m2.Serialize() + assert.NoError(t, err) + + assert.True(t, reflect.DeepEqual(m1, m2)) + } +} + +func Test_IPAddrPrefixString(t *testing.T) { + ipv4 := NewIPAddrPrefix(24, "129.6.10.0") + assert.Equal(t, "129.6.10.0/24", ipv4.String()) + ipv4 = NewIPAddrPrefix(24, "129.6.10.1") + assert.Equal(t, "129.6.10.0/24", ipv4.String()) + ipv4 = NewIPAddrPrefix(22, "129.6.129.0") + assert.Equal(t, "129.6.128.0/22", ipv4.String()) + + ipv6 := NewIPv6AddrPrefix(64, "3343:faba:3903::0") + assert.Equal(t, "3343:faba:3903::/64", ipv6.String()) + ipv6 = NewIPv6AddrPrefix(64, "3343:faba:3903::1") + assert.Equal(t, "3343:faba:3903::/64", ipv6.String()) + ipv6 = NewIPv6AddrPrefix(63, "3343:faba:3903:129::0") + assert.Equal(t, "3343:faba:3903:128::/63", ipv6.String()) +} + +func Test_RouteTargetMembershipNLRIString(t *testing.T) { + assert := assert.New(t) + + // TwoOctetAsSpecificExtended + buf := make([]byte, 13) + buf[0] = 96 // in bit length + binary.BigEndian.PutUint32(buf[1:5], 65546) + buf[5] = byte(EC_TYPE_TRANSITIVE_TWO_OCTET_AS_SPECIFIC) // typehigh + binary.BigEndian.PutUint16(buf[7:9], 65000) + binary.BigEndian.PutUint32(buf[9:], 65546) + r := &RouteTargetMembershipNLRI{} + err := r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:65000:65546", r.String()) + buf, err = r.Serialize() + assert.Equal(nil, err) + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:65000:65546", r.String()) + + // IPv4AddressSpecificExtended + buf = make([]byte, 13) + buf[0] = 96 // in bit length + binary.BigEndian.PutUint32(buf[1:5], 65546) + buf[5] = byte(EC_TYPE_TRANSITIVE_IP4_SPECIFIC) // typehigh + ip := net.ParseIP("10.0.0.1").To4() + copy(buf[7:11], []byte(ip)) + binary.BigEndian.PutUint16(buf[11:], 65000) + r = &RouteTargetMembershipNLRI{} + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:10.0.0.1:65000", r.String()) + buf, err = r.Serialize() + assert.Equal(nil, err) + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:10.0.0.1:65000", r.String()) + + // FourOctetAsSpecificExtended + buf = make([]byte, 13) + buf[0] = 96 // in bit length + binary.BigEndian.PutUint32(buf[1:5], 65546) + buf[5] = byte(EC_TYPE_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC) // typehigh + buf[6] = byte(EC_SUBTYPE_ROUTE_TARGET) // subtype + binary.BigEndian.PutUint32(buf[7:], 65546) + binary.BigEndian.PutUint16(buf[11:], 65000) + r = &RouteTargetMembershipNLRI{} + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:1.10:65000", r.String()) + buf, err = r.Serialize() + assert.Equal(nil, err) + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:1.10:65000", r.String()) + + // OpaqueExtended + buf = make([]byte, 13) + buf[0] = 96 // in bit length + binary.BigEndian.PutUint32(buf[1:5], 65546) + buf[5] = byte(EC_TYPE_TRANSITIVE_OPAQUE) // typehigh + binary.BigEndian.PutUint32(buf[9:], 1000000) + r = &RouteTargetMembershipNLRI{} + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:1000000", r.String()) + buf, err = r.Serialize() + assert.Equal(nil, err) + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:1000000", r.String()) + + // Unknown + buf = make([]byte, 13) + buf[0] = 96 // in bit length + binary.BigEndian.PutUint32(buf[1:5], 65546) + buf[5] = 0x04 // typehigh + binary.BigEndian.PutUint32(buf[9:], 1000000) + r = &RouteTargetMembershipNLRI{} + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:1000000", r.String()) + buf, err = r.Serialize() + assert.Equal(nil, err) + err = r.DecodeFromBytes(buf) + assert.Equal(nil, err) + assert.Equal("65546:1000000", r.String()) + +} + +func Test_MalformedUpdateMsg(t *testing.T) { + assert := assert.New(t) + + // Invalid AGGREGATOR + bufin := []byte{ + 0x00, 0x00, // Withdraws(0) + 0x00, 0x16, // Attrs Len(22) + 0xc0, 0x07, 0x05, // Flag, Type(7), Length(5) + 0x00, 0x00, 0x00, 0x64, // aggregator - invalid length + 0x00, + 0x40, 0x01, 0x01, 0x00, // Attr(ORIGIN) + 0x40, 0x03, 0x04, 0xc0, // Attr(NEXT_HOP) + 0xa8, 0x01, 0x64, + 0x40, 0x02, 0x00, // Attr(AS_PATH) + } + + u := &BGPUpdate{} + err := u.DecodeFromBytes(bufin) + assert.Error(err) + assert.Equal(ERROR_HANDLING_ATTRIBUTE_DISCARD, err.(*MessageError).ErrorHandling) + + // Invalid MP_REACH_NLRI + bufin = []byte{ + 0x00, 0x00, // Withdraws(0) + 0x00, 0x27, // Attrs Len(39) + 0x80, 0x0e, 0x1d, // Flag, Type(14), Length(29) + 0x00, 0x02, 0x01, // afi(2), safi(1) + 0x0f, 0x00, 0x00, 0x00, // nexthop - invalid nexthop length + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, + 0xff, 0x0a, 0x00, 0x00, + 0x00, // SNPA(0) + 0x40, 0x20, 0x01, 0x0d, // NLRI + 0xb8, 0x00, 0x01, 0x00, + 0x00, + 0x40, 0x01, 0x01, 0x00, // Attr(ORIGIN) + 0x40, 0x02, 0x00, // Attr(AS_PATH) + } + + err = u.DecodeFromBytes(bufin) + assert.Error(err) + assert.Equal(ERROR_HANDLING_AFISAFI_DISABLE, err.(*MessageError).ErrorHandling) + + // Invalid flag + bufin = []byte{ + 0x00, 0x00, // Withdraws(0) + 0x00, 0x0e, // Attrs Len(14) + 0xc0, 0x01, 0x01, 0x00, // Attr(ORIGIN) - invalid flag + 0x40, 0x03, 0x04, 0xc0, // Attr(NEXT_HOP) + 0xa8, 0x01, 0x64, + 0x40, 0x02, 0x00, // Attr(AS_PATH) + } + + err = u.DecodeFromBytes(bufin) + assert.Error(err) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, err.(*MessageError).ErrorHandling) + + // Invalid AGGREGATOR and MULTI_EXIT_DESC + bufin = []byte{ + 0x00, 0x00, // Withdraws(0) + 0x00, 0x1e, // Attrs Len(30) + 0xc0, 0x07, 0x05, 0x00, // Attr(AGGREGATOR) - invalid length + 0x00, 0x00, 0x64, 0x00, + 0x80, 0x04, 0x05, 0x00, // Attr(MULTI_EXIT_DESC) - invalid length + 0x00, 0x00, 0x00, 0x64, + 0x40, 0x01, 0x01, 0x00, // Attr(ORIGIN) + 0x40, 0x02, 0x00, // Attr(AS_PATH) + 0x40, 0x03, 0x04, 0xc0, // Attr(NEXT_HOP) + 0xa8, 0x01, 0x64, + 0x20, 0xc8, 0xc8, 0xc8, // NLRI + 0xc8, + } + + err = u.DecodeFromBytes(bufin) + assert.Error(err) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, err.(*MessageError).ErrorHandling) +} + +func Test_RFC5512(t *testing.T) { + assert := assert.New(t) + + buf := make([]byte, 8) + buf[0] = byte(EC_TYPE_TRANSITIVE_OPAQUE) + buf[1] = byte(EC_SUBTYPE_COLOR) + binary.BigEndian.PutUint32(buf[4:], 1000000) + ec, err := ParseExtended(buf) + assert.Equal(nil, err) + assert.Equal("1000000", ec.String()) + buf, err = ec.Serialize() + assert.Equal(nil, err) + assert.Equal([]byte{0x3, 0xb, 0x0, 0x0, 0x0, 0xf, 0x42, 0x40}, buf) + + buf = make([]byte, 8) + buf[0] = byte(EC_TYPE_TRANSITIVE_OPAQUE) + buf[1] = byte(EC_SUBTYPE_ENCAPSULATION) + binary.BigEndian.PutUint16(buf[6:], uint16(TUNNEL_TYPE_VXLAN)) + ec, err = ParseExtended(buf) + assert.Equal(nil, err) + assert.Equal("VXLAN", ec.String()) + buf, err = ec.Serialize() + assert.Equal(nil, err) + assert.Equal([]byte{0x3, 0xc, 0x0, 0x0, 0x0, 0x0, 0x0, 0x8}, buf) + + tlv := NewTunnelEncapTLV(TUNNEL_TYPE_VXLAN, []TunnelEncapSubTLVInterface{NewTunnelEncapSubTLVColor(10)}) + attr := NewPathAttributeTunnelEncap([]*TunnelEncapTLV{tlv}) + + buf1, err := attr.Serialize() + assert.Equal(nil, err) + + p, err := GetPathAttribute(buf1) + assert.Equal(nil, err) + + err = p.DecodeFromBytes(buf1) + assert.Equal(nil, err) + + buf2, err := p.Serialize() + assert.Equal(nil, err) + assert.Equal(buf1, buf2) + + n1 := NewEncapNLRI("10.0.0.1") + buf1, err = n1.Serialize() + assert.Equal(nil, err) + + n2 := NewEncapNLRI("") + err = n2.DecodeFromBytes(buf1) + assert.Equal(nil, err) + assert.Equal("10.0.0.1", n2.String()) + + n3 := NewEncapv6NLRI("2001::1") + buf1, err = n3.Serialize() + assert.Equal(nil, err) + + n4 := NewEncapv6NLRI("") + err = n4.DecodeFromBytes(buf1) + assert.Equal(nil, err) + assert.Equal("2001::1", n4.String()) +} + +func Test_ASLen(t *testing.T) { + assert := assert.New(t) + + aspath := AsPathParam{ + Num: 2, + AS: []uint16{65000, 65001}, + } + aspath.Type = BGP_ASPATH_ATTR_TYPE_SEQ + assert.Equal(2, aspath.ASLen()) + + aspath.Type = BGP_ASPATH_ATTR_TYPE_SET + assert.Equal(1, aspath.ASLen()) + + aspath.Type = BGP_ASPATH_ATTR_TYPE_CONFED_SEQ + assert.Equal(0, aspath.ASLen()) + + aspath.Type = BGP_ASPATH_ATTR_TYPE_CONFED_SET + assert.Equal(0, aspath.ASLen()) + + as4path := As4PathParam{ + Num: 2, + AS: []uint32{65000, 65001}, + } + as4path.Type = BGP_ASPATH_ATTR_TYPE_SEQ + assert.Equal(2, as4path.ASLen()) + + as4path.Type = BGP_ASPATH_ATTR_TYPE_SET + assert.Equal(1, as4path.ASLen()) + + as4path.Type = BGP_ASPATH_ATTR_TYPE_CONFED_SEQ + assert.Equal(0, as4path.ASLen()) + + as4path.Type = BGP_ASPATH_ATTR_TYPE_CONFED_SET + assert.Equal(0, as4path.ASLen()) + +} + +func Test_MPLSLabelStack(t *testing.T) { + assert := assert.New(t) + mpls := NewMPLSLabelStack() + buf, err := mpls.Serialize() + assert.Nil(err) + assert.Equal(true, bytes.Equal(buf, []byte{0, 0, 1})) + + mpls = &MPLSLabelStack{} + assert.Nil(mpls.DecodeFromBytes(buf)) + assert.Equal(1, len(mpls.Labels)) + assert.Equal(uint32(0), mpls.Labels[0]) + + mpls = NewMPLSLabelStack(WITHDRAW_LABEL) + buf, err = mpls.Serialize() + assert.Nil(err) + assert.Equal(true, bytes.Equal(buf, []byte{128, 0, 0})) + + mpls = &MPLSLabelStack{} + assert.Nil(mpls.DecodeFromBytes(buf)) + assert.Equal(1, len(mpls.Labels)) + assert.Equal(WITHDRAW_LABEL, mpls.Labels[0]) +} + +func Test_FlowSpecNlri(t *testing.T) { + assert := assert.New(t) + cmp := make([]FlowSpecComponentInterface, 0) + cmp = append(cmp, NewFlowSpecDestinationPrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + cmp = append(cmp, NewFlowSpecSourcePrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + item1 := NewFlowSpecComponentItem(DEC_NUM_OP_EQ, TCP) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_IP_PROTO, []*FlowSpecComponentItem{item1})) + item2 := NewFlowSpecComponentItem(DEC_NUM_OP_GT_EQ, 20) + item3 := NewFlowSpecComponentItem(DEC_NUM_OP_AND|DEC_NUM_OP_LT_EQ, 30) + item4 := NewFlowSpecComponentItem(DEC_NUM_OP_GT_EQ, 10) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_DST_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_SRC_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ICMP_TYPE, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ICMP_CODE, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_PKT_LEN, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_DSCP, []*FlowSpecComponentItem{item2, item3, item4})) + isFragment := uint64(0x02) + lastFragment := uint64(0x08) + item5 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_MATCH, isFragment) + item6 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_AND, lastFragment) + + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_FRAGMENT, []*FlowSpecComponentItem{item5, item6})) + item7 := NewFlowSpecComponentItem(0, TCP_FLAG_ACK) + item8 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_AND|BITMASK_FLAG_OP_NOT, TCP_FLAG_URGENT) + + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, []*FlowSpecComponentItem{item7, item8})) + n1 := NewFlowSpecIPv4Unicast(cmp) + + buf1, err := n1.Serialize() + assert.Nil(err) + + n2, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_FS_IPv4_UC)) + assert.Nil(err) + + err = n2.DecodeFromBytes(buf1) + assert.Nil(err) + // should be equal + assert.Equal(n1, n2) +} + +func Test_FlowSpecExtended(t *testing.T) { + assert := assert.New(t) + exts := make([]ExtendedCommunityInterface, 0) + exts = append(exts, NewTrafficRateExtended(100, 9600.0)) + exts = append(exts, NewTrafficActionExtended(true, false)) + exts = append(exts, NewRedirectTwoOctetAsSpecificExtended(1000, 1000)) + exts = append(exts, NewRedirectIPv4AddressSpecificExtended("10.0.0.1", 1000)) + exts = append(exts, NewRedirectFourOctetAsSpecificExtended(10000000, 1000)) + exts = append(exts, NewTrafficRemarkExtended(10)) + m1 := NewPathAttributeExtendedCommunities(exts) + buf1, err := m1.Serialize() + require.NoError(t, err) + + m2 := NewPathAttributeExtendedCommunities(nil) + err = m2.DecodeFromBytes(buf1) + require.NoError(t, err) + + _, err = m2.Serialize() + require.NoError(t, err) + + assert.Equal(m1, m2) +} + +func Test_IP6FlowSpecExtended(t *testing.T) { + exts := make([]ExtendedCommunityInterface, 0) + exts = append(exts, NewRedirectIPv6AddressSpecificExtended("2001:db8::68", 1000)) + m1 := NewPathAttributeIP6ExtendedCommunities(exts) + buf1, err := m1.Serialize() + require.NoError(t, err) + + m2 := NewPathAttributeIP6ExtendedCommunities(nil) + err = m2.DecodeFromBytes(buf1) + require.NoError(t, err) + + _, err = m2.Serialize() + require.NoError(t, err) + + assert.Equal(t, m1, m2) +} + +func Test_FlowSpecNlriv6(t *testing.T) { + cmp := make([]FlowSpecComponentInterface, 0) + cmp = append(cmp, NewFlowSpecDestinationPrefix6(NewIPv6AddrPrefix(64, "2001::"), 12)) + cmp = append(cmp, NewFlowSpecSourcePrefix6(NewIPv6AddrPrefix(64, "2001::"), 12)) + item1 := NewFlowSpecComponentItem(DEC_NUM_OP_EQ, TCP) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_IP_PROTO, []*FlowSpecComponentItem{item1})) + item2 := NewFlowSpecComponentItem(DEC_NUM_OP_GT_EQ, 20) + item3 := NewFlowSpecComponentItem(DEC_NUM_OP_AND|DEC_NUM_OP_LT_EQ, 30) + item4 := NewFlowSpecComponentItem(DEC_NUM_OP_EQ, 10) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_DST_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_SRC_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ICMP_TYPE, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ICMP_CODE, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_PKT_LEN, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_DSCP, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_LABEL, []*FlowSpecComponentItem{item2, item3, item4})) + isFragment := uint64(0x02) + item5 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_MATCH, isFragment) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_FRAGMENT, []*FlowSpecComponentItem{item5})) + item6 := NewFlowSpecComponentItem(0, TCP_FLAG_ACK) + item7 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_AND|BITMASK_FLAG_OP_NOT, TCP_FLAG_URGENT) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, []*FlowSpecComponentItem{item6, item7})) + n1 := NewFlowSpecIPv6Unicast(cmp) + buf1, err := n1.Serialize() + require.NoError(t, err) + + n2, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_FS_IPv6_UC)) + require.NoError(t, err) + + err = n2.DecodeFromBytes(buf1) + require.NoError(t, err) + + _, err = n2.Serialize() + require.NoError(t, err) + + assert.Equal(t, n1, n2) +} + +func Test_Aigp(t *testing.T) { + assert := assert.New(t) + m := NewAigpTLVIgpMetric(1000) + a1 := NewPathAttributeAigp([]AigpTLVInterface{m}) + buf1, err := a1.Serialize() + require.NoError(t, err) + + a2 := NewPathAttributeAigp(nil) + err = a2.DecodeFromBytes(buf1) + require.NoError(t, err) + + assert.Equal(a1, a2) +} + +func Test_FlowSpecNlriL2(t *testing.T) { + assert := assert.New(t) + mac, _ := net.ParseMAC("01:23:45:67:89:ab") + cmp := make([]FlowSpecComponentInterface, 0) + cmp = append(cmp, NewFlowSpecDestinationMac(mac)) + cmp = append(cmp, NewFlowSpecSourceMac(mac)) + item1 := NewFlowSpecComponentItem(DEC_NUM_OP_EQ, uint64(IPv4)) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ETHERNET_TYPE, []*FlowSpecComponentItem{item1})) + rd, _ := ParseRouteDistinguisher("100:100") + n1 := NewFlowSpecL2VPN(rd, cmp) + buf1, err := n1.Serialize() + assert.Nil(err) + n2, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_FS_L2_VPN)) + assert.Nil(err) + err = n2.DecodeFromBytes(buf1) + assert.Nil(err) + + assert.Equal(n1, n2) +} + +func Test_NotificationErrorCode(t *testing.T) { + // boundary check + t.Log(NewNotificationErrorCode(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_TYPE).String()) + t.Log(NewNotificationErrorCode(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_TYPE+1).String()) + t.Log(NewNotificationErrorCode(BGP_ERROR_MESSAGE_HEADER_ERROR, 0).String()) + t.Log(NewNotificationErrorCode(0, BGP_ERROR_SUB_BAD_MESSAGE_TYPE).String()) + t.Log(NewNotificationErrorCode(BGP_ERROR_ROUTE_REFRESH_MESSAGE_ERROR+1, 0).String()) +} + +func Test_FlowSpecNlriVPN(t *testing.T) { + assert := assert.New(t) + cmp := make([]FlowSpecComponentInterface, 0) + cmp = append(cmp, NewFlowSpecDestinationPrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + cmp = append(cmp, NewFlowSpecSourcePrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + rd, _ := ParseRouteDistinguisher("100:100") + n1 := NewFlowSpecIPv4VPN(rd, cmp) + buf1, err := n1.Serialize() + assert.Nil(err) + n2, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_FS_IPv4_VPN)) + assert.Nil(err) + err = n2.DecodeFromBytes(buf1) + require.NoError(t, err) + + assert.Equal(n1, n2) +} + +func Test_EVPNIPPrefixRoute(t *testing.T) { + assert := assert.New(t) + rd, _ := ParseRouteDistinguisher("100:100") + r := &EVPNIPPrefixRoute{ + RD: rd, + ESI: EthernetSegmentIdentifier{ + Type: ESI_ARBITRARY, + Value: make([]byte, 9), + }, + ETag: 10, + IPPrefixLength: 24, + IPPrefix: net.IP{10, 10, 10, 0}, + GWIPAddress: net.IP{10, 10, 10, 10}, + Label: 1000, + } + n1 := NewEVPNNLRI(EVPN_IP_PREFIX, r) + buf1, err := n1.Serialize() + assert.Nil(err) + n2, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_EVPN)) + assert.Nil(err) + err = n2.DecodeFromBytes(buf1) + assert.Nil(err) + + assert.Equal(n1, n2) +} + +func Test_CapExtendedNexthop(t *testing.T) { + assert := assert.New(t) + tuple := NewCapExtendedNexthopTuple(RF_IPv4_UC, AFI_IP6) + n1 := NewCapExtendedNexthop([]*CapExtendedNexthopTuple{tuple}) + buf1, err := n1.Serialize() + assert.Nil(err) + n2, err := DecodeCapability(buf1) + assert.Nil(err) + + assert.Equal(n1, n2) +} + +func Test_AddPath(t *testing.T) { + assert := assert.New(t) + opt := &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}} + { + n1 := NewIPAddrPrefix(24, "10.10.10.0") + assert.Equal(n1.PathIdentifier(), uint32(0)) + n1.SetPathLocalIdentifier(10) + assert.Equal(n1.PathLocalIdentifier(), uint32(10)) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := &IPAddrPrefix{} + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(10)) + } + { + n1 := NewIPv6AddrPrefix(64, "2001::") + n1.SetPathIdentifier(10) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewIPv6AddrPrefix(0, "") + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(0)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH, RF_IPv6_UC: BGP_ADD_PATH_BOTH}} + { + n1 := NewIPv6AddrPrefix(64, "2001::") + n1.SetPathLocalIdentifier(10) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewIPv6AddrPrefix(0, "") + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(10)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_IPv4_VPN: BGP_ADD_PATH_BOTH, RF_IPv6_VPN: BGP_ADD_PATH_BOTH}} + { + rd, _ := ParseRouteDistinguisher("100:100") + labels := NewMPLSLabelStack(100, 200) + n1 := NewLabeledVPNIPAddrPrefix(24, "10.10.10.0", *labels, rd) + n1.SetPathLocalIdentifier(20) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewLabeledVPNIPAddrPrefix(0, "", MPLSLabelStack{}, nil) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(20)) + } + { + rd, _ := ParseRouteDistinguisher("100:100") + labels := NewMPLSLabelStack(100, 200) + n1 := NewLabeledVPNIPv6AddrPrefix(64, "2001::", *labels, rd) + n1.SetPathLocalIdentifier(20) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewLabeledVPNIPAddrPrefix(0, "", MPLSLabelStack{}, nil) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(20)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_IPv4_MPLS: BGP_ADD_PATH_BOTH, RF_IPv6_MPLS: BGP_ADD_PATH_BOTH}} + { + labels := NewMPLSLabelStack(100, 200) + n1 := NewLabeledIPAddrPrefix(24, "10.10.10.0", *labels) + n1.SetPathLocalIdentifier(20) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewLabeledIPAddrPrefix(0, "", MPLSLabelStack{}) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(20)) + } + { + labels := NewMPLSLabelStack(100, 200) + n1 := NewLabeledIPv6AddrPrefix(64, "2001::", *labels) + n1.SetPathLocalIdentifier(20) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewLabeledIPAddrPrefix(0, "", MPLSLabelStack{}) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(20)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_RTC_UC: BGP_ADD_PATH_BOTH}} + { + rt, _ := ParseRouteTarget("100:100") + n1 := NewRouteTargetMembershipNLRI(65000, rt) + n1.SetPathLocalIdentifier(30) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewRouteTargetMembershipNLRI(0, nil) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(30)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_EVPN: BGP_ADD_PATH_BOTH}} + { + n1 := NewEVPNNLRI(EVPN_ROUTE_TYPE_ETHERNET_AUTO_DISCOVERY, + &EVPNEthernetAutoDiscoveryRoute{NewRouteDistinguisherFourOctetAS(5, 6), + EthernetSegmentIdentifier{ESI_ARBITRARY, make([]byte, 9)}, 2, 2}) + n1.SetPathLocalIdentifier(40) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewEVPNNLRI(0, nil) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(40)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_IPv4_ENCAP: BGP_ADD_PATH_BOTH}} + { + n1 := NewEncapNLRI("10.10.10.0") + n1.SetPathLocalIdentifier(50) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewEncapNLRI("") + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(50)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_FS_IPv4_UC: BGP_ADD_PATH_BOTH}} + { + n1 := NewFlowSpecIPv4Unicast([]FlowSpecComponentInterface{NewFlowSpecDestinationPrefix(NewIPAddrPrefix(24, "10.0.0.0"))}) + n1.SetPathLocalIdentifier(60) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := NewFlowSpecIPv4Unicast(nil) + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(60)) + } + opt = &MarshallingOption{AddPath: map[RouteFamily]BGPAddPathMode{RF_OPAQUE: BGP_ADD_PATH_BOTH}} + { + n1 := NewOpaqueNLRI([]byte("key"), []byte("value")) + n1.SetPathLocalIdentifier(70) + bits, err := n1.Serialize(opt) + assert.Nil(err) + n2 := &OpaqueNLRI{} + err = n2.DecodeFromBytes(bits, opt) + assert.Nil(err) + assert.Equal(n2.PathIdentifier(), uint32(70)) + } + +} + +func Test_CompareFlowSpecNLRI(t *testing.T) { + assert := assert.New(t) + cmp, err := ParseFlowSpecComponents(RF_FS_IPv4_UC, "destination 10.0.0.2/32 source 10.0.0.1/32 destination-port ==3128 protocol tcp") + assert.Nil(err) + // Note: Use NewFlowSpecIPv4Unicast() for the consistent ordered rules. + n1 := NewFlowSpecIPv4Unicast(cmp).FlowSpecNLRI + cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "source 10.0.0.0/24 destination-port ==3128 protocol tcp") + assert.Nil(err) + n2 := NewFlowSpecIPv4Unicast(cmp).FlowSpecNLRI + r, err := CompareFlowSpecNLRI(&n1, &n2) + assert.Nil(err) + assert.True(r > 0) + cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "source 10.0.0.9/32 port ==80 ==8080 destination-port >8080&<8080 ==3128 source-port >1024 protocol ==udp ==tcp") + n3 := NewFlowSpecIPv4Unicast(cmp).FlowSpecNLRI + assert.Nil(err) + cmp, err = ParseFlowSpecComponents(RF_FS_IPv4_UC, "destination 192.168.0.2/32") + n4 := NewFlowSpecIPv4Unicast(cmp).FlowSpecNLRI + assert.Nil(err) + r, err = CompareFlowSpecNLRI(&n3, &n4) + assert.Nil(err) + assert.True(r < 0) +} + +func Test_MpReachNLRIWithIPv4MappedIPv6Prefix(t *testing.T) { + assert := assert.New(t) + n1 := NewIPv6AddrPrefix(120, "::ffff:10.0.0.0") + buf1, err := n1.Serialize() + assert.Nil(err) + n2, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_IPv6_UC)) + assert.Nil(err) + err = n2.DecodeFromBytes(buf1) + assert.Nil(err) + + assert.Equal(n1, n2) + + label := NewMPLSLabelStack(2) + + n3 := NewLabeledIPv6AddrPrefix(120, "::ffff:10.0.0.0", *label) + buf1, err = n3.Serialize() + assert.Nil(err) + n4, err := NewPrefixFromRouteFamily(RouteFamilyToAfiSafi(RF_IPv6_MPLS)) + assert.Nil(err) + err = n4.DecodeFromBytes(buf1) + assert.Nil(err) + + assert.Equal(n3, n4) +} + +func Test_MpReachNLRIWithIPv6PrefixWithIPv4Peering(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x80, 0x0e, 0x1e, // flags(1), type(1), length(1) + 0x00, 0x02, 0x01, 0x10, // afi(2), safi(1), nexthoplen(1) + 0x00, 0x00, 0x00, 0x00, // nexthop(16) + 0x00, 0x00, 0x00, 0x00, // = "::ffff:172.20.0.1" + 0x00, 0x00, 0xff, 0xff, + 0xac, 0x14, 0x00, 0x01, + 0x00, // reserved(1) + 0x40, 0x20, 0x01, 0x0d, // nlri(9) + 0xb8, 0x00, 0x01, 0x00, // = "2001:db8:1:1::/64" + 0x01, + } + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x80), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x1e), p.Length) + assert.Equal(uint16(AFI_IP6), p.AFI) + assert.Equal(uint8(SAFI_UNICAST), p.SAFI) + assert.Equal(net.ParseIP("::ffff:172.20.0.1"), p.Nexthop) + assert.Equal(net.ParseIP(""), p.LinkLocalNexthop) + value := []AddrPrefixInterface{ + NewIPv6AddrPrefix(64, "2001:db8:1:1::"), + } + assert.Equal(value, p.Value) + // Set NextHop as IPv4 address (because IPv4 peering) + p.Nexthop = net.ParseIP("172.20.0.1") + // Test Serialize() + bufout, err := p.Serialize() + assert.Nil(err) + // Test serialised value + assert.Equal(bufin, bufout) +} + +func Test_MpReachNLRIWithIPv6(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x90, 0x0e, 0x00, 0x1e, // flags(1), type(1), length(2), + 0x00, 0x02, 0x01, 0x10, // afi(2), safi(1), nexthoplen(1) + 0x20, 0x01, 0x0d, 0xb8, // nexthop(16) + 0x00, 0x01, 0x00, 0x00, // = "2001:db8:1::1" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, // reserved(1) + 0x40, 0x20, 0x01, 0x0d, // nlri(9) + 0xb8, 0x00, 0x53, 0x00, // = "2001:db8:53::/64" + 0x00, + } + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x90), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x1e), p.Length) + assert.Equal(uint16(AFI_IP6), p.AFI) + assert.Equal(uint8(SAFI_UNICAST), p.SAFI) + assert.Equal(net.ParseIP("2001:db8:1::1"), p.Nexthop) + value := []AddrPrefixInterface{ + NewIPv6AddrPrefix(64, "2001:db8:53::"), + } + assert.Equal(value, p.Value) +} + +func Test_MpUnreachNLRIWithIPv6(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x90, 0x0f, 0x00, 0x0c, // flags(1), type(1), length(2), + 0x00, 0x02, 0x01, // afi(2), safi(1), + 0x40, 0x20, 0x01, 0x0d, // nlri(9) + 0xb8, 0x00, 0x53, 0x00, // = "2001:db8:53::/64" + 0x00, + } + // Test DecodeFromBytes() + p := &PathAttributeMpUnreachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x90), p.Flags) + assert.Equal(BGPAttrType(0xf), p.Type) + assert.Equal(uint16(0x0c), p.Length) + assert.Equal(uint16(AFI_IP6), p.AFI) + assert.Equal(uint8(SAFI_UNICAST), p.SAFI) + value := []AddrPrefixInterface{ + NewIPv6AddrPrefix(64, "2001:db8:53::"), + } + assert.Equal(value, p.Value) +} + +func Test_MpReachNLRIWithIPv6PrefixWithLinkLocalNexthop(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x80, 0x0e, 0x2c, // flags(1), type(1), length(1) + 0x00, 0x02, 0x01, 0x20, // afi(2), safi(1), nexthoplen(1) + 0x20, 0x01, 0x0d, 0xb8, // nexthop(32) + 0x00, 0x01, 0x00, 0x00, // = "2001:db8:1::1" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0xfe, 0x80, 0x00, 0x00, // + "fe80::1" (link local) + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, // reserved(1) + 0x30, 0x20, 0x10, 0x0a, // nlri(7) + 0xb8, 0x00, 0x01, // = "2010:ab8:1::/48" + } + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x80), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x2c), p.Length) + assert.Equal(uint16(AFI_IP6), p.AFI) + assert.Equal(uint8(SAFI_UNICAST), p.SAFI) + assert.Equal(net.ParseIP("2001:db8:1::1"), p.Nexthop) + assert.Equal(net.ParseIP("fe80::1"), p.LinkLocalNexthop) + value := []AddrPrefixInterface{ + NewIPv6AddrPrefix(48, "2010:ab8:1::"), + } + assert.Equal(value, p.Value) + // Test Serialize() + bufout, err := p.Serialize() + assert.Nil(err) + // Test serialised value + assert.Equal(bufin, bufout) +} + +func Test_MpReachNLRIWithVPNv4Prefix(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x80, 0x0e, 0x20, // flags(1), type(1), length(1) + 0x00, 0x01, 0x80, 0x0c, // afi(2), safi(1), nexthoplen(1) + 0x00, 0x00, 0x00, 0x00, // nexthop(12) + 0x00, 0x00, 0x00, 0x00, // = (rd:"0:0",) "172.20.0.1" + 0xac, 0x14, 0x00, 0x01, + 0x00, // reserved(1) + 0x70, 0x00, 0x01, 0x01, // nlri(15) + 0x00, 0x00, 0xfd, 0xe8, // = label:16, rd:"65000:100", prefix:"10.1.1.0/24" + 0x00, 0x00, 0x00, 0x64, + 0x0a, 0x01, 0x01, + } + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x80), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x20), p.Length) + assert.Equal(uint16(AFI_IP), p.AFI) + assert.Equal(uint8(SAFI_MPLS_VPN), p.SAFI) + assert.Equal(net.ParseIP("172.20.0.1").To4(), p.Nexthop) + assert.Equal(net.ParseIP(""), p.LinkLocalNexthop) + value := []AddrPrefixInterface{ + NewLabeledVPNIPAddrPrefix(24, "10.1.1.0", *NewMPLSLabelStack(16), + NewRouteDistinguisherTwoOctetAS(65000, 100)), + } + assert.Equal(value, p.Value) + // Test Serialize() + bufout, err := p.Serialize() + assert.Nil(err) + // Test serialised value + assert.Equal(bufin, bufout) +} + +func Test_MpReachNLRIWithVPNv6Prefix(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x80, 0x0e, 0x39, // flags(1), type(1), length(1) + 0x00, 0x02, 0x80, 0x18, // afi(2), safi(1), nexthoplen(1) + 0x00, 0x00, 0x00, 0x00, // nexthop(24) + 0x00, 0x00, 0x00, 0x00, // = (rd:"0:0",) "2001:db8:1::1" + 0x20, 0x01, 0x0d, 0xb8, + 0x00, 0x01, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, // reserved(1) + 0xd4, 0x00, 0x01, 0x01, // nlri(28) + 0x00, 0x00, 0xfd, 0xe8, // = label:16, rd:"65000:100", prefix:"2001:1::/124" + 0x00, 0x00, 0x00, 0x64, + 0x20, 0x01, 0x00, 0x01, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + } + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x80), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x39), p.Length) + assert.Equal(uint16(AFI_IP6), p.AFI) + assert.Equal(uint8(SAFI_MPLS_VPN), p.SAFI) + assert.Equal(net.ParseIP("2001:db8:1::1"), p.Nexthop) + assert.Equal(net.ParseIP(""), p.LinkLocalNexthop) + value := []AddrPrefixInterface{ + NewLabeledVPNIPv6AddrPrefix(124, "2001:1::", *NewMPLSLabelStack(16), + NewRouteDistinguisherTwoOctetAS(65000, 100)), + } + assert.Equal(value, p.Value) + // Test Serialize() + bufout, err := p.Serialize() + assert.Nil(err) + // Test serialised value + assert.Equal(bufin, bufout) +} + +func Test_MpReachNLRIWithIPv4PrefixWithIPv6Nexthop(t *testing.T) { + assert := assert.New(t) + bufin := []byte{ + 0x80, 0x0e, 0x19, // flags(1), type(1), length(1) + 0x00, 0x01, 0x01, 0x10, // afi(1), safi(1), nexthoplen(1) + 0x20, 0x01, 0x0d, 0xb8, // nexthop(32) + 0x00, 0x01, 0x00, 0x00, // = "2001:db8:1::1" + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, + 0x00, // reserved(1) + 0x18, 0xc0, 0xa8, 0x0a, // nlri(7) + } + // Test DecodeFromBytes() + p := &PathAttributeMpReachNLRI{} + err := p.DecodeFromBytes(bufin) + assert.Nil(err) + // Test decoded values + assert.Equal(BGPAttrFlag(0x80), p.Flags) + assert.Equal(BGPAttrType(0xe), p.Type) + assert.Equal(uint16(0x19), p.Length) + assert.Equal(uint16(AFI_IP), p.AFI) + assert.Equal(uint8(SAFI_UNICAST), p.SAFI) + assert.Equal(net.ParseIP("2001:db8:1::1"), p.Nexthop) + value := []AddrPrefixInterface{ + NewIPAddrPrefix(24, "192.168.10.0"), + } + assert.Equal(value, p.Value) + // Test Serialize() + bufout, err := p.Serialize() + assert.Nil(err) + // Test serialised value + assert.Equal(bufin, bufout) +} + +func Test_ParseRouteDistinguisher(t *testing.T) { + assert := assert.New(t) + + rd, _ := ParseRouteDistinguisher("100:1000") + rdType0, ok := rd.(*RouteDistinguisherTwoOctetAS) + if !ok { + t.Fatal("Type of RD interface is not RouteDistinguisherTwoOctetAS") + } + + assert.Equal(uint16(100), rdType0.Admin) + assert.Equal(uint32(1000), rdType0.Assigned) + + rd, _ = ParseRouteDistinguisher("10.0.0.0:100") + rdType1, ok := rd.(*RouteDistinguisherIPAddressAS) + if !ok { + t.Fatal("Type of RD interface is not RouteDistinguisherIPAddressAS") + } + + assert.Equal("10.0.0.0", rdType1.Admin.String()) + assert.Equal(uint16(100), rdType1.Assigned) + + rd, _ = ParseRouteDistinguisher("100.1000:10000") + rdType2, ok := rd.(*RouteDistinguisherFourOctetAS) + if !ok { + t.Fatal("Type of RD interface is not RouteDistinguisherFourOctetAS") + } + + assert.Equal(uint32((100<<16)|1000), rdType2.Admin) + assert.Equal(uint16(10000), rdType2.Assigned) +} + +func Test_ParseEthernetSegmentIdentifier(t *testing.T) { + assert := assert.New(t) + + // "single-homed" + esiZero := EthernetSegmentIdentifier{} + args := make([]string, 0) + esi, err := ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(esiZero, esi) + args = []string{"single-homed"} + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(esiZero, esi) + + // ESI_ARBITRARY + args = []string{"ARBITRARY", "11:22:33:44:55:66:77:88:99"} // omit "ESI_" + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESI_ARBITRARY, + Value: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, + }, esi) + + // ESI_LACP + args = []string{"lacp", "aa:bb:cc:dd:ee:ff", strconv.FormatInt(0x1122, 10)} // lower case + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESI_LACP, + Value: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22, 0x00}, + }, esi) + + // ESI_MSTP + args = []string{"esi_mstp", "aa:bb:cc:dd:ee:ff", strconv.FormatInt(0x1122, 10)} // omit "ESI_" + lower case + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESI_MSTP, + Value: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22, 0x00}, + }, esi) + + // ESI_MAC + args = []string{"ESI_MAC", "aa:bb:cc:dd:ee:ff", strconv.FormatInt(0x112233, 10)} + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESI_MAC, + Value: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff, 0x11, 0x22, 0x33}, + }, esi) + + // ESI_ROUTERID + args = []string{"ESI_ROUTERID", "1.1.1.1", strconv.FormatInt(0x11223344, 10)} + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESI_ROUTERID, + Value: []byte{0x01, 0x01, 0x01, 0x01, 0x11, 0x22, 0x33, 0x44, 0x00}, + }, esi) + + // ESI_AS + args = []string{"ESI_AS", strconv.FormatInt(0xaabbccdd, 10), strconv.FormatInt(0x11223344, 10)} + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESI_AS, + Value: []byte{0xaa, 0xbb, 0xcc, 0xdd, 0x11, 0x22, 0x33, 0x44, 0x00}, + }, esi) + + // Other + args = []string{"99", "11:22:33:44:55:66:77:88:99"} + esi, err = ParseEthernetSegmentIdentifier(args) + assert.Nil(err) + assert.Equal(EthernetSegmentIdentifier{ + Type: ESIType(99), + Value: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, + }, esi) +} + +func TestParseBogusShortData(t *testing.T) { + var bodies = []BGPBody{ + &BGPOpen{}, + &BGPUpdate{}, + &BGPNotification{}, + &BGPKeepAlive{}, + &BGPRouteRefresh{}, + } + + for _, b := range bodies { + b.DecodeFromBytes([]byte{0}) + } +} + +func TestFuzzCrashers(t *testing.T) { + var crashers = []string{ + "000000000000000000\x01", + } + + for _, f := range crashers { + ParseBGPMessage([]byte(f)) + } +} + +func TestNormalizeFlowSpecOpValues(t *testing.T) { + tests := []struct { + msg string + args []string + want []string + }{ + { + msg: "valid match", + args: []string{" & <=80", " tcp != udp ", " =! SA & =U! F", " = is-fragment+last-fragment"}, + want: []string{"<=80", "tcp", "!=udp", "=!SA", "&=U", "!F", "=is-fragment+last-fragment"}, + }, + { + msg: "RFC5575 trims & prefix", + args: []string{"&<=80"}, + want: []string{"<=80"}, + }, + } + + for _, tt := range tests { + t.Run(tt.msg, func(t *testing.T) { + got := normalizeFlowSpecOpValues(tt.args) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/packet/bgp/bgpattrtype_string.go b/pkg/packet/bgp/bgpattrtype_string.go new file mode 100644 index 00000000..1a2cf1d0 --- /dev/null +++ b/pkg/packet/bgp/bgpattrtype_string.go @@ -0,0 +1,28 @@ +// generated by stringer -type BGPAttrType bgp.go; DO NOT EDIT + +package bgp + +import "fmt" + +const ( + _BGPAttrType_name_0 = "BGP_ATTR_TYPE_ORIGINBGP_ATTR_TYPE_AS_PATHBGP_ATTR_TYPE_NEXT_HOPBGP_ATTR_TYPE_MULTI_EXIT_DISCBGP_ATTR_TYPE_LOCAL_PREFBGP_ATTR_TYPE_ATOMIC_AGGREGATEBGP_ATTR_TYPE_AGGREGATORBGP_ATTR_TYPE_COMMUNITIESBGP_ATTR_TYPE_ORIGINATOR_IDBGP_ATTR_TYPE_CLUSTER_LIST" + _BGPAttrType_name_1 = "BGP_ATTR_TYPE_MP_REACH_NLRIBGP_ATTR_TYPE_MP_UNREACH_NLRIBGP_ATTR_TYPE_EXTENDED_COMMUNITIESBGP_ATTR_TYPE_AS4_PATHBGP_ATTR_TYPE_AS4_AGGREGATOR" +) + +var ( + _BGPAttrType_index_0 = [...]uint8{0, 20, 41, 63, 92, 116, 146, 170, 195, 222, 248} + _BGPAttrType_index_1 = [...]uint8{0, 27, 56, 90, 112, 140} +) + +func (i BGPAttrType) String() string { + switch { + case 1 <= i && i <= 10: + i -= 1 + return _BGPAttrType_name_0[_BGPAttrType_index_0[i]:_BGPAttrType_index_0[i+1]] + case 14 <= i && i <= 18: + i -= 14 + return _BGPAttrType_name_1[_BGPAttrType_index_1[i]:_BGPAttrType_index_1[i+1]] + default: + return fmt.Sprintf("BGPAttrType(%d)", i) + } +} diff --git a/pkg/packet/bgp/constant.go b/pkg/packet/bgp/constant.go new file mode 100644 index 00000000..5ea7b414 --- /dev/null +++ b/pkg/packet/bgp/constant.go @@ -0,0 +1,327 @@ +// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgp + +import ( + "fmt" + "strings" +) + +const AS_TRANS = 23456 + +const BGP_PORT = 179 + +type FSMState int + +const ( + BGP_FSM_IDLE FSMState = iota + BGP_FSM_CONNECT + BGP_FSM_ACTIVE + BGP_FSM_OPENSENT + BGP_FSM_OPENCONFIRM + BGP_FSM_ESTABLISHED +) + +// partially taken from http://www.iana.org/assignments/protocol-numbers/protocol-numbers.xhtml +type Protocol int + +const ( + Unknown Protocol = iota + ICMP = 0x01 + IGMP = 0x02 + TCP = 0x06 + EGP = 0x08 + IGP = 0x09 + UDP = 0x11 + RSVP = 0x2e + GRE = 0x2f + OSPF = 0x59 + IPIP = 0x5e + PIM = 0x67 + SCTP = 0x84 +) + +var ProtocolNameMap = map[Protocol]string{ + Unknown: "unknown", + ICMP: "icmp", + IGMP: "igmp", + TCP: "tcp", + EGP: "egp", + IGP: "igp", + UDP: "udp", + RSVP: "rsvp", + GRE: "gre", + OSPF: "ospf", + IPIP: "ipip", + PIM: "pim", + SCTP: "sctp", +} + +func (p Protocol) String() string { + name, ok := ProtocolNameMap[p] + if !ok { + return fmt.Sprintf("%d", p) + } + return name +} + +type TCPFlag int + +const ( + _ TCPFlag = iota + TCP_FLAG_FIN = 0x01 + TCP_FLAG_SYN = 0x02 + TCP_FLAG_RST = 0x04 + TCP_FLAG_PUSH = 0x08 + TCP_FLAG_ACK = 0x10 + TCP_FLAG_URGENT = 0x20 + TCP_FLAG_ECE = 0x40 + TCP_FLAG_CWR = 0x80 +) + +var TCPFlagNameMap = map[TCPFlag]string{ + TCP_FLAG_FIN: "F", + TCP_FLAG_SYN: "S", + TCP_FLAG_RST: "R", + TCP_FLAG_PUSH: "P", + TCP_FLAG_ACK: "A", + TCP_FLAG_URGENT: "U", + TCP_FLAG_CWR: "C", + TCP_FLAG_ECE: "E", +} + +// Prepares a sorted list of flags because map iterations does not happen +// in a consistent order in Golang. +var TCPSortedFlags = []TCPFlag{ + TCP_FLAG_FIN, + TCP_FLAG_SYN, + TCP_FLAG_RST, + TCP_FLAG_PUSH, + TCP_FLAG_ACK, + TCP_FLAG_URGENT, + TCP_FLAG_ECE, + TCP_FLAG_CWR, +} + +func (f TCPFlag) String() string { + flags := make([]string, 0, len(TCPSortedFlags)) + for _, v := range TCPSortedFlags { + if f&v > 0 { + flags = append(flags, TCPFlagNameMap[v]) + } + } + return strings.Join(flags, "") +} + +type BitmaskFlagOp uint8 + +const ( + BITMASK_FLAG_OP_OR BitmaskFlagOp = iota + BITMASK_FLAG_OP_MATCH = 0x01 + BITMASK_FLAG_OP_NOT = 0x02 + BITMASK_FLAG_OP_NOT_MATCH = 0x03 + BITMASK_FLAG_OP_AND = 0x40 + BITMASK_FLAG_OP_END = 0x80 +) + +var BitmaskFlagOpNameMap = map[BitmaskFlagOp]string{ + BITMASK_FLAG_OP_OR: " ", + BITMASK_FLAG_OP_AND: "&", + BITMASK_FLAG_OP_END: "E", + BITMASK_FLAG_OP_NOT: "!", + BITMASK_FLAG_OP_MATCH: "=", +} + +// Note: Meaning of "" is different from that of the numeric operator because +// RFC5575 says if the Match bit in the bitmask operand is set, it should be +// "strictly" matching against the given value. +var BitmaskFlagOpValueMap = map[string]BitmaskFlagOp{ + " ": BITMASK_FLAG_OP_OR, + "": BITMASK_FLAG_OP_OR, + "==": BITMASK_FLAG_OP_MATCH, + "=": BITMASK_FLAG_OP_MATCH, + "!": BITMASK_FLAG_OP_NOT, + "!=": BITMASK_FLAG_OP_NOT_MATCH, + "=!": BITMASK_FLAG_OP_NOT_MATCH, // For the backward compatibility + "&": BITMASK_FLAG_OP_AND, + "E": BITMASK_FLAG_OP_END, +} + +func (f BitmaskFlagOp) String() string { + ops := make([]string, 0) + if f&BITMASK_FLAG_OP_AND > 0 { + ops = append(ops, BitmaskFlagOpNameMap[BITMASK_FLAG_OP_AND]) + } else { + ops = append(ops, BitmaskFlagOpNameMap[BITMASK_FLAG_OP_OR]) + } + if f&BITMASK_FLAG_OP_NOT > 0 { + ops = append(ops, BitmaskFlagOpNameMap[BITMASK_FLAG_OP_NOT]) + } + if f&BITMASK_FLAG_OP_MATCH > 0 { + ops = append(ops, BitmaskFlagOpNameMap[BITMASK_FLAG_OP_MATCH]) + } + return strings.Join(ops, "") +} + +type FragmentFlag int + +const ( + FRAG_FLAG_NOT FragmentFlag = iota + FRAG_FLAG_DONT = 0x01 + FRAG_FLAG_IS = 0x02 + FRAG_FLAG_FIRST = 0x04 + FRAG_FLAG_LAST = 0x08 +) + +var FragmentFlagNameMap = map[FragmentFlag]string{ + FRAG_FLAG_NOT: "not-a-fragment", + FRAG_FLAG_DONT: "dont-fragment", + FRAG_FLAG_IS: "is-fragment", + FRAG_FLAG_FIRST: "first-fragment", + FRAG_FLAG_LAST: "last-fragment", +} + +// Prepares a sorted list of flags because map iterations does not happen +// in a consistent order in Golang. +var FragmentSortedFlags = []FragmentFlag{ + FRAG_FLAG_NOT, + FRAG_FLAG_DONT, + FRAG_FLAG_IS, + FRAG_FLAG_FIRST, + FRAG_FLAG_LAST, +} + +func (f FragmentFlag) String() string { + flags := make([]string, 0, len(FragmentSortedFlags)) + for _, v := range FragmentSortedFlags { + if f&v > 0 { + flags = append(flags, FragmentFlagNameMap[v]) + } + } + // Note: If multiple bits are set, joins them with "+". + return strings.Join(flags, "+") +} + +type DECNumOp uint8 + +const ( + DEC_NUM_OP_TRUE DECNumOp = iota // true always with END bit set + DEC_NUM_OP_EQ = 0x01 + DEC_NUM_OP_GT = 0x02 + DEC_NUM_OP_GT_EQ = 0x03 + DEC_NUM_OP_LT = 0x04 + DEC_NUM_OP_LT_EQ = 0x05 + DEC_NUM_OP_NOT_EQ = 0x06 + DEC_NUM_OP_FALSE = 0x07 // false always with END bit set + DEC_NUM_OP_OR = 0x00 + DEC_NUM_OP_AND = 0x40 + DEC_NUM_OP_END = 0x80 +) + +var DECNumOpNameMap = map[DECNumOp]string{ + DEC_NUM_OP_TRUE: "true", + DEC_NUM_OP_EQ: "==", + DEC_NUM_OP_GT: ">", + DEC_NUM_OP_GT_EQ: ">=", + DEC_NUM_OP_LT: "<", + DEC_NUM_OP_LT_EQ: "<=", + DEC_NUM_OP_NOT_EQ: "!=", + DEC_NUM_OP_FALSE: "false", + //DEC_NUM_OP_OR: " ", // duplicate with DEC_NUM_OP_TRUE + DEC_NUM_OP_AND: "&", + DEC_NUM_OP_END: "E", +} + +var DECNumOpValueMap = map[string]DECNumOp{ + "true": DEC_NUM_OP_TRUE, + "": DEC_NUM_OP_EQ, + "==": DEC_NUM_OP_EQ, + "=": DEC_NUM_OP_EQ, + ">": DEC_NUM_OP_GT, + ">=": DEC_NUM_OP_GT_EQ, + "<": DEC_NUM_OP_LT, + "<=": DEC_NUM_OP_LT_EQ, + "!=": DEC_NUM_OP_NOT_EQ, + "=!": DEC_NUM_OP_NOT_EQ, + "!": DEC_NUM_OP_NOT_EQ, + "false": DEC_NUM_OP_FALSE, + " ": DEC_NUM_OP_OR, + "&": DEC_NUM_OP_AND, + "E": DEC_NUM_OP_END, +} + +func (f DECNumOp) String() string { + ops := make([]string, 0) + logicFlag := DECNumOp(f & 0xc0) // higher 2 bits + if logicFlag&DEC_NUM_OP_AND > 0 { + ops = append(ops, DECNumOpNameMap[DEC_NUM_OP_AND]) + } else { + ops = append(ops, " ") // DEC_NUM_OP_OR + } + // Omits DEC_NUM_OP_END + cmpFlag := DECNumOp(f & 0x7) // lower 3 bits + for v, s := range DECNumOpNameMap { + if cmpFlag == v { + ops = append(ops, s) + break + } + } + return strings.Join(ops, "") +} + +// Potentially taken from https://www.iana.org/assignments/ieee-802-numbers/ieee-802-numbers.xhtml +type EthernetType int + +const ( + IPv4 EthernetType = 0x0800 + ARP EthernetType = 0x0806 + RARP EthernetType = 0x8035 + VMTP EthernetType = 0x805B + APPLE_TALK EthernetType = 0x809B + AARP EthernetType = 0x80F3 + IPX EthernetType = 0x8137 + SNMP EthernetType = 0x814C + NET_BIOS EthernetType = 0x8191 + XTP EthernetType = 0x817D + IPv6 EthernetType = 0x86DD + PPPoE_DISCOVERY EthernetType = 0x8863 + PPPoE_SESSION EthernetType = 0x8864 + LOOPBACK EthernetType = 0x9000 +) + +var EthernetTypeNameMap = map[EthernetType]string{ + IPv4: "ipv4", + ARP: "arp", + RARP: "rarp", + VMTP: "vmtp", + APPLE_TALK: "apple-talk", + AARP: "aarp", + IPX: "ipx", + SNMP: "snmp", + NET_BIOS: "net-bios", + XTP: "xtp", + IPv6: "ipv6", + PPPoE_DISCOVERY: "pppoe-discovery", + PPPoE_SESSION: "pppoe-session", + LOOPBACK: "loopback", +} + +func (t EthernetType) String() string { + if name, ok := EthernetTypeNameMap[t]; ok { + return name + } + return fmt.Sprintf("%d", t) +} diff --git a/pkg/packet/bgp/esitype_string.go b/pkg/packet/bgp/esitype_string.go new file mode 100644 index 00000000..5651bda8 --- /dev/null +++ b/pkg/packet/bgp/esitype_string.go @@ -0,0 +1,16 @@ +// generated by stringer -type=ESIType bgp.go validate.go; DO NOT EDIT + +package bgp + +import "fmt" + +const _ESIType_name = "ESI_ARBITRARYESI_LACPESI_MSTPESI_MACESI_ROUTERIDESI_AS" + +var _ESIType_index = [...]uint8{0, 13, 21, 29, 36, 48, 54} + +func (i ESIType) String() string { + if i+1 >= ESIType(len(_ESIType_index)) { + return fmt.Sprintf("ESIType(%d)", i) + } + return _ESIType_name[_ESIType_index[i]:_ESIType_index[i+1]] +} diff --git a/pkg/packet/bgp/fsmstate_string.go b/pkg/packet/bgp/fsmstate_string.go new file mode 100644 index 00000000..4416afc1 --- /dev/null +++ b/pkg/packet/bgp/fsmstate_string.go @@ -0,0 +1,16 @@ +// generated by stringer -type=FSMState -output=fsmstate_string.go bgp.go validate.go mrt.go rtr.go constant.go bmp.go esitype_string.go bgpattrtype_string.go; DO NOT EDIT + +package bgp + +import "fmt" + +const _FSMState_name = "BGP_FSM_IDLEBGP_FSM_CONNECTBGP_FSM_ACTIVEBGP_FSM_OPENSENTBGP_FSM_OPENCONFIRMBGP_FSM_ESTABLISHED" + +var _FSMState_index = [...]uint8{0, 12, 27, 41, 57, 76, 95} + +func (i FSMState) String() string { + if i < 0 || i >= FSMState(len(_FSMState_index)-1) { + return fmt.Sprintf("FSMState(%d)", i) + } + return _FSMState_name[_FSMState_index[i]:_FSMState_index[i+1]] +} diff --git a/pkg/packet/bgp/helper.go b/pkg/packet/bgp/helper.go new file mode 100644 index 00000000..34648b2d --- /dev/null +++ b/pkg/packet/bgp/helper.go @@ -0,0 +1,126 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bgp + +func NewTestBGPOpenMessage() *BGPMessage { + p1 := NewOptionParameterCapability( + []ParameterCapabilityInterface{NewCapRouteRefresh()}) + p2 := NewOptionParameterCapability( + []ParameterCapabilityInterface{NewCapMultiProtocol(RF_IPv4_UC)}) + g := &CapGracefulRestartTuple{4, 2, 3} + p3 := NewOptionParameterCapability( + []ParameterCapabilityInterface{NewCapGracefulRestart(false, true, 100, + []*CapGracefulRestartTuple{g})}) + p4 := NewOptionParameterCapability( + []ParameterCapabilityInterface{NewCapFourOctetASNumber(100000)}) + p5 := NewOptionParameterCapability( + []ParameterCapabilityInterface{NewCapAddPath([]*CapAddPathTuple{NewCapAddPathTuple(RF_IPv4_UC, BGP_ADD_PATH_BOTH)})}) + return NewBGPOpenMessage(11033, 303, "100.4.10.3", + []OptionParameterInterface{p1, p2, p3, p4, p5}) +} + +func NewTestBGPUpdateMessage() *BGPMessage { + w1 := NewIPAddrPrefix(23, "121.1.3.2") + w2 := NewIPAddrPrefix(17, "100.33.3.0") + w := []*IPAddrPrefix{w1, w2} + + aspath1 := []AsPathParamInterface{ + NewAsPathParam(2, []uint16{1000}), + NewAsPathParam(1, []uint16{1001, 1002}), + NewAsPathParam(2, []uint16{1003, 1004}), + } + + aspath2 := []AsPathParamInterface{ + NewAs4PathParam(2, []uint32{1000000}), + NewAs4PathParam(1, []uint32{1000001, 1002}), + NewAs4PathParam(2, []uint32{1003, 100004}), + } + + aspath3 := []*As4PathParam{ + NewAs4PathParam(2, []uint32{1000000}), + NewAs4PathParam(1, []uint32{1000001, 1002}), + NewAs4PathParam(2, []uint32{1003, 100004}), + } + + isTransitive := true + + ecommunities := []ExtendedCommunityInterface{ + NewTwoOctetAsSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, 10003, 3<<20, isTransitive), + NewFourOctetAsSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, 1<<20, 300, isTransitive), + NewIPv4AddressSpecificExtended(EC_SUBTYPE_ROUTE_TARGET, "192.2.1.2", 3000, isTransitive), + NewOpaqueExtended(false, []byte{1, 2, 3, 4, 5, 6, 7}), + NewValidationExtended(VALIDATION_STATE_INVALID), + NewUnknownExtended(99, []byte{0, 1, 2, 3, 4, 5, 6, 7}), + NewESILabelExtended(1000, true), + NewESImportRouteTarget("11:22:33:44:55:66"), + NewMacMobilityExtended(123, false), + } + + prefixes1 := []AddrPrefixInterface{ + NewLabeledVPNIPAddrPrefix(24, "192.0.9.0", *NewMPLSLabelStack(1, 2, 3), + NewRouteDistinguisherTwoOctetAS(256, 10000)), + NewLabeledVPNIPAddrPrefix(24, "192.10.8.0", *NewMPLSLabelStack(5, 6, 7, 8), + NewRouteDistinguisherIPAddressAS("10.0.1.1", 10001)), + } + + prefixes2 := []AddrPrefixInterface{NewIPv6AddrPrefix(128, + "fe80:1234:1234:5667:8967:af12:8912:1023")} + + prefixes3 := []AddrPrefixInterface{NewLabeledVPNIPv6AddrPrefix(128, + "fe80:1234:1234:5667:8967:af12:1203:33a1", *NewMPLSLabelStack(5, 6), + NewRouteDistinguisherFourOctetAS(5, 6))} + + prefixes4 := []AddrPrefixInterface{NewLabeledIPAddrPrefix(25, "192.168.0.0", + *NewMPLSLabelStack(5, 6, 7))} + + prefixes5 := []AddrPrefixInterface{ + NewEVPNEthernetAutoDiscoveryRoute(NewRouteDistinguisherFourOctetAS(5, 6), EthernetSegmentIdentifier{ESI_ARBITRARY, make([]byte, 9)}, 2, 2), + NewEVPNMacIPAdvertisementRoute(NewRouteDistinguisherFourOctetAS(5, 6), EthernetSegmentIdentifier{ESI_ARBITRARY, make([]byte, 9)}, 3, "01:23:45:67:89:ab", "192.2.1.2", []uint32{3, 4}), + NewEVPNMulticastEthernetTagRoute(NewRouteDistinguisherFourOctetAS(5, 6), 3, "192.2.1.2"), + NewEVPNEthernetSegmentRoute(NewRouteDistinguisherFourOctetAS(5, 6), EthernetSegmentIdentifier{ESI_ARBITRARY, make([]byte, 9)}, "192.2.1.1"), + NewEVPNIPPrefixRoute(NewRouteDistinguisherFourOctetAS(5, 6), EthernetSegmentIdentifier{ESI_ARBITRARY, make([]byte, 9)}, 5, 24, "192.2.1.0", "192.3.1.1", 5), + } + + p := []PathAttributeInterface{ + NewPathAttributeOrigin(3), + NewPathAttributeAsPath(aspath1), + NewPathAttributeAsPath(aspath2), + NewPathAttributeNextHop("129.1.1.2"), + NewPathAttributeMultiExitDisc(1 << 20), + NewPathAttributeLocalPref(1 << 22), + NewPathAttributeAtomicAggregate(), + NewPathAttributeAggregator(uint16(30002), "129.0.2.99"), + NewPathAttributeAggregator(uint32(30002), "129.0.2.99"), + NewPathAttributeAggregator(uint32(300020), "129.0.2.99"), + NewPathAttributeCommunities([]uint32{1, 3}), + NewPathAttributeOriginatorId("10.10.0.1"), + NewPathAttributeClusterList([]string{"10.10.0.2", "10.10.0.3"}), + NewPathAttributeExtendedCommunities(ecommunities), + NewPathAttributeAs4Path(aspath3), + NewPathAttributeAs4Aggregator(10000, "112.22.2.1"), + NewPathAttributeMpReachNLRI("112.22.2.0", prefixes1), + NewPathAttributeMpReachNLRI("1023::", prefixes2), + NewPathAttributeMpReachNLRI("fe80::", prefixes3), + NewPathAttributeMpReachNLRI("129.1.1.1", prefixes4), + NewPathAttributeMpReachNLRI("129.1.1.1", prefixes5), + NewPathAttributeMpUnreachNLRI(prefixes1), + //NewPathAttributeMpReachNLRI("112.22.2.0", []AddrPrefixInterface{}), + //NewPathAttributeMpUnreachNLRI([]AddrPrefixInterface{}), + NewPathAttributeUnknown(BGP_ATTR_FLAG_TRANSITIVE, 100, []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}), + } + n := []*IPAddrPrefix{NewIPAddrPrefix(24, "13.2.3.1")} + return NewBGPUpdateMessage(w, p, n) +} diff --git a/pkg/packet/bgp/validate.go b/pkg/packet/bgp/validate.go new file mode 100644 index 00000000..60cf26e4 --- /dev/null +++ b/pkg/packet/bgp/validate.go @@ -0,0 +1,337 @@ +package bgp + +import ( + "encoding/binary" + "fmt" + "math" + "net" + "strconv" +) + +// Validator for BGPUpdate +func ValidateUpdateMsg(m *BGPUpdate, rfs map[RouteFamily]BGPAddPathMode, isEBGP bool, isConfed bool) (bool, error) { + var strongestError error + + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCodeAttrList := uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST) + eSubCodeMissing := uint8(BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE) + + if len(m.NLRI) > 0 || len(m.WithdrawnRoutes) > 0 { + if _, ok := rfs[RF_IPv4_UC]; !ok { + return false, NewMessageError(0, 0, nil, fmt.Sprintf("Address-family rf %d not available for session", RF_IPv4_UC)) + } + } + + seen := make(map[BGPAttrType]PathAttributeInterface) + newAttrs := make([]PathAttributeInterface, 0, len(seen)) + // check path attribute + for _, a := range m.PathAttributes { + // check duplication + if _, ok := seen[a.GetType()]; !ok { + seen[a.GetType()] = a + newAttrs = append(newAttrs, a) + //check specific path attribute + ok, err := ValidateAttribute(a, rfs, isEBGP, isConfed) + if !ok { + if err.(*MessageError).ErrorHandling == ERROR_HANDLING_SESSION_RESET { + return false, err + } else if err.(*MessageError).Stronger(strongestError) { + strongestError = err + } + } + } else if a.GetType() == BGP_ATTR_TYPE_MP_REACH_NLRI || a.GetType() == BGP_ATTR_TYPE_MP_UNREACH_NLRI { + eMsg := "the path attribute apears twice. Type : " + strconv.Itoa(int(a.GetType())) + return false, NewMessageError(eCode, eSubCodeAttrList, nil, eMsg) + } else { + eMsg := "the path attribute apears twice. Type : " + strconv.Itoa(int(a.GetType())) + e := NewMessageErrorWithErrorHandling(eCode, eSubCodeAttrList, nil, ERROR_HANDLING_ATTRIBUTE_DISCARD, nil, eMsg) + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + } + } + m.PathAttributes = newAttrs + + if _, ok := seen[BGP_ATTR_TYPE_MP_REACH_NLRI]; ok || len(m.NLRI) > 0 { + // check the existence of well-known mandatory attributes + exist := func(attrs []BGPAttrType) (bool, BGPAttrType) { + for _, attr := range attrs { + _, ok := seen[attr] + if !ok { + return false, attr + } + } + return true, 0 + } + mandatory := []BGPAttrType{BGP_ATTR_TYPE_ORIGIN, BGP_ATTR_TYPE_AS_PATH} + if len(m.NLRI) > 0 { + mandatory = append(mandatory, BGP_ATTR_TYPE_NEXT_HOP) + } + if ok, t := exist(mandatory); !ok { + eMsg := "well-known mandatory attributes are not present. type : " + strconv.Itoa(int(t)) + data := []byte{byte(t)} + e := NewMessageErrorWithErrorHandling(eCode, eSubCodeMissing, data, ERROR_HANDLING_TREAT_AS_WITHDRAW, nil, eMsg) + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + } + } + + return strongestError == nil, strongestError +} + +func ValidateAttribute(a PathAttributeInterface, rfs map[RouteFamily]BGPAddPathMode, isEBGP bool, isConfed bool) (bool, error) { + var strongestError error + + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCodeBadOrigin := uint8(BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE) + eSubCodeBadNextHop := uint8(BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE) + eSubCodeUnknown := uint8(BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE) + eSubCodeMalformedAspath := uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH) + + checkPrefix := func(l []AddrPrefixInterface) error { + for _, prefix := range l { + rf := AfiSafiToRouteFamily(prefix.AFI(), prefix.SAFI()) + if _, ok := rfs[rf]; !ok { + return NewMessageError(0, 0, nil, fmt.Sprintf("Address-family %s not available for this session", rf)) + } + switch rf { + case RF_FS_IPv4_UC, RF_FS_IPv6_UC, RF_FS_IPv4_VPN, RF_FS_IPv6_VPN, RF_FS_L2_VPN: + t := BGPFlowSpecType(0) + value := make([]FlowSpecComponentInterface, 0) + switch rf { + case RF_FS_IPv4_UC: + value = prefix.(*FlowSpecIPv4Unicast).Value + case RF_FS_IPv6_UC: + value = prefix.(*FlowSpecIPv6Unicast).Value + case RF_FS_IPv4_VPN: + value = prefix.(*FlowSpecIPv4VPN).Value + case RF_FS_IPv6_VPN: + value = prefix.(*FlowSpecIPv6VPN).Value + case RF_FS_L2_VPN: + value = prefix.(*FlowSpecL2VPN).Value + } + for _, v := range value { + if v.Type() <= t { + return NewMessageError(0, 0, nil, fmt.Sprintf("%s nlri violate strict type ordering", rf)) + } + t = v.Type() + } + } + } + return nil + } + + switch p := a.(type) { + case *PathAttributeMpUnreachNLRI: + rf := AfiSafiToRouteFamily(p.AFI, p.SAFI) + if _, ok := rfs[rf]; !ok { + return false, NewMessageError(0, 0, nil, fmt.Sprintf("Address-family rf %d not available for session", rf)) + } + if err := checkPrefix(p.Value); err != nil { + return false, err + } + case *PathAttributeMpReachNLRI: + rf := AfiSafiToRouteFamily(p.AFI, p.SAFI) + if _, ok := rfs[rf]; !ok { + return false, NewMessageError(0, 0, nil, fmt.Sprintf("Address-family rf %d not available for session", rf)) + } + if err := checkPrefix(p.Value); err != nil { + return false, err + } + case *PathAttributeOrigin: + v := uint8(p.Value) + if v != BGP_ORIGIN_ATTR_TYPE_IGP && + v != BGP_ORIGIN_ATTR_TYPE_EGP && + v != BGP_ORIGIN_ATTR_TYPE_INCOMPLETE { + data, _ := a.Serialize() + eMsg := "invalid origin attribute. value : " + strconv.Itoa(int(v)) + e := NewMessageErrorWithErrorHandling(eCode, eSubCodeBadOrigin, data, getErrorHandlingFromPathAttribute(p.GetType()), nil, eMsg) + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + } + case *PathAttributeNextHop: + + isZero := func(ip net.IP) bool { + res := ip[0] & 0xff + return res == 0x00 + } + + isClassDorE := func(ip net.IP) bool { + res := ip[0] & 0xe0 + return res == 0xe0 + } + + //check IP address represents host address + if p.Value.IsLoopback() || isZero(p.Value) || isClassDorE(p.Value) { + eMsg := "invalid nexthop address" + data, _ := a.Serialize() + e := NewMessageErrorWithErrorHandling(eCode, eSubCodeBadNextHop, data, getErrorHandlingFromPathAttribute(p.GetType()), nil, eMsg) + if e.(*MessageError).Stronger(strongestError) { + strongestError = e + } + } + case *PathAttributeAsPath: + if isEBGP { + if isConfed { + if segType := p.Value[0].GetType(); segType != BGP_ASPATH_ATTR_TYPE_CONFED_SEQ { + return false, NewMessageError(eCode, eSubCodeMalformedAspath, nil, fmt.Sprintf("segment type is not confederation seq (%d)", segType)) + } + } else { + for _, param := range p.Value { + segType := param.GetType() + switch segType { + case BGP_ASPATH_ATTR_TYPE_CONFED_SET, BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + err := NewMessageErrorWithErrorHandling( + eCode, eSubCodeMalformedAspath, nil, getErrorHandlingFromPathAttribute(p.GetType()), nil, fmt.Sprintf("segment type confederation(%d) found", segType)) + if err.(*MessageError).Stronger(strongestError) { + strongestError = err + } + } + } + } + } + case *PathAttributeLargeCommunities: + uniq := make([]*LargeCommunity, 0, len(p.Values)) + for _, x := range p.Values { + found := false + for _, y := range uniq { + if x.String() == y.String() { + found = true + break + } + } + if !found { + uniq = append(uniq, x) + } + } + p.Values = uniq + + case *PathAttributeUnknown: + if p.GetFlags()&BGP_ATTR_FLAG_OPTIONAL == 0 { + eMsg := fmt.Sprintf("unrecognized well-known attribute %s", p.GetType()) + data, _ := a.Serialize() + return false, NewMessageError(eCode, eSubCodeUnknown, data, eMsg) + } + } + + return strongestError == nil, strongestError +} + +// validator for PathAttribute +func validatePathAttributeFlags(t BGPAttrType, flags BGPAttrFlag) string { + + /* + * RFC 4271 P.17 For well-known attributes, the Transitive bit MUST be set to 1. + */ + if flags&BGP_ATTR_FLAG_OPTIONAL == 0 && flags&BGP_ATTR_FLAG_TRANSITIVE == 0 { + eMsg := fmt.Sprintf("well-known attribute %s must have transitive flag 1", t) + return eMsg + } + /* + * RFC 4271 P.17 For well-known attributes and for optional non-transitive attributes, + * the Partial bit MUST be set to 0. + */ + if flags&BGP_ATTR_FLAG_OPTIONAL == 0 && flags&BGP_ATTR_FLAG_PARTIAL != 0 { + eMsg := fmt.Sprintf("well-known attribute %s must have partial bit 0", t) + return eMsg + } + if flags&BGP_ATTR_FLAG_OPTIONAL != 0 && flags&BGP_ATTR_FLAG_TRANSITIVE == 0 && flags&BGP_ATTR_FLAG_PARTIAL != 0 { + eMsg := fmt.Sprintf("optional non-transitive attribute %s must have partial bit 0", t) + return eMsg + } + + // check flags are correct + if f, ok := PathAttrFlags[t]; ok { + if f != flags & ^BGP_ATTR_FLAG_EXTENDED_LENGTH & ^BGP_ATTR_FLAG_PARTIAL { + eMsg := fmt.Sprintf("flags are invalid. attribute type: %s, expect: %s, actual: %s", t, f, flags) + return eMsg + } + } + return "" +} + +func validateAsPathValueBytes(data []byte) (bool, error) { + eCode := uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR) + eSubCode := uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH) + if len(data)%2 != 0 { + return false, NewMessageError(eCode, eSubCode, nil, "AS PATH length is not odd") + } + + tryParse := func(data []byte, use4byte bool) (bool, error) { + for len(data) > 0 { + if len(data) < 2 { + return false, NewMessageError(eCode, eSubCode, nil, "AS PATH header is short") + } + segType := data[0] + if segType == 0 || segType > 4 { + return false, NewMessageError(eCode, eSubCode, nil, "unknown AS_PATH seg type") + } + asNum := data[1] + data = data[2:] + if asNum == 0 || int(asNum) > math.MaxUint8 { + return false, NewMessageError(eCode, eSubCode, nil, "AS PATH the number of AS is incorrect") + } + segLength := int(asNum) + if use4byte { + segLength *= 4 + } else { + segLength *= 2 + } + if int(segLength) > len(data) { + return false, NewMessageError(eCode, eSubCode, nil, "seg length is short") + } + data = data[segLength:] + } + return true, nil + } + _, err := tryParse(data, true) + if err == nil { + return true, nil + } + + _, err = tryParse(data, false) + if err == nil { + return false, nil + } + return false, NewMessageError(eCode, eSubCode, nil, "can't parse AS_PATH") +} + +func ValidateBGPMessage(m *BGPMessage) error { + if m.Header.Len > BGP_MAX_MESSAGE_LENGTH { + buf := make([]byte, 2) + binary.BigEndian.PutUint16(buf, m.Header.Len) + return NewMessageError(BGP_ERROR_MESSAGE_HEADER_ERROR, BGP_ERROR_SUB_BAD_MESSAGE_LENGTH, buf, "too long length") + } + + return nil +} + +func ValidateOpenMsg(m *BGPOpen, expectedAS uint32) (uint32, error) { + if m.Version != 4 { + return 0, NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNSUPPORTED_VERSION_NUMBER, nil, fmt.Sprintf("unsupported version %d", m.Version)) + } + + as := uint32(m.MyAS) + for _, p := range m.OptParams { + paramCap, y := p.(*OptionParameterCapability) + if !y { + continue + } + for _, c := range paramCap.Capability { + if c.Code() == BGP_CAP_FOUR_OCTET_AS_NUMBER { + cap := c.(*CapFourOctetASNumber) + as = cap.CapValue + } + } + } + if expectedAS != 0 && as != expectedAS { + return 0, NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_BAD_PEER_AS, nil, fmt.Sprintf("as number mismatch expected %d, received %d", expectedAS, as)) + } + + if m.HoldTime < 3 && m.HoldTime != 0 { + return 0, NewMessageError(BGP_ERROR_OPEN_MESSAGE_ERROR, BGP_ERROR_SUB_UNACCEPTABLE_HOLD_TIME, nil, fmt.Sprintf("unacceptable hold time %d", m.HoldTime)) + } + return as, nil +} diff --git a/pkg/packet/bgp/validate_test.go b/pkg/packet/bgp/validate_test.go new file mode 100644 index 00000000..8bdec550 --- /dev/null +++ b/pkg/packet/bgp/validate_test.go @@ -0,0 +1,423 @@ +package bgp + +import ( + "encoding/binary" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func bgpupdate() *BGPMessage { + aspath := []AsPathParamInterface{ + NewAsPathParam(2, []uint16{65001}), + } + + p := []PathAttributeInterface{ + NewPathAttributeOrigin(1), + NewPathAttributeAsPath(aspath), + NewPathAttributeNextHop("192.168.1.1"), + } + + n := []*IPAddrPrefix{NewIPAddrPrefix(24, "10.10.10.0")} + return NewBGPUpdateMessage(nil, p, n) +} + +func bgpupdateV6() *BGPMessage { + aspath := []AsPathParamInterface{ + NewAsPathParam(2, []uint16{65001}), + } + + prefixes := []AddrPrefixInterface{NewIPv6AddrPrefix(100, + "fe80:1234:1234:5667:8967:af12:8912:1023")} + + p := []PathAttributeInterface{ + NewPathAttributeOrigin(1), + NewPathAttributeAsPath(aspath), + NewPathAttributeMpReachNLRI("1023::", prefixes), + } + return NewBGPUpdateMessage(nil, p, nil) +} + +func Test_Validate_CapV4(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv6_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + + res, err = ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + require.NoError(t, err) + assert.Equal(true, res) +} + +func Test_Validate_CapV6(t *testing.T) { + assert := assert.New(t) + message := bgpupdateV6().Body.(*BGPUpdate) + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv6_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.NoError(err) + assert.True(res) + + res, err = ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Error(err) + assert.False(res) +} + +func Test_Validate_OK(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(true, res) + assert.NoError(err) + +} + +// func Test_Validate_wellknown_but_nontransitive(t *testing.T) { +// assert := assert.New(t) +// message := bgpupdate().Body.(*BGPUpdate) + +// originBytes := []byte{0, 1, 1, 1} // 0 means Flags +// origin := &PathAttributeOrigin{} +// origin.DecodeFromBytes(originBytes) +// message.PathAttributes[0] = origin + +// res, err := ValidateUpdateMsg(message, []RouteFamily{RF_IPv4_UC,}) +// assert.Equal(false, res) +// assert.Error(err) +// e := err.(*MessageError) +// assert.Equal(BGP_ERROR_UPDATE_MESSAGE_ERROR, e.TypeCode) +// assert.Equal(BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR, e.SubTypeCode) +// assert.Equal(originBytes, e.Data) +// } + +// func Test_Validate_wellknown_but_partial(t *testing.T) { +// assert := assert.New(t) +// message := bgpupdate().Body.(*BGPUpdate) + +// originBytes := []byte{BGP_ATTR_FLAG_PARTIAL, 1, 1, 1} +// origin := &PathAttributeOrigin{} +// origin.DecodeFromBytes(originBytes) +// message.PathAttributes[0] = origin + +// res, err := ValidateUpdateMsg(message, []RouteFamily{RF_IPv4_UC,}) +// assert.Equal(false, res) +// assert.Error(err) +// e := err.(*MessageError) +// assert.Equal(BGP_ERROR_UPDATE_MESSAGE_ERROR, e.TypeCode) +// assert.Equal(BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR, e.SubTypeCode) +// assert.Equal(originBytes, e.Data) +// } + +// func Test_Validate_optional_nontransitive_but_partial(t *testing.T) { +// assert := assert.New(t) +// message := bgpupdate().Body.(*BGPUpdate) +// f := BGP_ATTR_FLAG_OPTIONAL | BGP_ATTR_FLAG_PARTIAL +// originBytes := []byte{byte(f), 1, 1, 1} +// origin := &PathAttributeOrigin{} +// origin.DecodeFromBytes(originBytes) +// message.PathAttributes[0] = origin + +// res, err := ValidateUpdateMsg(message, []RouteFamily{RF_IPv4_UC,}) +// assert.Equal(false, res) +// assert.Error(err) +// e := err.(*MessageError) +// assert.Equal(BGP_ERROR_UPDATE_MESSAGE_ERROR, e.TypeCode) +// assert.Equal(BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR, e.SubTypeCode) +// assert.Equal(originBytes, e.Data) +// } + +// func Test_Validate_flag_mismatch(t *testing.T) { +// assert := assert.New(t) +// message := bgpupdate().Body.(*BGPUpdate) +// f := BGP_ATTR_FLAG_OPTIONAL +// // origin needs to be well-known +// originBytes := []byte{byte(f), 1, 1, 1} +// origin := &PathAttributeOrigin{} +// origin.DecodeFromBytes(originBytes) +// message.PathAttributes[0] = origin + +// res, err := ValidateUpdateMsg(message, []RouteFamily{RF_IPv4_UC,}) +// assert.Equal(false, res) +// assert.Error(err) +// e := err.(*MessageError) +// assert.Equal(BGP_ERROR_UPDATE_MESSAGE_ERROR, e.TypeCode) +// assert.Equal(BGP_ERROR_SUB_ATTRIBUTE_FLAGS_ERROR, e.SubTypeCode) +// assert.Equal(originBytes, e.Data) +// } + +func Test_Validate_duplicate_attribute(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + // duplicate origin path attribute + originBytes := []byte{byte(PathAttrFlags[BGP_ATTR_TYPE_ORIGIN]), 1, 1, 1} + origin := &PathAttributeOrigin{} + origin.DecodeFromBytes(originBytes) + message.PathAttributes = append(message.PathAttributes, origin) + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_MALFORMED_ATTRIBUTE_LIST), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_ATTRIBUTE_DISCARD, e.ErrorHandling) + assert.Nil(e.Data) +} + +func Test_Validate_mandatory_missing(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + message.PathAttributes = message.PathAttributes[1:] + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_MISSING_WELL_KNOWN_ATTRIBUTE), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + missing, _ := binary.Uvarint(e.Data) + assert.Equal(uint64(1), missing) +} + +func Test_Validate_mandatory_missing_nocheck(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + message.PathAttributes = message.PathAttributes[1:] + message.NLRI = nil + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(true, res) + assert.NoError(err) +} + +func Test_Validate_invalid_origin(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + // origin needs to be well-known + originBytes := []byte{byte(PathAttrFlags[BGP_ATTR_TYPE_ORIGIN]), 1, 1, 5} + origin := &PathAttributeOrigin{} + origin.DecodeFromBytes(originBytes) + message.PathAttributes[0] = origin + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_INVALID_ORIGIN_ATTRIBUTE), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + assert.Equal(originBytes, e.Data) +} + +func Test_Validate_invalid_nexthop_zero(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + + // invalid nexthop + addr := net.ParseIP("0.0.0.1").To4() + nexthopBytes := []byte{byte(PathAttrFlags[BGP_ATTR_TYPE_NEXT_HOP]), 3, 4} + nexthopBytes = append(nexthopBytes, addr...) + nexthop := &PathAttributeNextHop{} + nexthop.DecodeFromBytes(nexthopBytes) + message.PathAttributes[2] = nexthop + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + assert.Equal(nexthopBytes, e.Data) +} + +func Test_Validate_invalid_nexthop_lo(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + + // invalid nexthop + addr := net.ParseIP("127.0.0.1").To4() + nexthopBytes := []byte{byte(PathAttrFlags[BGP_ATTR_TYPE_NEXT_HOP]), 3, 4} + nexthopBytes = append(nexthopBytes, addr...) + nexthop := &PathAttributeNextHop{} + nexthop.DecodeFromBytes(nexthopBytes) + message.PathAttributes[2] = nexthop + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + assert.Equal(nexthopBytes, e.Data) +} + +func Test_Validate_invalid_nexthop_de(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + + // invalid nexthop + addr := net.ParseIP("224.0.0.1").To4() + nexthopBytes := []byte{byte(PathAttrFlags[BGP_ATTR_TYPE_NEXT_HOP]), 3, 4} + nexthopBytes = append(nexthopBytes, addr...) + nexthop := &PathAttributeNextHop{} + nexthop.DecodeFromBytes(nexthopBytes) + message.PathAttributes[2] = nexthop + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_INVALID_NEXT_HOP_ATTRIBUTE), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + assert.Equal(nexthopBytes, e.Data) + +} + +func Test_Validate_unrecognized_well_known(t *testing.T) { + + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + f := BGP_ATTR_FLAG_TRANSITIVE + unknownBytes := []byte{byte(f), 30, 1, 1} + unknown := &PathAttributeUnknown{} + unknown.DecodeFromBytes(unknownBytes) + message.PathAttributes = append(message.PathAttributes, unknown) + + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, false, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_UNRECOGNIZED_WELL_KNOWN_ATTRIBUTE), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_SESSION_RESET, e.ErrorHandling) + assert.Equal(unknownBytes, e.Data) +} + +func Test_Validate_aspath(t *testing.T) { + assert := assert.New(t) + message := bgpupdate().Body.(*BGPUpdate) + + // VALID AS_PATH + res, err := ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, true, false) + require.NoError(t, err) + assert.Equal(true, res) + + // CONFED_SET + newAttrs := make([]PathAttributeInterface, 0) + attrs := message.PathAttributes + for _, attr := range attrs { + if _, y := attr.(*PathAttributeAsPath); y { + aspath := []AsPathParamInterface{ + NewAsPathParam(BGP_ASPATH_ATTR_TYPE_CONFED_SET, []uint16{65001}), + } + newAttrs = append(newAttrs, NewPathAttributeAsPath(aspath)) + } else { + newAttrs = append(newAttrs, attr) + } + } + + message.PathAttributes = newAttrs + res, err = ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, true, false) + assert.Equal(false, res) + assert.Error(err) + e := err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + assert.Nil(e.Data) + + res, err = ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, true, true) + assert.Equal(false, res) + assert.Error(err) + e = err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH), e.SubTypeCode) + assert.Nil(e.Data) + + // CONFED_SEQ + newAttrs = make([]PathAttributeInterface, 0) + attrs = message.PathAttributes + for _, attr := range attrs { + if _, y := attr.(*PathAttributeAsPath); y { + aspath := []AsPathParamInterface{ + NewAsPathParam(BGP_ASPATH_ATTR_TYPE_CONFED_SEQ, []uint16{65001}), + } + newAttrs = append(newAttrs, NewPathAttributeAsPath(aspath)) + } else { + newAttrs = append(newAttrs, attr) + } + } + + message.PathAttributes = newAttrs + res, err = ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, true, false) + assert.Equal(false, res) + assert.Error(err) + e = err.(*MessageError) + assert.Equal(uint8(BGP_ERROR_UPDATE_MESSAGE_ERROR), e.TypeCode) + assert.Equal(uint8(BGP_ERROR_SUB_MALFORMED_AS_PATH), e.SubTypeCode) + assert.Equal(ERROR_HANDLING_TREAT_AS_WITHDRAW, e.ErrorHandling) + assert.Nil(e.Data) + + res, err = ValidateUpdateMsg(message, map[RouteFamily]BGPAddPathMode{RF_IPv4_UC: BGP_ADD_PATH_BOTH}, true, true) + require.NoError(t, err) + assert.Equal(true, res) +} + +func Test_Validate_flowspec(t *testing.T) { + assert := assert.New(t) + cmp := make([]FlowSpecComponentInterface, 0) + cmp = append(cmp, NewFlowSpecDestinationPrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + cmp = append(cmp, NewFlowSpecSourcePrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + item1 := NewFlowSpecComponentItem(DEC_NUM_OP_EQ, TCP) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_IP_PROTO, []*FlowSpecComponentItem{item1})) + item2 := NewFlowSpecComponentItem(DEC_NUM_OP_GT_EQ, 20) + item3 := NewFlowSpecComponentItem(DEC_NUM_OP_AND|DEC_NUM_OP_LT_EQ, 30) + item4 := NewFlowSpecComponentItem(DEC_NUM_OP_EQ, 10) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_DST_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_SRC_PORT, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ICMP_TYPE, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_ICMP_CODE, []*FlowSpecComponentItem{item2, item3, item4})) + item5 := NewFlowSpecComponentItem(0, TCP_FLAG_ACK) + item6 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_AND|BITMASK_FLAG_OP_NOT, TCP_FLAG_URGENT) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_TCP_FLAG, []*FlowSpecComponentItem{item5, item6})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_PKT_LEN, []*FlowSpecComponentItem{item2, item3, item4})) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_DSCP, []*FlowSpecComponentItem{item2, item3, item4})) + isFragment := uint64(0x02) + item7 := NewFlowSpecComponentItem(BITMASK_FLAG_OP_MATCH, isFragment) + cmp = append(cmp, NewFlowSpecComponent(FLOW_SPEC_TYPE_FRAGMENT, []*FlowSpecComponentItem{item7})) + n1 := NewFlowSpecIPv4Unicast(cmp) + a := NewPathAttributeMpReachNLRI("", []AddrPrefixInterface{n1}) + m := map[RouteFamily]BGPAddPathMode{RF_FS_IPv4_UC: BGP_ADD_PATH_NONE} + _, err := ValidateAttribute(a, m, false, false) + assert.Nil(err) + + cmp = make([]FlowSpecComponentInterface, 0) + cmp = append(cmp, NewFlowSpecSourcePrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + cmp = append(cmp, NewFlowSpecDestinationPrefix(NewIPAddrPrefix(24, "10.0.0.0"))) + n1 = NewFlowSpecIPv4Unicast(cmp) + a = NewPathAttributeMpReachNLRI("", []AddrPrefixInterface{n1}) + // Swaps components order to reproduce the rules order violation. + n1.Value[0], n1.Value[1] = n1.Value[1], n1.Value[0] + _, err = ValidateAttribute(a, m, false, false) + assert.NotNil(err) +} + +func TestValidateLargeCommunities(t *testing.T) { + assert := assert.New(t) + c1, err := ParseLargeCommunity("10:10:10") + assert.Nil(err) + c2, err := ParseLargeCommunity("10:10:10") + assert.Nil(err) + c3, err := ParseLargeCommunity("10:10:20") + assert.Nil(err) + a := NewPathAttributeLargeCommunities([]*LargeCommunity{c1, c2, c3}) + assert.True(len(a.Values) == 3) + _, err = ValidateAttribute(a, nil, false, false) + assert.Nil(err) + assert.True(len(a.Values) == 2) +} diff --git a/pkg/packet/bmp/bmp.go b/pkg/packet/bmp/bmp.go new file mode 100644 index 00000000..535ef4b0 --- /dev/null +++ b/pkg/packet/bmp/bmp.go @@ -0,0 +1,1072 @@ +// Copyright (C) 2014,2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bmp + +import ( + "encoding/binary" + "fmt" + "math" + "net" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +type BMPHeader struct { + Version uint8 + Length uint32 + Type uint8 +} + +const ( + BMP_VERSION = 3 + BMP_HEADER_SIZE = 6 + BMP_PEER_HEADER_SIZE = 42 +) + +const ( + BMP_DEFAULT_PORT = 11019 +) + +const ( + BMP_PEER_TYPE_GLOBAL uint8 = iota + BMP_PEER_TYPE_L3VPN + BMP_PEER_TYPE_LOCAL + BMP_PEER_TYPE_LOCAL_RIB +) + +const ( + BMP_PEER_FLAG_IPV6 = 1 << 7 + BMP_PEER_FLAG_POST_POLICY = 1 << 6 + BMP_PEER_FLAG_TWO_AS = 1 << 5 + BMP_PEER_FLAG_FILTERED = 1 << 6 +) + +func (h *BMPHeader) DecodeFromBytes(data []byte) error { + h.Version = data[0] + if data[0] != BMP_VERSION { + return fmt.Errorf("error version") + } + h.Length = binary.BigEndian.Uint32(data[1:5]) + h.Type = data[5] + return nil +} + +func (h *BMPHeader) Serialize() ([]byte, error) { + buf := make([]byte, BMP_HEADER_SIZE) + buf[0] = h.Version + binary.BigEndian.PutUint32(buf[1:], h.Length) + buf[5] = h.Type + return buf, nil +} + +type BMPPeerHeader struct { + PeerType uint8 + Flags uint8 + PeerDistinguisher uint64 + PeerAddress net.IP + PeerAS uint32 + PeerBGPID net.IP + Timestamp float64 +} + +func NewBMPPeerHeader(t uint8, flags uint8, dist uint64, address string, as uint32, id string, stamp float64) *BMPPeerHeader { + h := &BMPPeerHeader{ + PeerType: t, + Flags: flags, + PeerDistinguisher: dist, + PeerAS: as, + PeerBGPID: net.ParseIP(id).To4(), + Timestamp: stamp, + } + if net.ParseIP(address).To4() != nil { + h.PeerAddress = net.ParseIP(address).To4() + } else { + h.PeerAddress = net.ParseIP(address).To16() + h.Flags |= BMP_PEER_FLAG_IPV6 + } + return h +} + +func (h *BMPPeerHeader) IsPostPolicy() bool { + if h.Flags&BMP_PEER_FLAG_POST_POLICY != 0 { + return true + } else { + return false + } +} + +func (h *BMPPeerHeader) DecodeFromBytes(data []byte) error { + h.PeerType = data[0] + h.Flags = data[1] + h.PeerDistinguisher = binary.BigEndian.Uint64(data[2:10]) + if h.Flags&BMP_PEER_FLAG_IPV6 != 0 { + h.PeerAddress = net.IP(data[10:26]).To16() + } else { + h.PeerAddress = net.IP(data[22:26]).To4() + } + h.PeerAS = binary.BigEndian.Uint32(data[26:30]) + h.PeerBGPID = data[30:34] + + timestamp1 := binary.BigEndian.Uint32(data[34:38]) + timestamp2 := binary.BigEndian.Uint32(data[38:42]) + h.Timestamp = float64(timestamp1) + float64(timestamp2)*math.Pow10(-6) + return nil +} + +func (h *BMPPeerHeader) Serialize() ([]byte, error) { + buf := make([]byte, BMP_PEER_HEADER_SIZE) + buf[0] = h.PeerType + buf[1] = h.Flags + binary.BigEndian.PutUint64(buf[2:10], h.PeerDistinguisher) + if h.Flags&BMP_PEER_FLAG_IPV6 != 0 { + copy(buf[10:26], h.PeerAddress) + } else { + copy(buf[22:26], h.PeerAddress.To4()) + } + binary.BigEndian.PutUint32(buf[26:30], h.PeerAS) + copy(buf[30:34], h.PeerBGPID) + t1, t2 := math.Modf(h.Timestamp) + t2 = math.Ceil(t2 * math.Pow10(6)) + binary.BigEndian.PutUint32(buf[34:38], uint32(t1)) + binary.BigEndian.PutUint32(buf[38:42], uint32(t2)) + return buf, nil +} + +type BMPRouteMonitoring struct { + BGPUpdate *bgp.BGPMessage + BGPUpdatePayload []byte +} + +func NewBMPRouteMonitoring(p BMPPeerHeader, update *bgp.BGPMessage) *BMPMessage { + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_ROUTE_MONITORING, + }, + PeerHeader: p, + Body: &BMPRouteMonitoring{ + BGPUpdate: update, + }, + } +} + +func (body *BMPRouteMonitoring) ParseBody(msg *BMPMessage, data []byte) error { + update, err := bgp.ParseBGPMessage(data) + if err != nil { + return err + } + body.BGPUpdate = update + return nil +} + +func (body *BMPRouteMonitoring) Serialize() ([]byte, error) { + if body.BGPUpdatePayload != nil { + return body.BGPUpdatePayload, nil + } + return body.BGPUpdate.Serialize() +} + +const ( + BMP_STAT_TYPE_REJECTED = iota + BMP_STAT_TYPE_DUPLICATE_PREFIX + BMP_STAT_TYPE_DUPLICATE_WITHDRAW + BMP_STAT_TYPE_INV_UPDATE_DUE_TO_CLUSTER_LIST_LOOP + BMP_STAT_TYPE_INV_UPDATE_DUE_TO_AS_PATH_LOOP + BMP_STAT_TYPE_INV_UPDATE_DUE_TO_ORIGINATOR_ID + BMP_STAT_TYPE_INV_UPDATE_DUE_TO_AS_CONFED_LOOP + BMP_STAT_TYPE_ADJ_RIB_IN + BMP_STAT_TYPE_LOC_RIB + BMP_STAT_TYPE_PER_AFI_SAFI_ADJ_RIB_IN + BMP_STAT_TYPE_PER_AFI_SAFI_LOC_RIB + BMP_STAT_TYPE_WITHDRAW_UPDATE + BMP_STAT_TYPE_WITHDRAW_PREFIX + BMP_STAT_TYPE_DUPLICATE_UPDATE +) + +type BMPStatsTLVInterface interface { + ParseValue([]byte) error + Serialize() ([]byte, error) +} + +type BMPStatsTLV struct { + Type uint16 + Length uint16 +} + +type BMPStatsTLV32 struct { + BMPStatsTLV + Value uint32 +} + +func NewBMPStatsTLV32(t uint16, v uint32) *BMPStatsTLV32 { + return &BMPStatsTLV32{ + BMPStatsTLV: BMPStatsTLV{ + Type: t, + Length: 4, + }, + Value: v, + } +} + +func (s *BMPStatsTLV32) ParseValue(data []byte) error { + if s.Length != 4 { + return fmt.Errorf("invalid length: %d bytes (%d bytes expected)", s.Length, 4) + } + s.Value = binary.BigEndian.Uint32(data[:8]) + return nil +} + +func (s *BMPStatsTLV32) Serialize() ([]byte, error) { + buf := make([]byte, 8) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], 4) + binary.BigEndian.PutUint32(buf[4:8], s.Value) + return buf, nil +} + +type BMPStatsTLV64 struct { + BMPStatsTLV + Value uint64 +} + +func NewBMPStatsTLV64(t uint16, v uint64) *BMPStatsTLV64 { + return &BMPStatsTLV64{ + BMPStatsTLV: BMPStatsTLV{ + Type: t, + Length: 8, + }, + Value: v, + } +} + +func (s *BMPStatsTLV64) ParseValue(data []byte) error { + if s.Length != 8 { + return fmt.Errorf("invalid length: %d bytes (%d bytes expected)", s.Length, 8) + } + s.Value = binary.BigEndian.Uint64(data[:8]) + return nil +} + +func (s *BMPStatsTLV64) Serialize() ([]byte, error) { + buf := make([]byte, 12) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], 8) + binary.BigEndian.PutUint64(buf[4:12], s.Value) + return buf, nil +} + +type BMPStatsTLVPerAfiSafi64 struct { + BMPStatsTLV + AFI uint16 + SAFI uint8 + Value uint64 +} + +func NewBMPStatsTLVPerAfiSafi64(t uint16, afi uint16, safi uint8, v uint64) *BMPStatsTLVPerAfiSafi64 { + return &BMPStatsTLVPerAfiSafi64{ + BMPStatsTLV: BMPStatsTLV{ + Type: t, + Length: 11, + }, + AFI: afi, + SAFI: safi, + Value: v, + } +} + +func (s *BMPStatsTLVPerAfiSafi64) ParseValue(data []byte) error { + if s.Length != 11 { + return fmt.Errorf("invalid length: %d bytes (%d bytes expected)", s.Length, 11) + } + s.AFI = binary.BigEndian.Uint16(data[0:2]) + s.SAFI = data[2] + s.Value = binary.BigEndian.Uint64(data[3:11]) + return nil +} + +func (s *BMPStatsTLVPerAfiSafi64) Serialize() ([]byte, error) { + buf := make([]byte, 15) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], 11) + binary.BigEndian.PutUint16(buf[4:6], s.AFI) + buf[6] = s.SAFI + binary.BigEndian.PutUint64(buf[7:15], s.Value) + return buf, nil +} + +type BMPStatisticsReport struct { + Count uint32 + Stats []BMPStatsTLVInterface +} + +func NewBMPStatisticsReport(p BMPPeerHeader, stats []BMPStatsTLVInterface) *BMPMessage { + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_STATISTICS_REPORT, + }, + PeerHeader: p, + Body: &BMPStatisticsReport{ + Count: uint32(len(stats)), + Stats: stats, + }, + } +} + +func (body *BMPStatisticsReport) ParseBody(msg *BMPMessage, data []byte) error { + body.Count = binary.BigEndian.Uint32(data[0:4]) + data = data[4:] + for len(data) >= 4 { + tl := BMPStatsTLV{ + Type: binary.BigEndian.Uint16(data[0:2]), + Length: binary.BigEndian.Uint16(data[2:4]), + } + data = data[4:] + if len(data) < int(tl.Length) { + return fmt.Errorf("value length is not enough: %d bytes (%d bytes expected)", len(data), tl.Length) + } + var s BMPStatsTLVInterface + switch tl.Type { + case BMP_STAT_TYPE_ADJ_RIB_IN, BMP_STAT_TYPE_LOC_RIB: + s = &BMPStatsTLV64{BMPStatsTLV: tl} + case BMP_STAT_TYPE_PER_AFI_SAFI_ADJ_RIB_IN, BMP_STAT_TYPE_PER_AFI_SAFI_LOC_RIB: + s = &BMPStatsTLVPerAfiSafi64{BMPStatsTLV: tl} + default: + s = &BMPStatsTLV32{BMPStatsTLV: tl} + } + if err := s.ParseValue(data); err != nil { + return err + } + body.Stats = append(body.Stats, s) + data = data[tl.Length:] + } + return nil +} + +func (body *BMPStatisticsReport) Serialize() ([]byte, error) { + buf := make([]byte, 4) + body.Count = uint32(len(body.Stats)) + binary.BigEndian.PutUint32(buf[0:4], body.Count) + for _, tlv := range body.Stats { + tlvBuf, err := tlv.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, tlvBuf...) + } + return buf, nil +} + +const ( + BMP_PEER_DOWN_REASON_UNKNOWN = iota + BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION + BMP_PEER_DOWN_REASON_LOCAL_NO_NOTIFICATION + BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION + BMP_PEER_DOWN_REASON_REMOTE_NO_NOTIFICATION + BMP_PEER_DOWN_REASON_PEER_DE_CONFIGURED +) + +type BMPPeerDownNotification struct { + Reason uint8 + BGPNotification *bgp.BGPMessage + Data []byte +} + +func NewBMPPeerDownNotification(p BMPPeerHeader, reason uint8, notification *bgp.BGPMessage, data []byte) *BMPMessage { + b := &BMPPeerDownNotification{ + Reason: reason, + } + switch reason { + case BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION, BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION: + b.BGPNotification = notification + case BMP_PEER_DOWN_REASON_LOCAL_NO_NOTIFICATION: + b.Data = data + default: + } + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_PEER_DOWN_NOTIFICATION, + }, + PeerHeader: p, + Body: b, + } +} + +func (body *BMPPeerDownNotification) ParseBody(msg *BMPMessage, data []byte) error { + body.Reason = data[0] + data = data[1:] + if body.Reason == BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION || body.Reason == BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION { + notification, err := bgp.ParseBGPMessage(data) + if err != nil { + return err + } + body.BGPNotification = notification + } else { + body.Data = data + } + return nil +} + +func (body *BMPPeerDownNotification) Serialize() ([]byte, error) { + buf := make([]byte, 1) + buf[0] = body.Reason + switch body.Reason { + case BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION, BMP_PEER_DOWN_REASON_REMOTE_BGP_NOTIFICATION: + if body.BGPNotification != nil { + b, err := body.BGPNotification.Serialize() + if err != nil { + return nil, err + } else { + buf = append(buf, b...) + } + } + default: + if body.Data != nil { + buf = append(buf, body.Data...) + } + } + return buf, nil +} + +type BMPPeerUpNotification struct { + LocalAddress net.IP + LocalPort uint16 + RemotePort uint16 + SentOpenMsg *bgp.BGPMessage + ReceivedOpenMsg *bgp.BGPMessage +} + +func NewBMPPeerUpNotification(p BMPPeerHeader, lAddr string, lPort, rPort uint16, sent, recv *bgp.BGPMessage) *BMPMessage { + b := &BMPPeerUpNotification{ + LocalPort: lPort, + RemotePort: rPort, + SentOpenMsg: sent, + ReceivedOpenMsg: recv, + } + addr := net.ParseIP(lAddr) + if addr.To4() != nil { + b.LocalAddress = addr.To4() + } else { + b.LocalAddress = addr.To16() + } + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_PEER_UP_NOTIFICATION, + }, + PeerHeader: p, + Body: b, + } +} + +func (body *BMPPeerUpNotification) ParseBody(msg *BMPMessage, data []byte) error { + if msg.PeerHeader.Flags&BMP_PEER_FLAG_IPV6 != 0 { + body.LocalAddress = net.IP(data[:16]).To16() + } else { + body.LocalAddress = net.IP(data[12:16]).To4() + } + + body.LocalPort = binary.BigEndian.Uint16(data[16:18]) + body.RemotePort = binary.BigEndian.Uint16(data[18:20]) + + data = data[20:] + sentopen, err := bgp.ParseBGPMessage(data) + if err != nil { + return err + } + body.SentOpenMsg = sentopen + data = data[body.SentOpenMsg.Header.Len:] + body.ReceivedOpenMsg, err = bgp.ParseBGPMessage(data) + if err != nil { + return err + } + return nil +} + +func (body *BMPPeerUpNotification) Serialize() ([]byte, error) { + buf := make([]byte, 20) + if body.LocalAddress.To4() != nil { + copy(buf[12:16], body.LocalAddress.To4()) + } else { + copy(buf[:16], body.LocalAddress.To16()) + } + + binary.BigEndian.PutUint16(buf[16:18], body.LocalPort) + binary.BigEndian.PutUint16(buf[18:20], body.RemotePort) + + m, _ := body.SentOpenMsg.Serialize() + buf = append(buf, m...) + m, _ = body.ReceivedOpenMsg.Serialize() + buf = append(buf, m...) + return buf, nil +} + +const ( + BMP_INIT_TLV_TYPE_STRING = iota + BMP_INIT_TLV_TYPE_SYS_DESCR + BMP_INIT_TLV_TYPE_SYS_NAME +) + +type BMPInfoTLVInterface interface { + ParseValue([]byte) error + Serialize() ([]byte, error) +} + +type BMPInfoTLV struct { + Type uint16 + Length uint16 +} + +type BMPInfoTLVString struct { + BMPInfoTLV + Value string +} + +func NewBMPInfoTLVString(t uint16, v string) *BMPInfoTLVString { + return &BMPInfoTLVString{ + BMPInfoTLV: BMPInfoTLV{Type: t}, + Value: v, + } +} + +func (s *BMPInfoTLVString) ParseValue(data []byte) error { + s.Value = string(data[:s.Length]) + return nil +} + +func (s *BMPInfoTLVString) Serialize() ([]byte, error) { + s.Length = uint16(len([]byte(s.Value))) + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + buf = append(buf, []byte(s.Value)...) + return buf, nil +} + +type BMPInfoTLVUnknown struct { + BMPInfoTLV + Value []byte +} + +func NewBMPInfoTLVUnknown(t uint16, v []byte) *BMPInfoTLVUnknown { + return &BMPInfoTLVUnknown{ + BMPInfoTLV: BMPInfoTLV{Type: t}, + Value: v, + } +} + +func (s *BMPInfoTLVUnknown) ParseValue(data []byte) error { + s.Value = data[:s.Length] + return nil +} + +func (s *BMPInfoTLVUnknown) Serialize() ([]byte, error) { + s.Length = uint16(len([]byte(s.Value))) + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + buf = append(buf, s.Value...) + return buf, nil +} + +type BMPInitiation struct { + Info []BMPInfoTLVInterface +} + +func NewBMPInitiation(info []BMPInfoTLVInterface) *BMPMessage { + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_INITIATION, + }, + Body: &BMPInitiation{ + Info: info, + }, + } +} + +func (body *BMPInitiation) ParseBody(msg *BMPMessage, data []byte) error { + for len(data) >= 4 { + tl := BMPInfoTLV{ + Type: binary.BigEndian.Uint16(data[0:2]), + Length: binary.BigEndian.Uint16(data[2:4]), + } + data = data[4:] + if len(data) < int(tl.Length) { + return fmt.Errorf("value length is not enough: %d bytes (%d bytes expected)", len(data), tl.Length) + } + var tlv BMPInfoTLVInterface + switch tl.Type { + case BMP_INIT_TLV_TYPE_STRING, BMP_INIT_TLV_TYPE_SYS_DESCR, BMP_INIT_TLV_TYPE_SYS_NAME: + tlv = &BMPInfoTLVString{BMPInfoTLV: tl} + default: + tlv = &BMPInfoTLVUnknown{BMPInfoTLV: tl} + } + if err := tlv.ParseValue(data); err != nil { + return err + } + body.Info = append(body.Info, tlv) + data = data[tl.Length:] + } + return nil +} + +func (body *BMPInitiation) Serialize() ([]byte, error) { + buf := make([]byte, 0) + for _, tlv := range body.Info { + b, err := tlv.Serialize() + if err != nil { + return buf, err + } + buf = append(buf, b...) + } + return buf, nil +} + +const ( + BMP_TERM_TLV_TYPE_STRING = iota + BMP_TERM_TLV_TYPE_REASON +) + +const ( + BMP_TERM_REASON_ADMIN = iota + BMP_TERM_REASON_UNSPEC + BMP_TERM_REASON_OUT_OF_RESOURCES + BMP_TERM_REASON_REDUNDANT_CONNECTION + BMP_TERM_REASON_PERMANENTLY_ADMIN +) + +type BMPTermTLVInterface interface { + ParseValue([]byte) error + Serialize() ([]byte, error) +} + +type BMPTermTLV struct { + Type uint16 + Length uint16 +} + +type BMPTermTLVString struct { + BMPTermTLV + Value string +} + +func NewBMPTermTLVString(t uint16, v string) *BMPTermTLVString { + return &BMPTermTLVString{ + BMPTermTLV: BMPTermTLV{Type: t}, + Value: v, + } +} + +func (s *BMPTermTLVString) ParseValue(data []byte) error { + s.Value = string(data[:s.Length]) + return nil +} + +func (s *BMPTermTLVString) Serialize() ([]byte, error) { + s.Length = uint16(len([]byte(s.Value))) + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + buf = append(buf, []byte(s.Value)...) + return buf, nil +} + +type BMPTermTLV16 struct { + BMPTermTLV + Value uint16 +} + +func NewBMPTermTLV16(t uint16, v uint16) *BMPTermTLV16 { + return &BMPTermTLV16{ + BMPTermTLV: BMPTermTLV{Type: t}, + Value: v, + } +} + +func (s *BMPTermTLV16) ParseValue(data []byte) error { + s.Value = binary.BigEndian.Uint16(data[:2]) + return nil +} + +func (s *BMPTermTLV16) Serialize() ([]byte, error) { + s.Length = 2 + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + binary.BigEndian.PutUint16(buf[4:6], s.Value) + return buf, nil +} + +type BMPTermTLVUnknown struct { + BMPTermTLV + Value []byte +} + +func NewBMPTermTLVUnknown(t uint16, v []byte) *BMPTermTLVUnknown { + return &BMPTermTLVUnknown{ + BMPTermTLV: BMPTermTLV{Type: t}, + Value: v, + } +} + +func (s *BMPTermTLVUnknown) ParseValue(data []byte) error { + s.Value = data[:s.Length] + return nil +} + +func (s *BMPTermTLVUnknown) Serialize() ([]byte, error) { + s.Length = uint16(len([]byte(s.Value))) + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + buf = append(buf, s.Value...) + return buf, nil +} + +type BMPTermination struct { + Info []BMPTermTLVInterface +} + +func NewBMPTermination(info []BMPTermTLVInterface) *BMPMessage { + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_TERMINATION, + }, + Body: &BMPTermination{ + Info: info, + }, + } +} + +func (body *BMPTermination) ParseBody(msg *BMPMessage, data []byte) error { + for len(data) >= 4 { + tl := BMPTermTLV{ + Type: binary.BigEndian.Uint16(data[0:2]), + Length: binary.BigEndian.Uint16(data[2:4]), + } + data = data[4:] + if len(data) < int(tl.Length) { + return fmt.Errorf("value length is not enough: %d bytes (%d bytes expected)", len(data), tl.Length) + } + var tlv BMPTermTLVInterface + switch tl.Type { + case BMP_TERM_TLV_TYPE_STRING: + tlv = &BMPTermTLVString{BMPTermTLV: tl} + case BMP_TERM_TLV_TYPE_REASON: + tlv = &BMPTermTLV16{BMPTermTLV: tl} + default: + tlv = &BMPTermTLVUnknown{BMPTermTLV: tl} + } + if err := tlv.ParseValue(data); err != nil { + return err + } + body.Info = append(body.Info, tlv) + data = data[tl.Length:] + } + return nil +} + +func (body *BMPTermination) Serialize() ([]byte, error) { + buf := make([]byte, 0) + for _, tlv := range body.Info { + b, err := tlv.Serialize() + if err != nil { + return buf, err + } + buf = append(buf, b...) + } + return buf, nil +} + +const ( + BMP_ROUTE_MIRRORING_TLV_TYPE_BGP_MSG = iota + BMP_ROUTE_MIRRORING_TLV_TYPE_INFO +) + +const ( + BMP_ROUTE_MIRRORING_INFO_ERR_PDU = iota + BMP_ROUTE_MIRRORING_INFO_MSG_LOST +) + +type BMPRouteMirrTLVInterface interface { + ParseValue([]byte) error + Serialize() ([]byte, error) +} + +type BMPRouteMirrTLV struct { + Type uint16 + Length uint16 +} + +type BMPRouteMirrTLVBGPMsg struct { + BMPRouteMirrTLV + Value *bgp.BGPMessage +} + +func NewBMPRouteMirrTLVBGPMsg(t uint16, v *bgp.BGPMessage) *BMPRouteMirrTLVBGPMsg { + return &BMPRouteMirrTLVBGPMsg{ + BMPRouteMirrTLV: BMPRouteMirrTLV{Type: t}, + Value: v, + } +} + +func (s *BMPRouteMirrTLVBGPMsg) ParseValue(data []byte) error { + v, err := bgp.ParseBGPMessage(data) + if err != nil { + return err + } + s.Value = v + return nil +} + +func (s *BMPRouteMirrTLVBGPMsg) Serialize() ([]byte, error) { + m, err := s.Value.Serialize() + if err != nil { + return nil, err + } + s.Length = uint16(len(m)) + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + buf = append(buf, m...) + return buf, nil +} + +type BMPRouteMirrTLV16 struct { + BMPRouteMirrTLV + Value uint16 +} + +func NewBMPRouteMirrTLV16(t uint16, v uint16) *BMPRouteMirrTLV16 { + return &BMPRouteMirrTLV16{ + BMPRouteMirrTLV: BMPRouteMirrTLV{Type: t}, + Value: v, + } +} + +func (s *BMPRouteMirrTLV16) ParseValue(data []byte) error { + s.Value = binary.BigEndian.Uint16(data[:2]) + return nil +} + +func (s *BMPRouteMirrTLV16) Serialize() ([]byte, error) { + s.Length = 2 + buf := make([]byte, 6) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + binary.BigEndian.PutUint16(buf[4:6], s.Value) + return buf, nil +} + +type BMPRouteMirrTLVUnknown struct { + BMPRouteMirrTLV + Value []byte +} + +func NewBMPRouteMirrTLVUnknown(t uint16, v []byte) *BMPRouteMirrTLVUnknown { + return &BMPRouteMirrTLVUnknown{ + BMPRouteMirrTLV: BMPRouteMirrTLV{Type: t}, + Value: v, + } +} + +func (s *BMPRouteMirrTLVUnknown) ParseValue(data []byte) error { + s.Value = data[:s.Length] + return nil +} + +func (s *BMPRouteMirrTLVUnknown) Serialize() ([]byte, error) { + s.Length = uint16(len([]byte(s.Value))) + buf := make([]byte, 4) + binary.BigEndian.PutUint16(buf[0:2], s.Type) + binary.BigEndian.PutUint16(buf[2:4], s.Length) + buf = append(buf, s.Value...) + return buf, nil +} + +type BMPRouteMirroring struct { + Info []BMPRouteMirrTLVInterface +} + +func NewBMPRouteMirroring(p BMPPeerHeader, info []BMPRouteMirrTLVInterface) *BMPMessage { + return &BMPMessage{ + Header: BMPHeader{ + Version: BMP_VERSION, + Type: BMP_MSG_ROUTE_MIRRORING, + }, + PeerHeader: p, + Body: &BMPRouteMirroring{ + Info: info, + }, + } +} + +func (body *BMPRouteMirroring) ParseBody(msg *BMPMessage, data []byte) error { + for len(data) >= 4 { + tl := BMPRouteMirrTLV{ + Type: binary.BigEndian.Uint16(data[0:2]), + Length: binary.BigEndian.Uint16(data[2:4]), + } + data = data[4:] + if len(data) < int(tl.Length) { + return fmt.Errorf("value length is not enough: %d bytes (%d bytes expected)", len(data), tl.Length) + } + var tlv BMPRouteMirrTLVInterface + switch tl.Type { + case BMP_ROUTE_MIRRORING_TLV_TYPE_BGP_MSG: + tlv = &BMPRouteMirrTLVBGPMsg{BMPRouteMirrTLV: tl} + case BMP_ROUTE_MIRRORING_TLV_TYPE_INFO: + tlv = &BMPRouteMirrTLV16{BMPRouteMirrTLV: tl} + default: + tlv = &BMPRouteMirrTLVUnknown{BMPRouteMirrTLV: tl} + } + if err := tlv.ParseValue(data); err != nil { + return err + } + body.Info = append(body.Info, tlv) + data = data[tl.Length:] + } + return nil +} + +func (body *BMPRouteMirroring) Serialize() ([]byte, error) { + buf := make([]byte, 0) + for _, tlv := range body.Info { + b, err := tlv.Serialize() + if err != nil { + return buf, err + } + buf = append(buf, b...) + } + return buf, nil +} + +type BMPBody interface { + // Sigh, some body messages need a BMPHeader to parse the body + // data so we need to pass BMPHeader (avoid DecodeFromBytes + // function name). + ParseBody(*BMPMessage, []byte) error + Serialize() ([]byte, error) +} + +type BMPMessage struct { + Header BMPHeader + PeerHeader BMPPeerHeader + Body BMPBody +} + +func (msg *BMPMessage) Serialize() ([]byte, error) { + buf := make([]byte, 0) + if msg.Header.Type != BMP_MSG_INITIATION && msg.Header.Type != BMP_MSG_TERMINATION { + p, err := msg.PeerHeader.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, p...) + } + + b, err := msg.Body.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, b...) + + if msg.Header.Length == 0 { + msg.Header.Length = uint32(BMP_HEADER_SIZE + len(buf)) + } + + h, err := msg.Header.Serialize() + if err != nil { + return nil, err + } + return append(h, buf...), nil +} + +func (msg *BMPMessage) Len() int { + return int(msg.Header.Length) +} + +const ( + BMP_MSG_ROUTE_MONITORING = iota + BMP_MSG_STATISTICS_REPORT + BMP_MSG_PEER_DOWN_NOTIFICATION + BMP_MSG_PEER_UP_NOTIFICATION + BMP_MSG_INITIATION + BMP_MSG_TERMINATION + BMP_MSG_ROUTE_MIRRORING +) + +func ParseBMPMessage(data []byte) (msg *BMPMessage, err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("not all data bytes are available") + } + }() + + msg = &BMPMessage{} + err = msg.Header.DecodeFromBytes(data) + if err != nil { + return nil, err + } + data = data[BMP_HEADER_SIZE:msg.Header.Length] + + switch msg.Header.Type { + case BMP_MSG_ROUTE_MONITORING: + msg.Body = &BMPRouteMonitoring{} + case BMP_MSG_STATISTICS_REPORT: + msg.Body = &BMPStatisticsReport{} + case BMP_MSG_PEER_DOWN_NOTIFICATION: + msg.Body = &BMPPeerDownNotification{} + case BMP_MSG_PEER_UP_NOTIFICATION: + msg.Body = &BMPPeerUpNotification{} + case BMP_MSG_INITIATION: + msg.Body = &BMPInitiation{} + case BMP_MSG_TERMINATION: + msg.Body = &BMPTermination{} + case BMP_MSG_ROUTE_MIRRORING: + msg.Body = &BMPRouteMirroring{} + default: + return nil, fmt.Errorf("unsupported BMP message type: %d", msg.Header.Type) + } + + if msg.Header.Type != BMP_MSG_INITIATION && msg.Header.Type != BMP_MSG_TERMINATION { + msg.PeerHeader.DecodeFromBytes(data) + data = data[BMP_PEER_HEADER_SIZE:] + } + + err = msg.Body.ParseBody(msg, data) + if err != nil { + return nil, err + } + return msg, nil +} + +func SplitBMP(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 || len(data) < BMP_HEADER_SIZE { + return 0, nil, nil + } + + msg := &BMPMessage{} + msg.Header.DecodeFromBytes(data) + if uint32(len(data)) < msg.Header.Length { + return 0, nil, nil + } + + return int(msg.Header.Length), data[0:msg.Header.Length], nil +} diff --git a/pkg/packet/bmp/bmp_test.go b/pkg/packet/bmp/bmp_test.go new file mode 100644 index 00000000..be99b687 --- /dev/null +++ b/pkg/packet/bmp/bmp_test.go @@ -0,0 +1,105 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package bmp + +import ( + "testing" + + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func verify(t *testing.T, m1 *BMPMessage) { + buf1, _ := m1.Serialize() + m2, err := ParseBMPMessage(buf1) + require.NoError(t, err) + + assert.Equal(t, m1, m2) +} + +func Test_Initiation(t *testing.T) { + verify(t, NewBMPInitiation(nil)) + m := NewBMPInitiation([]BMPInfoTLVInterface{ + NewBMPInfoTLVString(BMP_INIT_TLV_TYPE_STRING, "free-form UTF-8 string"), + NewBMPInfoTLVUnknown(0xff, []byte{0x01, 0x02, 0x03, 0x04}), + }) + verify(t, m) +} + +func Test_Termination(t *testing.T) { + verify(t, NewBMPTermination(nil)) + m := NewBMPTermination([]BMPTermTLVInterface{ + NewBMPTermTLVString(BMP_TERM_TLV_TYPE_STRING, "free-form UTF-8 string"), + NewBMPTermTLV16(BMP_TERM_TLV_TYPE_REASON, BMP_TERM_REASON_ADMIN), + NewBMPTermTLVUnknown(0xff, []byte{0x01, 0x02, 0x03, 0x04}), + }) + verify(t, m) +} + +func Test_PeerUpNotification(t *testing.T) { + m := bgp.NewTestBGPOpenMessage() + p0 := NewBMPPeerHeader(0, 0, 1000, "10.0.0.1", 70000, "10.0.0.2", 1) + verify(t, NewBMPPeerUpNotification(*p0, "10.0.0.3", 10, 100, m, m)) + p1 := NewBMPPeerHeader(0, 0, 1000, "fe80::6e40:8ff:feab:2c2a", 70000, "10.0.0.2", 1) + verify(t, NewBMPPeerUpNotification(*p1, "fe80::6e40:8ff:feab:2c2a", 10, 100, m, m)) +} + +func Test_PeerDownNotification(t *testing.T) { + p0 := NewBMPPeerHeader(0, 0, 1000, "10.0.0.1", 70000, "10.0.0.2", 1) + verify(t, NewBMPPeerDownNotification(*p0, BMP_PEER_DOWN_REASON_LOCAL_NO_NOTIFICATION, nil, []byte{0x3, 0xb})) + m := bgp.NewBGPNotificationMessage(1, 2, nil) + verify(t, NewBMPPeerDownNotification(*p0, BMP_PEER_DOWN_REASON_LOCAL_BGP_NOTIFICATION, m, nil)) +} + +func Test_RouteMonitoring(t *testing.T) { + m := bgp.NewTestBGPUpdateMessage() + p0 := NewBMPPeerHeader(0, 0, 1000, "fe80::6e40:8ff:feab:2c2a", 70000, "10.0.0.2", 1) + verify(t, NewBMPRouteMonitoring(*p0, m)) +} + +func Test_StatisticsReport(t *testing.T) { + p0 := NewBMPPeerHeader(0, 0, 1000, "10.0.0.1", 70000, "10.0.0.2", 1) + s0 := NewBMPStatisticsReport( + *p0, + []BMPStatsTLVInterface{ + NewBMPStatsTLV32(BMP_STAT_TYPE_REJECTED, 100), + NewBMPStatsTLV64(BMP_STAT_TYPE_ADJ_RIB_IN, 200), + NewBMPStatsTLVPerAfiSafi64(BMP_STAT_TYPE_PER_AFI_SAFI_LOC_RIB, bgp.AFI_IP, bgp.SAFI_UNICAST, 300), + }, + ) + verify(t, s0) +} + +func Test_RouteMirroring(t *testing.T) { + p0 := NewBMPPeerHeader(0, 0, 1000, "10.0.0.1", 70000, "10.0.0.2", 1) + s0 := NewBMPRouteMirroring( + *p0, + []BMPRouteMirrTLVInterface{ + NewBMPRouteMirrTLV16(BMP_ROUTE_MIRRORING_TLV_TYPE_INFO, BMP_ROUTE_MIRRORING_INFO_MSG_LOST), + NewBMPRouteMirrTLVUnknown(0xff, []byte{0x01, 0x02, 0x03, 0x04}), + // RFC7854: BGP Message TLV MUST occur last in the list of TLVs + NewBMPRouteMirrTLVBGPMsg(BMP_ROUTE_MIRRORING_TLV_TYPE_BGP_MSG, bgp.NewTestBGPOpenMessage()), + }, + ) + verify(t, s0) +} + +func Test_BogusHeader(t *testing.T) { + h, err := ParseBMPMessage(make([]byte, 10)) + assert.Nil(t, h) + assert.NotNil(t, err) +} diff --git a/pkg/packet/mrt/mrt.go b/pkg/packet/mrt/mrt.go new file mode 100644 index 00000000..dc07ba9b --- /dev/null +++ b/pkg/packet/mrt/mrt.go @@ -0,0 +1,1006 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mrt + +import ( + "bytes" + "encoding/binary" + "fmt" + "math" + "net" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +const ( + MRT_COMMON_HEADER_LEN = 12 +) + +type MRTType uint16 + +const ( + NULL MRTType = 0 // deprecated + START MRTType = 1 // deprecated + DIE MRTType = 2 // deprecated + I_AM_DEAD MRTType = 3 // deprecated + PEER_DOWN MRTType = 4 // deprecated + BGP MRTType = 5 // deprecated + RIP MRTType = 6 // deprecated + IDRP MRTType = 7 // deprecated + RIPNG MRTType = 8 // deprecated + BGP4PLUS MRTType = 9 // deprecated + BGP4PLUS01 MRTType = 10 // deprecated + OSPFv2 MRTType = 11 + TABLE_DUMP MRTType = 12 + TABLE_DUMPv2 MRTType = 13 + BGP4MP MRTType = 16 + BGP4MP_ET MRTType = 17 + ISIS MRTType = 32 + ISIS_ET MRTType = 33 + OSPFv3 MRTType = 48 + OSPFv3_ET MRTType = 49 +) + +type MRTSubTyper interface { + ToUint16() uint16 +} + +type MRTSubTypeTableDumpv2 uint16 + +const ( + PEER_INDEX_TABLE MRTSubTypeTableDumpv2 = 1 + RIB_IPV4_UNICAST MRTSubTypeTableDumpv2 = 2 + RIB_IPV4_MULTICAST MRTSubTypeTableDumpv2 = 3 + RIB_IPV6_UNICAST MRTSubTypeTableDumpv2 = 4 + RIB_IPV6_MULTICAST MRTSubTypeTableDumpv2 = 5 + RIB_GENERIC MRTSubTypeTableDumpv2 = 6 + GEO_PEER_TABLE MRTSubTypeTableDumpv2 = 7 // RFC6397 + RIB_IPV4_UNICAST_ADDPATH MRTSubTypeTableDumpv2 = 8 // RFC8050 + RIB_IPV4_MULTICAST_ADDPATH MRTSubTypeTableDumpv2 = 9 // RFC8050 + RIB_IPV6_UNICAST_ADDPATH MRTSubTypeTableDumpv2 = 10 // RFC8050 + RIB_IPV6_MULTICAST_ADDPATH MRTSubTypeTableDumpv2 = 11 // RFC8050 + RIB_GENERIC_ADDPATH MRTSubTypeTableDumpv2 = 12 // RFC8050 +) + +func (t MRTSubTypeTableDumpv2) ToUint16() uint16 { + return uint16(t) +} + +type MRTSubTypeBGP4MP uint16 + +const ( + STATE_CHANGE MRTSubTypeBGP4MP = 0 + MESSAGE MRTSubTypeBGP4MP = 1 + MESSAGE_AS4 MRTSubTypeBGP4MP = 4 + STATE_CHANGE_AS4 MRTSubTypeBGP4MP = 5 + MESSAGE_LOCAL MRTSubTypeBGP4MP = 6 + MESSAGE_AS4_LOCAL MRTSubTypeBGP4MP = 7 + MESSAGE_ADDPATH MRTSubTypeBGP4MP = 8 // RFC8050 + MESSAGE_AS4_ADDPATH MRTSubTypeBGP4MP = 9 // RFC8050 + MESSAGE_LOCAL_ADDPATH MRTSubTypeBGP4MP = 10 // RFC8050 + MESSAGE_AS4_LOCAL_ADDPATH MRTSubTypeBGP4MP = 11 // RFC8050 +) + +func (t MRTSubTypeBGP4MP) ToUint16() uint16 { + return uint16(t) +} + +type BGPState uint16 + +const ( + IDLE BGPState = 1 + CONNECT BGPState = 2 + ACTIVE BGPState = 3 + OPENSENT BGPState = 4 + OPENCONFIRM BGPState = 5 + ESTABLISHED BGPState = 6 +) + +func packValues(values []interface{}) ([]byte, error) { + b := new(bytes.Buffer) + for _, v := range values { + err := binary.Write(b, binary.BigEndian, v) + if err != nil { + return nil, err + } + } + return b.Bytes(), nil +} + +type MRTHeader struct { + Timestamp uint32 + Type MRTType + SubType uint16 + Len uint32 +} + +func (h *MRTHeader) DecodeFromBytes(data []byte) error { + if len(data) < MRT_COMMON_HEADER_LEN { + return fmt.Errorf("not all MRTHeader bytes are available. expected: %d, actual: %d", MRT_COMMON_HEADER_LEN, len(data)) + } + h.Timestamp = binary.BigEndian.Uint32(data[:4]) + h.Type = MRTType(binary.BigEndian.Uint16(data[4:6])) + h.SubType = binary.BigEndian.Uint16(data[6:8]) + h.Len = binary.BigEndian.Uint32(data[8:12]) + return nil +} + +func (h *MRTHeader) Serialize() ([]byte, error) { + return packValues([]interface{}{h.Timestamp, h.Type, h.SubType, h.Len}) +} + +func NewMRTHeader(timestamp uint32, t MRTType, subtype MRTSubTyper, l uint32) (*MRTHeader, error) { + return &MRTHeader{ + Timestamp: timestamp, + Type: t, + SubType: subtype.ToUint16(), + Len: l, + }, nil +} + +func (h *MRTHeader) GetTime() time.Time { + t := int64(h.Timestamp) + return time.Unix(t, 0) +} + +type MRTMessage struct { + Header MRTHeader + Body Body +} + +func (m *MRTMessage) Serialize() ([]byte, error) { + buf, err := m.Body.Serialize() + if err != nil { + return nil, err + } + m.Header.Len = uint32(len(buf)) + bbuf, err := m.Header.Serialize() + if err != nil { + return nil, err + } + return append(bbuf, buf...), nil +} + +func NewMRTMessage(timestamp uint32, t MRTType, subtype MRTSubTyper, body Body) (*MRTMessage, error) { + header, err := NewMRTHeader(timestamp, t, subtype, 0) + if err != nil { + return nil, err + } + return &MRTMessage{ + Header: *header, + Body: body, + }, nil +} + +type Body interface { + DecodeFromBytes([]byte) error + Serialize() ([]byte, error) +} + +type Peer struct { + Type uint8 + BgpId net.IP + IpAddress net.IP + AS uint32 +} + +func (p *Peer) DecodeFromBytes(data []byte) ([]byte, error) { + notAllBytesAvail := fmt.Errorf("not all Peer bytes are available") + if len(data) < 5 { + return nil, notAllBytesAvail + } + p.Type = uint8(data[0]) + p.BgpId = net.IP(data[1:5]) + data = data[5:] + + if p.Type&1 > 0 { + if len(data) < 16 { + return nil, notAllBytesAvail + } + p.IpAddress = net.IP(data[:16]) + data = data[16:] + } else { + if len(data) < 4 { + return nil, notAllBytesAvail + } + p.IpAddress = net.IP(data[:4]) + data = data[4:] + } + + if p.Type&(1<<1) > 0 { + if len(data) < 4 { + return nil, notAllBytesAvail + } + p.AS = binary.BigEndian.Uint32(data[:4]) + data = data[4:] + } else { + if len(data) < 2 { + return nil, notAllBytesAvail + } + p.AS = uint32(binary.BigEndian.Uint16(data[:2])) + data = data[2:] + } + + return data, nil +} + +func (p *Peer) Serialize() ([]byte, error) { + var err error + var bbuf []byte + buf := make([]byte, 5) + buf[0] = uint8(p.Type) + copy(buf[1:], p.BgpId.To4()) + if p.Type&1 > 0 { + buf = append(buf, p.IpAddress.To16()...) + } else { + buf = append(buf, p.IpAddress.To4()...) + } + if p.Type&(1<<1) > 0 { + bbuf, err = packValues([]interface{}{p.AS}) + } else { + if p.AS > uint32(math.MaxUint16) { + return nil, fmt.Errorf("AS number is beyond 2 octet. %d > %d", p.AS, math.MaxUint16) + } + bbuf, err = packValues([]interface{}{uint16(p.AS)}) + } + if err != nil { + return nil, err + } + return append(buf, bbuf...), nil +} + +func NewPeer(bgpid string, ipaddr string, asn uint32, isAS4 bool) *Peer { + t := 0 + addr := net.ParseIP(ipaddr).To4() + if addr == nil { + t |= 1 + addr = net.ParseIP(ipaddr).To16() + } + if isAS4 { + t |= (1 << 1) + } + return &Peer{ + Type: uint8(t), + BgpId: net.ParseIP(bgpid).To4(), + IpAddress: addr, + AS: asn, + } +} + +func (p *Peer) String() string { + return fmt.Sprintf("PEER ENTRY: ID [%s] Addr [%s] AS [%d]", p.BgpId, p.IpAddress, p.AS) +} + +type PeerIndexTable struct { + CollectorBgpId net.IP + ViewName string + Peers []*Peer +} + +func (t *PeerIndexTable) DecodeFromBytes(data []byte) error { + notAllBytesAvail := fmt.Errorf("not all PeerIndexTable bytes are available") + if len(data) < 6 { + return notAllBytesAvail + } + t.CollectorBgpId = net.IP(data[:4]) + viewLen := binary.BigEndian.Uint16(data[4:6]) + if len(data) < 6+int(viewLen) { + return notAllBytesAvail + } + t.ViewName = string(data[6 : 6+viewLen]) + + data = data[6+viewLen:] + + if len(data) < 2 { + return notAllBytesAvail + } + peerNum := binary.BigEndian.Uint16(data[:2]) + data = data[2:] + t.Peers = make([]*Peer, 0, peerNum) + var err error + for i := 0; i < int(peerNum); i++ { + p := &Peer{} + data, err = p.DecodeFromBytes(data) + if err != nil { + return err + } + t.Peers = append(t.Peers, p) + } + + return nil +} + +func (t *PeerIndexTable) Serialize() ([]byte, error) { + buf := make([]byte, 8+len(t.ViewName)) + copy(buf, t.CollectorBgpId.To4()) + binary.BigEndian.PutUint16(buf[4:], uint16(len(t.ViewName))) + copy(buf[6:], t.ViewName) + binary.BigEndian.PutUint16(buf[6+len(t.ViewName):], uint16(len(t.Peers))) + for _, peer := range t.Peers { + bbuf, err := peer.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + } + return buf, nil +} + +func NewPeerIndexTable(bgpid string, viewname string, peers []*Peer) *PeerIndexTable { + return &PeerIndexTable{ + CollectorBgpId: net.ParseIP(bgpid).To4(), + ViewName: viewname, + Peers: peers, + } +} + +func (t *PeerIndexTable) String() string { + return fmt.Sprintf("PEER_INDEX_TABLE: CollectorBgpId [%s] ViewName [%s] Peers [%s]", t.CollectorBgpId, t.ViewName, t.Peers) +} + +type RibEntry struct { + PeerIndex uint16 + OriginatedTime uint32 + PathIdentifier uint32 + PathAttributes []bgp.PathAttributeInterface + isAddPath bool +} + +func (e *RibEntry) DecodeFromBytes(data []byte) ([]byte, error) { + notAllBytesAvail := fmt.Errorf("not all RibEntry bytes are available") + if len(data) < 8 { + return nil, notAllBytesAvail + } + e.PeerIndex = binary.BigEndian.Uint16(data[:2]) + e.OriginatedTime = binary.BigEndian.Uint32(data[2:6]) + if e.isAddPath { + e.PathIdentifier = binary.BigEndian.Uint32(data[6:10]) + data = data[10:] + } else { + data = data[6:] + } + totalLen := binary.BigEndian.Uint16(data[:2]) + data = data[2:] + for attrLen := totalLen; attrLen > 0; { + p, err := bgp.GetPathAttribute(data) + if err != nil { + return nil, err + } + err = p.DecodeFromBytes(data) + if err != nil { + return nil, err + } + attrLen -= uint16(p.Len()) + if len(data) < p.Len() { + return nil, notAllBytesAvail + } + data = data[p.Len():] + e.PathAttributes = append(e.PathAttributes, p) + } + return data, nil +} + +func (e *RibEntry) Serialize() ([]byte, error) { + pbuf := make([]byte, 0) + totalLen := 0 + for _, pattr := range e.PathAttributes { + // TODO special modification is needed for MP_REACH_NLRI + // but also Quagga doesn't implement this. + // + // RFC 6396 4.3.4 + // There is one exception to the encoding of BGP attributes for the BGP + // MP_REACH_NLRI attribute (BGP Type Code 14). + // Since the AFI, SAFI, and NLRI information is already encoded + // in the RIB Entry Header or RIB_GENERIC Entry Header, + // only the Next Hop Address Length and Next Hop Address fields are included. + + pb, err := pattr.Serialize() + if err != nil { + return nil, err + } + pbuf = append(pbuf, pb...) + totalLen += len(pb) + } + var buf []byte + if e.isAddPath { + buf = make([]byte, 12) + binary.BigEndian.PutUint16(buf, e.PeerIndex) + binary.BigEndian.PutUint32(buf[2:], e.OriginatedTime) + binary.BigEndian.PutUint32(buf[6:], e.PathIdentifier) + binary.BigEndian.PutUint16(buf[10:], uint16(totalLen)) + } else { + buf = make([]byte, 8) + binary.BigEndian.PutUint16(buf, e.PeerIndex) + binary.BigEndian.PutUint32(buf[2:], e.OriginatedTime) + binary.BigEndian.PutUint16(buf[6:], uint16(totalLen)) + } + buf = append(buf, pbuf...) + return buf, nil +} + +func NewRibEntry(index uint16, time uint32, pathId uint32, pathAttrs []bgp.PathAttributeInterface, isAddPath bool) *RibEntry { + return &RibEntry{ + PeerIndex: index, + OriginatedTime: time, + PathIdentifier: pathId, + PathAttributes: pathAttrs, + isAddPath: isAddPath, + } +} + +func (e *RibEntry) String() string { + if e.isAddPath { + return fmt.Sprintf("RIB_ENTRY: PeerIndex [%d] OriginatedTime [%d] PathIdentifier[%d] PathAttributes [%v]", e.PeerIndex, e.OriginatedTime, e.PathIdentifier, e.PathAttributes) + } else { + return fmt.Sprintf("RIB_ENTRY: PeerIndex [%d] OriginatedTime [%d] PathAttributes [%v]", e.PeerIndex, e.OriginatedTime, e.PathAttributes) + } + +} + +type Rib struct { + SequenceNumber uint32 + Prefix bgp.AddrPrefixInterface + Entries []*RibEntry + RouteFamily bgp.RouteFamily + isAddPath bool +} + +func (u *Rib) DecodeFromBytes(data []byte) error { + if len(data) < 4 { + return fmt.Errorf("Not all RibIpv4Unicast message bytes available") + } + u.SequenceNumber = binary.BigEndian.Uint32(data[:4]) + data = data[4:] + afi, safi := bgp.RouteFamilyToAfiSafi(u.RouteFamily) + if afi == 0 && safi == 0 { + afi = binary.BigEndian.Uint16(data[:2]) + safi = data[2] + data = data[3:] + } + prefix, err := bgp.NewPrefixFromRouteFamily(afi, safi) + if err != nil { + return err + } + err = prefix.DecodeFromBytes(data) + if err != nil { + return err + } + u.Prefix = prefix + data = data[prefix.Len():] + entryNum := binary.BigEndian.Uint16(data[:2]) + data = data[2:] + u.Entries = make([]*RibEntry, 0, entryNum) + for i := 0; i < int(entryNum); i++ { + e := &RibEntry{ + isAddPath: u.isAddPath, + } + data, err = e.DecodeFromBytes(data) + if err != nil { + return err + } + u.Entries = append(u.Entries, e) + } + return nil +} + +func (u *Rib) Serialize() ([]byte, error) { + buf := make([]byte, 4) + binary.BigEndian.PutUint32(buf, u.SequenceNumber) + rf := bgp.AfiSafiToRouteFamily(u.Prefix.AFI(), u.Prefix.SAFI()) + switch rf { + case bgp.RF_IPv4_UC, bgp.RF_IPv4_MC, bgp.RF_IPv6_UC, bgp.RF_IPv6_MC: + default: + bbuf := make([]byte, 2) + binary.BigEndian.PutUint16(bbuf, u.Prefix.AFI()) + buf = append(buf, bbuf...) + buf = append(buf, u.Prefix.SAFI()) + } + bbuf, err := u.Prefix.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + bbuf, err = packValues([]interface{}{uint16(len(u.Entries))}) + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + for _, entry := range u.Entries { + bbuf, err = entry.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, bbuf...) + } + return buf, nil +} + +func NewRib(seq uint32, prefix bgp.AddrPrefixInterface, entries []*RibEntry) *Rib { + rf := bgp.AfiSafiToRouteFamily(prefix.AFI(), prefix.SAFI()) + return &Rib{ + SequenceNumber: seq, + Prefix: prefix, + Entries: entries, + RouteFamily: rf, + isAddPath: entries[0].isAddPath, + } +} + +func (u *Rib) String() string { + return fmt.Sprintf("RIB: Seq [%d] Prefix [%s] Entries [%s]", u.SequenceNumber, u.Prefix, u.Entries) +} + +type GeoPeer struct { + Type uint8 + BgpId net.IP + Latitude float32 + Longitude float32 +} + +func (p *GeoPeer) DecodeFromBytes(data []byte) ([]byte, error) { + if len(data) < 13 { + return nil, fmt.Errorf("not all GeoPeer bytes are available") + } + // Peer IP Address and Peer AS should not be included + p.Type = uint8(data[0]) + if p.Type != uint8(0) { + return nil, fmt.Errorf("unsupported peer type for GeoPeer: %d", p.Type) + } + p.BgpId = net.IP(data[1:5]) + p.Latitude = math.Float32frombits(binary.BigEndian.Uint32(data[5:9])) + p.Longitude = math.Float32frombits(binary.BigEndian.Uint32(data[9:13])) + return data[13:], nil +} + +func (p *GeoPeer) Serialize() ([]byte, error) { + buf := make([]byte, 13) + buf[0] = uint8(0) // Peer IP Address and Peer AS should not be included + bgpId := p.BgpId.To4() + if bgpId == nil { + return nil, fmt.Errorf("invalid BgpId: %s", p.BgpId) + } + copy(buf[1:5], bgpId) + binary.BigEndian.PutUint32(buf[5:9], math.Float32bits(p.Latitude)) + binary.BigEndian.PutUint32(buf[9:13], math.Float32bits(p.Longitude)) + return buf, nil +} + +func NewGeoPeer(bgpid string, latitude float32, longitude float32) *GeoPeer { + return &GeoPeer{ + Type: 0, // Peer IP Address and Peer AS should not be included + BgpId: net.ParseIP(bgpid).To4(), + Latitude: latitude, + Longitude: longitude, + } +} + +func (p *GeoPeer) String() string { + return fmt.Sprintf("PEER ENTRY: ID [%s] Latitude [%f] Longitude [%f]", p.BgpId, p.Latitude, p.Longitude) +} + +type GeoPeerTable struct { + CollectorBgpId net.IP + CollectorLatitude float32 + CollectorLongitude float32 + Peers []*GeoPeer +} + +func (t *GeoPeerTable) DecodeFromBytes(data []byte) error { + if len(data) < 14 { + return fmt.Errorf("not all GeoPeerTable bytes are available") + } + t.CollectorBgpId = net.IP(data[0:4]) + t.CollectorLatitude = math.Float32frombits(binary.BigEndian.Uint32(data[4:8])) + t.CollectorLongitude = math.Float32frombits(binary.BigEndian.Uint32(data[8:12])) + peerCount := binary.BigEndian.Uint16(data[12:14]) + data = data[14:] + t.Peers = make([]*GeoPeer, 0, peerCount) + var err error + for i := 0; i < int(peerCount); i++ { + p := &GeoPeer{} + if data, err = p.DecodeFromBytes(data); err != nil { + return err + } + t.Peers = append(t.Peers, p) + } + return nil +} + +func (t *GeoPeerTable) Serialize() ([]byte, error) { + buf := make([]byte, 14) + collectorBgpId := t.CollectorBgpId.To4() + if collectorBgpId == nil { + return nil, fmt.Errorf("invalid CollectorBgpId: %s", t.CollectorBgpId) + } + copy(buf[0:4], collectorBgpId) + binary.BigEndian.PutUint32(buf[4:8], math.Float32bits(t.CollectorLatitude)) + binary.BigEndian.PutUint32(buf[8:12], math.Float32bits(t.CollectorLongitude)) + binary.BigEndian.PutUint16(buf[12:14], uint16(len(t.Peers))) + for _, peer := range t.Peers { + pbuf, err := peer.Serialize() + if err != nil { + return nil, err + } + buf = append(buf, pbuf...) + } + return buf, nil +} + +func NewGeoPeerTable(bgpid string, latitude float32, longitude float32, peers []*GeoPeer) *GeoPeerTable { + return &GeoPeerTable{ + CollectorBgpId: net.ParseIP(bgpid).To4(), + CollectorLatitude: latitude, + CollectorLongitude: longitude, + Peers: peers, + } +} + +func (t *GeoPeerTable) String() string { + return fmt.Sprintf("GEO_PEER_TABLE: CollectorBgpId [%s] CollectorLatitude [%f] CollectorLongitude [%f] Peers [%s]", t.CollectorBgpId, t.CollectorLatitude, t.CollectorLongitude, t.Peers) +} + +type BGP4MPHeader struct { + PeerAS uint32 + LocalAS uint32 + InterfaceIndex uint16 + AddressFamily uint16 + PeerIpAddress net.IP + LocalIpAddress net.IP + isAS4 bool +} + +func (m *BGP4MPHeader) decodeFromBytes(data []byte) ([]byte, error) { + if m.isAS4 && len(data) < 8 { + return nil, fmt.Errorf("Not all BGP4MPMessageAS4 bytes available") + } else if !m.isAS4 && len(data) < 4 { + return nil, fmt.Errorf("Not all BGP4MPMessageAS bytes available") + } + + if m.isAS4 { + m.PeerAS = binary.BigEndian.Uint32(data[:4]) + m.LocalAS = binary.BigEndian.Uint32(data[4:8]) + data = data[8:] + } else { + m.PeerAS = uint32(binary.BigEndian.Uint16(data[:2])) + m.LocalAS = uint32(binary.BigEndian.Uint16(data[2:4])) + data = data[4:] + } + m.InterfaceIndex = binary.BigEndian.Uint16(data[:2]) + m.AddressFamily = binary.BigEndian.Uint16(data[2:4]) + switch m.AddressFamily { + case bgp.AFI_IP: + m.PeerIpAddress = net.IP(data[4:8]).To4() + m.LocalIpAddress = net.IP(data[8:12]).To4() + data = data[12:] + case bgp.AFI_IP6: + m.PeerIpAddress = net.IP(data[4:20]) + m.LocalIpAddress = net.IP(data[20:36]) + data = data[36:] + default: + return nil, fmt.Errorf("unsupported address family: %d", m.AddressFamily) + } + return data, nil +} + +func (m *BGP4MPHeader) serialize() ([]byte, error) { + var values []interface{} + if m.isAS4 { + values = []interface{}{m.PeerAS, m.LocalAS, m.InterfaceIndex, m.AddressFamily} + } else { + values = []interface{}{uint16(m.PeerAS), uint16(m.LocalAS), m.InterfaceIndex, m.AddressFamily} + } + buf, err := packValues(values) + if err != nil { + return nil, err + } + var bbuf []byte + switch m.AddressFamily { + case bgp.AFI_IP: + bbuf = make([]byte, 8) + copy(bbuf, m.PeerIpAddress.To4()) + copy(bbuf[4:], m.LocalIpAddress.To4()) + case bgp.AFI_IP6: + bbuf = make([]byte, 32) + copy(bbuf, m.PeerIpAddress) + copy(bbuf[16:], m.LocalIpAddress) + default: + return nil, fmt.Errorf("unsupported address family: %d", m.AddressFamily) + } + return append(buf, bbuf...), nil +} + +func newBGP4MPHeader(peeras, localas uint32, intfindex uint16, peerip, localip string, isAS4 bool) (*BGP4MPHeader, error) { + var af uint16 + paddr := net.ParseIP(peerip).To4() + laddr := net.ParseIP(localip).To4() + if paddr != nil && laddr != nil { + af = bgp.AFI_IP + } else { + paddr = net.ParseIP(peerip).To16() + laddr = net.ParseIP(localip).To16() + if paddr != nil && laddr != nil { + af = bgp.AFI_IP6 + } else { + return nil, fmt.Errorf("Peer IP Address and Local IP Address must have the same address family") + } + } + return &BGP4MPHeader{ + PeerAS: peeras, + LocalAS: localas, + InterfaceIndex: intfindex, + AddressFamily: af, + PeerIpAddress: paddr, + LocalIpAddress: laddr, + isAS4: isAS4, + }, nil +} + +type BGP4MPStateChange struct { + *BGP4MPHeader + OldState BGPState + NewState BGPState +} + +func (m *BGP4MPStateChange) DecodeFromBytes(data []byte) error { + rest, err := m.decodeFromBytes(data) + if err != nil { + return err + } + if len(rest) < 4 { + return fmt.Errorf("Not all BGP4MPStateChange bytes available") + } + m.OldState = BGPState(binary.BigEndian.Uint16(rest[:2])) + m.NewState = BGPState(binary.BigEndian.Uint16(rest[2:4])) + return nil +} + +func (m *BGP4MPStateChange) Serialize() ([]byte, error) { + buf, err := m.serialize() + if err != nil { + return nil, err + } + bbuf, err := packValues([]interface{}{m.OldState, m.NewState}) + if err != nil { + return nil, err + } + return append(buf, bbuf...), nil +} + +func NewBGP4MPStateChange(peeras, localas uint32, intfindex uint16, peerip, localip string, isAS4 bool, oldstate, newstate BGPState) *BGP4MPStateChange { + header, _ := newBGP4MPHeader(peeras, localas, intfindex, peerip, localip, isAS4) + return &BGP4MPStateChange{ + BGP4MPHeader: header, + OldState: oldstate, + NewState: newstate, + } +} + +type BGP4MPMessage struct { + *BGP4MPHeader + BGPMessage *bgp.BGPMessage + BGPMessagePayload []byte + isLocal bool + isAddPath bool +} + +func (m *BGP4MPMessage) DecodeFromBytes(data []byte) error { + rest, err := m.decodeFromBytes(data) + if err != nil { + return err + } + + if len(rest) < bgp.BGP_HEADER_LENGTH { + return fmt.Errorf("Not all BGP4MPMessageAS4 bytes available") + } + + msg, err := bgp.ParseBGPMessage(rest) + if err != nil { + return err + } + m.BGPMessage = msg + return nil +} + +func (m *BGP4MPMessage) Serialize() ([]byte, error) { + buf, err := m.serialize() + if err != nil { + return nil, err + } + if m.BGPMessagePayload != nil { + return append(buf, m.BGPMessagePayload...), nil + } + bbuf, err := m.BGPMessage.Serialize() + if err != nil { + return nil, err + } + return append(buf, bbuf...), nil +} + +func NewBGP4MPMessage(peeras, localas uint32, intfindex uint16, peerip, localip string, isAS4 bool, msg *bgp.BGPMessage) *BGP4MPMessage { + header, _ := newBGP4MPHeader(peeras, localas, intfindex, peerip, localip, isAS4) + return &BGP4MPMessage{ + BGP4MPHeader: header, + BGPMessage: msg, + } +} + +func NewBGP4MPMessageLocal(peeras, localas uint32, intfindex uint16, peerip, localip string, isAS4 bool, msg *bgp.BGPMessage) *BGP4MPMessage { + header, _ := newBGP4MPHeader(peeras, localas, intfindex, peerip, localip, isAS4) + return &BGP4MPMessage{ + BGP4MPHeader: header, + BGPMessage: msg, + isLocal: true, + } +} + +func NewBGP4MPMessageAddPath(peeras, localas uint32, intfindex uint16, peerip, localip string, isAS4 bool, msg *bgp.BGPMessage) *BGP4MPMessage { + header, _ := newBGP4MPHeader(peeras, localas, intfindex, peerip, localip, isAS4) + return &BGP4MPMessage{ + BGP4MPHeader: header, + BGPMessage: msg, + isAddPath: true, + } +} + +func NewBGP4MPMessageLocalAddPath(peeras, localas uint32, intfindex uint16, peerip, localip string, isAS4 bool, msg *bgp.BGPMessage) *BGP4MPMessage { + header, _ := newBGP4MPHeader(peeras, localas, intfindex, peerip, localip, isAS4) + return &BGP4MPMessage{ + BGP4MPHeader: header, + BGPMessage: msg, + isLocal: true, + isAddPath: true, + } +} + +func (m *BGP4MPMessage) String() string { + title := "BGP4MP_MSG" + if m.isAS4 { + title += "_AS4" + } + if m.isLocal { + title += "_LOCAL" + } + if m.isAddPath { + title += "_ADDPATH" + } + return fmt.Sprintf("%s: PeerAS [%d] LocalAS [%d] InterfaceIndex [%d] PeerIP [%s] LocalIP [%s] BGPMessage [%v]", title, m.PeerAS, m.LocalAS, m.InterfaceIndex, m.PeerIpAddress, m.LocalIpAddress, m.BGPMessage) +} + +//This function can be passed into a bufio.Scanner.Split() to read buffered mrt msgs +func SplitMrt(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 { + return 0, nil, nil + } + if cap(data) < MRT_COMMON_HEADER_LEN { // read more + return 0, nil, nil + } + //this reads the data + hdr := &MRTHeader{} + errh := hdr.DecodeFromBytes(data[:MRT_COMMON_HEADER_LEN]) + if errh != nil { + return 0, nil, errh + } + totlen := int(hdr.Len + MRT_COMMON_HEADER_LEN) + if len(data) < totlen { //need to read more + return 0, nil, nil + } + return totlen, data[0:totlen], nil +} + +func ParseMRTBody(h *MRTHeader, data []byte) (*MRTMessage, error) { + if len(data) < int(h.Len) { + return nil, fmt.Errorf("Not all MRT message bytes available. expected: %d, actual: %d", int(h.Len), len(data)) + } + msg := &MRTMessage{Header: *h} + switch h.Type { + case TABLE_DUMPv2: + subType := MRTSubTypeTableDumpv2(h.SubType) + rf := bgp.RouteFamily(0) + isAddPath := false + switch subType { + case PEER_INDEX_TABLE: + msg.Body = &PeerIndexTable{} + case RIB_IPV4_UNICAST: + rf = bgp.RF_IPv4_UC + case RIB_IPV4_MULTICAST: + rf = bgp.RF_IPv4_MC + case RIB_IPV6_UNICAST: + rf = bgp.RF_IPv6_UC + case RIB_IPV6_MULTICAST: + rf = bgp.RF_IPv6_MC + case RIB_GENERIC: + case GEO_PEER_TABLE: + msg.Body = &GeoPeerTable{} + case RIB_IPV4_UNICAST_ADDPATH: + rf = bgp.RF_IPv4_UC + isAddPath = true + case RIB_IPV4_MULTICAST_ADDPATH: + rf = bgp.RF_IPv4_MC + isAddPath = true + case RIB_IPV6_UNICAST_ADDPATH: + rf = bgp.RF_IPv6_UC + isAddPath = true + case RIB_IPV6_MULTICAST_ADDPATH: + rf = bgp.RF_IPv6_MC + isAddPath = true + case RIB_GENERIC_ADDPATH: + isAddPath = true + default: + return nil, fmt.Errorf("unsupported table dumpv2 subtype: %v\n", subType) + } + + if msg.Body == nil { + msg.Body = &Rib{ + RouteFamily: rf, + isAddPath: isAddPath, + } + } + case BGP4MP: + subType := MRTSubTypeBGP4MP(h.SubType) + isAS4 := true + switch subType { + case STATE_CHANGE: + isAS4 = false + fallthrough + case STATE_CHANGE_AS4: + msg.Body = &BGP4MPStateChange{ + BGP4MPHeader: &BGP4MPHeader{isAS4: isAS4}, + } + case MESSAGE: + isAS4 = false + fallthrough + case MESSAGE_AS4: + msg.Body = &BGP4MPMessage{ + BGP4MPHeader: &BGP4MPHeader{isAS4: isAS4}, + } + case MESSAGE_LOCAL: + isAS4 = false + fallthrough + case MESSAGE_AS4_LOCAL: + msg.Body = &BGP4MPMessage{ + BGP4MPHeader: &BGP4MPHeader{isAS4: isAS4}, + isLocal: true, + } + case MESSAGE_ADDPATH: + isAS4 = false + fallthrough + case MESSAGE_AS4_ADDPATH: + msg.Body = &BGP4MPMessage{ + BGP4MPHeader: &BGP4MPHeader{isAS4: isAS4}, + isAddPath: true, + } + case MESSAGE_LOCAL_ADDPATH: + isAS4 = false + fallthrough + case MESSAGE_AS4_LOCAL_ADDPATH: + msg.Body = &BGP4MPMessage{ + BGP4MPHeader: &BGP4MPHeader{isAS4: isAS4}, + isLocal: true, + isAddPath: true, + } + default: + return nil, fmt.Errorf("unsupported bgp4mp subtype: %v\n", subType) + } + default: + return nil, fmt.Errorf("unsupported type: %v\n", h.Type) + } + err := msg.Body.DecodeFromBytes(data) + if err != nil { + return nil, err + } + return msg, nil +} diff --git a/pkg/packet/mrt/mrt_test.go b/pkg/packet/mrt/mrt_test.go new file mode 100644 index 00000000..8a710758 --- /dev/null +++ b/pkg/packet/mrt/mrt_test.go @@ -0,0 +1,302 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package mrt + +import ( + "bufio" + "bytes" + "reflect" + "testing" + "time" + + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/stretchr/testify/assert" +) + +func TestMrtHdr(t *testing.T) { + h1, err := NewMRTHeader(10, TABLE_DUMPv2, RIB_IPV4_MULTICAST, 20) + if err != nil { + t.Fatal(err) + } + b1, err := h1.Serialize() + if err != nil { + t.Fatal(err) + } + h2 := &MRTHeader{} + err = h2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(h1, h2), true) +} + +func TestMrtHdrTime(t *testing.T) { + h1, err := NewMRTHeader(10, TABLE_DUMPv2, RIB_IPV4_MULTICAST, 20) + if err != nil { + t.Fatal(err) + } + ttime := time.Unix(10, 0) + htime := h1.GetTime() + t.Logf("this timestamp should be 10s after epoch:%v", htime) + assert.Equal(t, h1.GetTime(), ttime) +} + +func testPeer(t *testing.T, p1 *Peer) { + b1, err := p1.Serialize() + if err != nil { + t.Fatal(err) + } + p2 := &Peer{} + rest, err := p2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(rest), 0) + assert.Equal(t, reflect.DeepEqual(p1, p2), true) +} + +func TestMrtPeer(t *testing.T) { + p := NewPeer("192.168.0.1", "10.0.0.1", 65000, false) + testPeer(t, p) +} + +func TestMrtPeerv6(t *testing.T) { + p := NewPeer("192.168.0.1", "2001::1", 65000, false) + testPeer(t, p) +} + +func TestMrtPeerAS4(t *testing.T) { + p := NewPeer("192.168.0.1", "2001::1", 135500, true) + testPeer(t, p) +} + +func TestMrtPeerIndexTable(t *testing.T) { + p1 := NewPeer("192.168.0.1", "10.0.0.1", 65000, false) + p2 := NewPeer("192.168.0.1", "2001::1", 65000, false) + p3 := NewPeer("192.168.0.1", "2001::1", 135500, true) + pt1 := NewPeerIndexTable("192.168.0.1", "test", []*Peer{p1, p2, p3}) + b1, err := pt1.Serialize() + if err != nil { + t.Fatal(err) + } + pt2 := &PeerIndexTable{} + err = pt2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(pt1, pt2), true) +} + +func TestMrtRibEntry(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{1000}), + bgp.NewAsPathParam(1, []uint16{1001, 1002}), + bgp.NewAsPathParam(2, []uint16{1003, 1004}), + } + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(3), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("129.1.1.2"), + bgp.NewPathAttributeMultiExitDisc(1 << 20), + bgp.NewPathAttributeLocalPref(1 << 22), + } + + e1 := NewRibEntry(1, uint32(time.Now().Unix()), 0, p, false) + b1, err := e1.Serialize() + if err != nil { + t.Fatal(err) + } + + e2 := &RibEntry{} + rest, err := e2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(rest), 0) + assert.Equal(t, reflect.DeepEqual(e1, e2), true) +} + +func TestMrtRibEntryWithAddPath(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{1000}), + bgp.NewAsPathParam(1, []uint16{1001, 1002}), + bgp.NewAsPathParam(2, []uint16{1003, 1004}), + } + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(3), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("129.1.1.2"), + bgp.NewPathAttributeMultiExitDisc(1 << 20), + bgp.NewPathAttributeLocalPref(1 << 22), + } + e1 := NewRibEntry(1, uint32(time.Now().Unix()), 200, p, true) + b1, err := e1.Serialize() + if err != nil { + t.Fatal(err) + } + + e2 := &RibEntry{isAddPath: true} + rest, err := e2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, len(rest), 0) + assert.Equal(t, reflect.DeepEqual(e1, e2), true) +} + +func TestMrtRib(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{1000}), + bgp.NewAsPathParam(1, []uint16{1001, 1002}), + bgp.NewAsPathParam(2, []uint16{1003, 1004}), + } + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(3), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("129.1.1.2"), + bgp.NewPathAttributeMultiExitDisc(1 << 20), + bgp.NewPathAttributeLocalPref(1 << 22), + } + + e1 := NewRibEntry(1, uint32(time.Now().Unix()), 0, p, false) + e2 := NewRibEntry(2, uint32(time.Now().Unix()), 0, p, false) + e3 := NewRibEntry(3, uint32(time.Now().Unix()), 0, p, false) + + r1 := NewRib(1, bgp.NewIPAddrPrefix(24, "192.168.0.0"), []*RibEntry{e1, e2, e3}) + b1, err := r1.Serialize() + if err != nil { + t.Fatal(err) + } + r2 := &Rib{ + RouteFamily: bgp.RF_IPv4_UC, + } + err = r2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(r1, r2), true) +} + +func TestMrtRibWithAddPath(t *testing.T) { + aspath1 := []bgp.AsPathParamInterface{ + bgp.NewAsPathParam(2, []uint16{1000}), + bgp.NewAsPathParam(1, []uint16{1001, 1002}), + bgp.NewAsPathParam(2, []uint16{1003, 1004}), + } + + p := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(3), + bgp.NewPathAttributeAsPath(aspath1), + bgp.NewPathAttributeNextHop("129.1.1.2"), + bgp.NewPathAttributeMultiExitDisc(1 << 20), + bgp.NewPathAttributeLocalPref(1 << 22), + } + + e1 := NewRibEntry(1, uint32(time.Now().Unix()), 100, p, true) + e2 := NewRibEntry(2, uint32(time.Now().Unix()), 200, p, true) + e3 := NewRibEntry(3, uint32(time.Now().Unix()), 300, p, true) + + r1 := NewRib(1, bgp.NewIPAddrPrefix(24, "192.168.0.0"), []*RibEntry{e1, e2, e3}) + b1, err := r1.Serialize() + if err != nil { + t.Fatal(err) + } + r2 := &Rib{ + RouteFamily: bgp.RF_IPv4_UC, + isAddPath: true, + } + err = r2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(r1, r2), true) +} + +func TestMrtGeoPeerTable(t *testing.T) { + p1 := NewGeoPeer("192.168.0.1", 28.031157, 86.899684) + p2 := NewGeoPeer("192.168.0.1", 35.360556, 138.727778) + pt1 := NewGeoPeerTable("192.168.0.1", 12.345678, 98.765432, []*GeoPeer{p1, p2}) + b1, err := pt1.Serialize() + if err != nil { + t.Fatal(err) + } + pt2 := &GeoPeerTable{} + err = pt2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(pt1, pt2), true) +} + +func TestMrtBgp4mpStateChange(t *testing.T) { + c1 := NewBGP4MPStateChange(65000, 65001, 1, "192.168.0.1", "192.168.0.2", false, ACTIVE, ESTABLISHED) + b1, err := c1.Serialize() + if err != nil { + t.Fatal(err) + } + c2 := &BGP4MPStateChange{BGP4MPHeader: &BGP4MPHeader{}} + err = c2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + _, err = c2.Serialize() + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(c1, c2), true) +} + +func TestMrtBgp4mpMessage(t *testing.T) { + msg := bgp.NewBGPKeepAliveMessage() + m1 := NewBGP4MPMessage(65000, 65001, 1, "192.168.0.1", "192.168.0.2", false, msg) + b1, err := m1.Serialize() + if err != nil { + t.Fatal(err) + } + m2 := &BGP4MPMessage{BGP4MPHeader: &BGP4MPHeader{}} + err = m2.DecodeFromBytes(b1) + if err != nil { + t.Fatal(err) + } + assert.Equal(t, reflect.DeepEqual(m1, m2), true) +} + +func TestMrtSplit(t *testing.T) { + var b bytes.Buffer + numwrite, numread := 10, 0 + for i := 0; i < numwrite; i++ { + msg := bgp.NewBGPKeepAliveMessage() + m1 := NewBGP4MPMessage(65000, 65001, 1, "192.168.0.1", "192.168.0.2", false, msg) + mm, _ := NewMRTMessage(1234, BGP4MP, MESSAGE, m1) + b1, err := mm.Serialize() + if err != nil { + t.Fatal(err) + } + b.Write(b1) + } + t.Logf("wrote %d serialized MRT keepalives in the buffer", numwrite) + r := bytes.NewReader(b.Bytes()) + scanner := bufio.NewScanner(r) + scanner.Split(SplitMrt) + for scanner.Scan() { + numread += 1 + } + t.Logf("scanner scanned %d serialized keepalives from the buffer", numread) + assert.Equal(t, numwrite, numread) +} diff --git a/pkg/packet/rtr/rtr.go b/pkg/packet/rtr/rtr.go new file mode 100644 index 00000000..902f1e62 --- /dev/null +++ b/pkg/packet/rtr/rtr.go @@ -0,0 +1,392 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtr + +import ( + "encoding/binary" + "fmt" + "net" +) + +const ( + RPKI_DEFAULT_PORT = 323 +) + +const ( + RTR_SERIAL_NOTIFY = iota + RTR_SERIAL_QUERY + RTR_RESET_QUERY + RTR_CACHE_RESPONSE + RTR_IPV4_PREFIX + _ + RTR_IPV6_PREFIX + RTR_END_OF_DATA + RTR_CACHE_RESET + _ + RTR_ERROR_REPORT +) + +const ( + RTR_SERIAL_NOTIFY_LEN = 12 + RTR_SERIAL_QUERY_LEN = 12 + RTR_RESET_QUERY_LEN = 8 + RTR_CACHE_RESPONSE_LEN = 8 + RTR_IPV4_PREFIX_LEN = 20 + RTR_IPV6_PREFIX_LEN = 32 + RTR_END_OF_DATA_LEN = 12 + RTR_CACHE_RESET_LEN = 8 + RTR_MIN_LEN = 8 + RTR_ERROR_REPORT_ERR_PDU_LEN = 4 + RTR_ERROR_REPORT_ERR_TEXT_LEN = 4 +) + +const ( + WITHDRAWAL uint8 = iota + ANNOUNCEMENT +) + +const ( + CORRUPT_DATA uint16 = iota + INTERNAL_ERROR + NO_DATA_AVAILABLE + INVALID_REQUEST + UNSUPPORTED_PROTOCOL_VERSION + UNSUPPORTED_PDU_TYPE + WITHDRAWAL_OF_UNKNOWN_RECORD + DUPLICATE_ANNOUNCEMENT_RECORD +) + +type RTRMessage interface { + DecodeFromBytes([]byte) error + Serialize() ([]byte, error) +} + +type RTRCommon struct { + Version uint8 + Type uint8 + SessionID uint16 + Len uint32 + SerialNumber uint32 +} + +func (m *RTRCommon) DecodeFromBytes(data []byte) error { + m.Version = data[0] + m.Type = data[1] + m.SessionID = binary.BigEndian.Uint16(data[2:4]) + m.Len = binary.BigEndian.Uint32(data[4:8]) + m.SerialNumber = binary.BigEndian.Uint32(data[8:12]) + return nil +} + +func (m *RTRCommon) Serialize() ([]byte, error) { + data := make([]byte, m.Len) + data[0] = m.Version + data[1] = m.Type + binary.BigEndian.PutUint16(data[2:4], m.SessionID) + binary.BigEndian.PutUint32(data[4:8], m.Len) + binary.BigEndian.PutUint32(data[8:12], m.SerialNumber) + return data, nil +} + +type RTRSerialNotify struct { + RTRCommon +} + +func NewRTRSerialNotify(id uint16, sn uint32) *RTRSerialNotify { + return &RTRSerialNotify{ + RTRCommon{ + Type: RTR_SERIAL_NOTIFY, + SessionID: id, + Len: RTR_SERIAL_NOTIFY_LEN, + SerialNumber: sn, + }, + } +} + +type RTRSerialQuery struct { + RTRCommon +} + +func NewRTRSerialQuery(id uint16, sn uint32) *RTRSerialQuery { + return &RTRSerialQuery{ + RTRCommon{ + Type: RTR_SERIAL_QUERY, + SessionID: id, + Len: RTR_SERIAL_QUERY_LEN, + SerialNumber: sn, + }, + } +} + +type RTRReset struct { + Version uint8 + Type uint8 + Len uint32 +} + +func (m *RTRReset) DecodeFromBytes(data []byte) error { + m.Version = data[0] + m.Type = data[1] + m.Len = binary.BigEndian.Uint32(data[4:8]) + return nil +} + +func (m *RTRReset) Serialize() ([]byte, error) { + data := make([]byte, m.Len) + data[0] = m.Version + data[1] = m.Type + binary.BigEndian.PutUint32(data[4:8], m.Len) + return data, nil +} + +type RTRResetQuery struct { + RTRReset +} + +func NewRTRResetQuery() *RTRResetQuery { + return &RTRResetQuery{ + RTRReset{ + Type: RTR_RESET_QUERY, + Len: RTR_RESET_QUERY_LEN, + }, + } +} + +type RTRCacheResponse struct { + Version uint8 + Type uint8 + SessionID uint16 + Len uint32 +} + +func (m *RTRCacheResponse) DecodeFromBytes(data []byte) error { + m.Version = data[0] + m.Type = data[1] + m.SessionID = binary.BigEndian.Uint16(data[2:4]) + m.Len = binary.BigEndian.Uint32(data[4:8]) + return nil +} + +func (m *RTRCacheResponse) Serialize() ([]byte, error) { + data := make([]byte, m.Len) + data[0] = m.Version + data[1] = m.Type + binary.BigEndian.PutUint16(data[2:4], m.SessionID) + binary.BigEndian.PutUint32(data[4:8], m.Len) + return data, nil +} + +func NewRTRCacheResponse(id uint16) *RTRCacheResponse { + return &RTRCacheResponse{ + Type: RTR_CACHE_RESPONSE, + SessionID: id, + Len: RTR_CACHE_RESPONSE_LEN, + } +} + +type RTRIPPrefix struct { + Version uint8 + Type uint8 + Len uint32 + Flags uint8 + PrefixLen uint8 + MaxLen uint8 + Prefix net.IP + AS uint32 +} + +func (m *RTRIPPrefix) DecodeFromBytes(data []byte) error { + m.Version = data[0] + m.Type = data[1] + m.Len = binary.BigEndian.Uint32(data[4:8]) + m.Flags = data[8] + m.PrefixLen = data[9] + m.MaxLen = data[10] + if m.Type == RTR_IPV4_PREFIX { + m.Prefix = net.IP(data[12:16]).To4() + m.AS = binary.BigEndian.Uint32(data[16:20]) + } else { + m.Prefix = net.IP(data[12:28]).To16() + m.AS = binary.BigEndian.Uint32(data[28:32]) + } + return nil +} + +func (m *RTRIPPrefix) Serialize() ([]byte, error) { + data := make([]byte, m.Len) + data[0] = m.Version + data[1] = m.Type + binary.BigEndian.PutUint32(data[4:8], m.Len) + data[8] = m.Flags + data[9] = m.PrefixLen + data[10] = m.MaxLen + if m.Type == RTR_IPV4_PREFIX { + copy(data[12:16], m.Prefix.To4()) + binary.BigEndian.PutUint32(data[16:20], m.AS) + } else { + copy(data[12:28], m.Prefix.To16()) + binary.BigEndian.PutUint32(data[28:32], m.AS) + } + return data, nil +} + +func NewRTRIPPrefix(prefix net.IP, prefixLen, maxLen uint8, as uint32, flags uint8) *RTRIPPrefix { + var pduType uint8 + var pduLen uint32 + if prefix.To4() != nil && prefixLen <= 32 { + pduType = RTR_IPV4_PREFIX + pduLen = RTR_IPV4_PREFIX_LEN + } else { + pduType = RTR_IPV6_PREFIX + pduLen = RTR_IPV6_PREFIX_LEN + } + + return &RTRIPPrefix{ + Type: pduType, + Len: pduLen, + Flags: flags, + PrefixLen: prefixLen, + MaxLen: maxLen, + Prefix: prefix, + AS: as, + } +} + +type RTREndOfData struct { + RTRCommon +} + +func NewRTREndOfData(id uint16, sn uint32) *RTREndOfData { + return &RTREndOfData{ + RTRCommon{ + Type: RTR_END_OF_DATA, + SessionID: id, + Len: RTR_END_OF_DATA_LEN, + SerialNumber: sn, + }, + } +} + +type RTRCacheReset struct { + RTRReset +} + +func NewRTRCacheReset() *RTRCacheReset { + return &RTRCacheReset{ + RTRReset{ + Type: RTR_CACHE_RESET, + Len: RTR_CACHE_RESET_LEN, + }, + } +} + +type RTRErrorReport struct { + Version uint8 + Type uint8 + ErrorCode uint16 + Len uint32 + PDULen uint32 + PDU []byte + TextLen uint32 + Text []byte +} + +func (m *RTRErrorReport) DecodeFromBytes(data []byte) error { + m.Version = data[0] + m.Type = data[1] + m.ErrorCode = binary.BigEndian.Uint16(data[2:4]) + m.Len = binary.BigEndian.Uint32(data[4:8]) + m.PDULen = binary.BigEndian.Uint32(data[8:12]) + m.PDU = make([]byte, m.PDULen) + copy(m.PDU, data[12:12+m.PDULen]) + m.TextLen = binary.BigEndian.Uint32(data[12+m.PDULen : 16+m.PDULen]) + m.Text = make([]byte, m.TextLen) + copy(m.Text, data[16+m.PDULen:]) + return nil +} + +func (m *RTRErrorReport) Serialize() ([]byte, error) { + data := make([]byte, m.Len) + data[0] = m.Version + data[1] = m.Type + binary.BigEndian.PutUint16(data[2:4], m.ErrorCode) + binary.BigEndian.PutUint32(data[4:8], m.Len) + binary.BigEndian.PutUint32(data[8:12], m.PDULen) + copy(data[12:], m.PDU) + binary.BigEndian.PutUint32(data[12+m.PDULen:16+m.PDULen], m.TextLen) + copy(data[16+m.PDULen:], m.Text) + return data, nil +} + +func NewRTRErrorReport(errCode uint16, errPDU []byte, errMsg []byte) *RTRErrorReport { + pdu := &RTRErrorReport{Type: RTR_ERROR_REPORT, ErrorCode: errCode} + if errPDU != nil { + if errPDU[1] == RTR_ERROR_REPORT { + return nil + } + pdu.PDULen = uint32(len(errPDU)) + pdu.PDU = errPDU + } + if errMsg != nil { + pdu.Text = errMsg + pdu.TextLen = uint32(len(errMsg)) + } + pdu.Len = uint32(RTR_MIN_LEN) + uint32(RTR_ERROR_REPORT_ERR_PDU_LEN) + pdu.PDULen + uint32(RTR_ERROR_REPORT_ERR_TEXT_LEN) + pdu.TextLen + return pdu +} + +func SplitRTR(data []byte, atEOF bool) (advance int, token []byte, err error) { + if atEOF && len(data) == 0 || len(data) < RTR_MIN_LEN { + return 0, nil, nil + } + + totalLen := binary.BigEndian.Uint32(data[4:8]) + if totalLen < RTR_MIN_LEN { + return 0, nil, fmt.Errorf("Invalid length: %d", totalLen) + } + if uint32(len(data)) < totalLen { + return 0, nil, nil + } + return int(totalLen), data[0:totalLen], nil +} + +func ParseRTR(data []byte) (RTRMessage, error) { + var msg RTRMessage + switch data[1] { + case RTR_SERIAL_NOTIFY: + msg = &RTRSerialNotify{} + case RTR_SERIAL_QUERY: + msg = &RTRSerialQuery{} + case RTR_RESET_QUERY: + msg = &RTRResetQuery{} + case RTR_CACHE_RESPONSE: + msg = &RTRCacheResponse{} + case RTR_IPV4_PREFIX: + msg = &RTRIPPrefix{} + case RTR_IPV6_PREFIX: + msg = &RTRIPPrefix{} + case RTR_END_OF_DATA: + msg = &RTREndOfData{} + case RTR_CACHE_RESET: + msg = &RTRCacheReset{} + case RTR_ERROR_REPORT: + msg = &RTRErrorReport{} + default: + return nil, fmt.Errorf("unknown RTR message type %d:", data[1]) + } + err := msg.DecodeFromBytes(data) + return msg, err +} diff --git a/pkg/packet/rtr/rtr_test.go b/pkg/packet/rtr/rtr_test.go new file mode 100644 index 00000000..08f930b2 --- /dev/null +++ b/pkg/packet/rtr/rtr_test.go @@ -0,0 +1,118 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package rtr + +import ( + "encoding/hex" + "math/rand" + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func verifyRTRMessage(t *testing.T, m1 RTRMessage) { + buf1, _ := m1.Serialize() + m2, err := ParseRTR(buf1) + require.NoError(t, err) + + buf2, err := m2.Serialize() + require.NoError(t, err) + + assert.Equal(t, buf1, buf2, "buf1: %v buf2: %v", hex.EncodeToString(buf1), hex.EncodeToString(buf2)) +} + +func randUint32() uint32 { + rand.Seed(time.Now().UnixNano()) + return rand.Uint32() +} + +func Test_RTRSerialNotify(t *testing.T) { + id := uint16(time.Now().Unix()) + sn := randUint32() + verifyRTRMessage(t, NewRTRSerialNotify(id, sn)) +} + +func Test_RTRSerialQuery(t *testing.T) { + id := uint16(time.Now().Unix()) + sn := randUint32() + verifyRTRMessage(t, NewRTRSerialQuery(id, sn)) +} + +func Test_RTRResetQuery(t *testing.T) { + verifyRTRMessage(t, NewRTRResetQuery()) +} + +func Test_RTRCacheResponse(t *testing.T) { + id := uint16(time.Now().Unix()) + verifyRTRMessage(t, NewRTRCacheResponse(id)) +} + +type rtrIPPrefixTestCase struct { + pString string + pLen uint8 + mLen uint8 + asn uint32 + flags uint8 +} + +var rtrIPPrefixTestCases = []rtrIPPrefixTestCase{ + {"192.168.0.0", 16, 32, 65001, ANNOUNCEMENT}, + {"192.168.0.0", 16, 32, 65001, WITHDRAWAL}, + {"2001:db8::", 32, 128, 65001, ANNOUNCEMENT}, + {"2001:db8::", 32, 128, 65001, WITHDRAWAL}, + {"::ffff:0.0.0.0", 96, 128, 65001, ANNOUNCEMENT}, + {"::ffff:0.0.0.0", 96, 128, 65001, WITHDRAWAL}, +} + +func Test_RTRIPPrefix(t *testing.T) { + for i := range rtrIPPrefixTestCases { + test := &rtrIPPrefixTestCases[i] + addr := net.ParseIP(test.pString) + verifyRTRMessage(t, NewRTRIPPrefix(addr, test.pLen, test.mLen, test.asn, test.flags)) + } +} + +func Test_RTREndOfData(t *testing.T) { + id := uint16(time.Now().Unix()) + sn := randUint32() + verifyRTRMessage(t, NewRTREndOfData(id, sn)) +} + +func Test_RTRCacheReset(t *testing.T) { + verifyRTRMessage(t, NewRTRCacheReset()) +} + +func Test_RTRErrorReport(t *testing.T) { + errPDU, _ := NewRTRResetQuery().Serialize() + errText1 := []byte("Couldn't send CacheResponce PDU") + errText2 := []byte("Wrong Length of PDU: 10 bytes") + + // See 5.10 ErrorReport in RFC6810 + // when it doesn't have both "erroneous PDU" and "Arbitrary Text" + verifyRTRMessage(t, NewRTRErrorReport(NO_DATA_AVAILABLE, nil, nil)) + + // when it has "erroneous PDU" + verifyRTRMessage(t, NewRTRErrorReport(UNSUPPORTED_PROTOCOL_VERSION, errPDU, nil)) + + // when it has "ArbitaryText" + verifyRTRMessage(t, NewRTRErrorReport(INTERNAL_ERROR, nil, errText1)) + + // when it has both "erroneous PDU" and "Arbitrary Text" + verifyRTRMessage(t, NewRTRErrorReport(CORRUPT_DATA, errPDU, errText2)) +} diff --git a/pkg/server/bmp.go b/pkg/server/bmp.go new file mode 100644 index 00000000..7904fae1 --- /dev/null +++ b/pkg/server/bmp.go @@ -0,0 +1,358 @@ +// Copyright (C) 2015-2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "fmt" + "net" + "strconv" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/packet/bmp" + log "github.com/sirupsen/logrus" +) + +type ribout map[string][]*table.Path + +func newribout() ribout { + return make(map[string][]*table.Path) +} + +// return true if we need to send the path to the BMP server +func (r ribout) update(p *table.Path) bool { + key := p.GetNlri().String() // TODO expose (*Path).getPrefix() + l := r[key] + if p.IsWithdraw { + if len(l) == 0 { + return false + } + n := make([]*table.Path, 0, len(l)) + for _, q := range l { + if p.GetSource() == q.GetSource() { + continue + } + n = append(n, q) + } + if len(n) == 0 { + delete(r, key) + } else { + r[key] = n + } + return true + } + + if len(l) == 0 { + r[key] = []*table.Path{p} + return true + } + + doAppend := true + for idx, q := range l { + if p.GetSource() == q.GetSource() { + // if we have sent the same path, don't send it again + if p.Equal(q) { + return false + } + l[idx] = p + doAppend = false + } + } + if doAppend { + r[key] = append(r[key], p) + } + return true +} + +func (b *bmpClient) tryConnect() *net.TCPConn { + interval := 1 + for { + log.WithFields(log.Fields{"Topic": "bmp"}).Debugf("Connecting BMP server:%s", b.host) + conn, err := net.Dial("tcp", b.host) + if err != nil { + select { + case <-b.dead: + return nil + default: + } + time.Sleep(time.Duration(interval) * time.Second) + if interval < 30 { + interval *= 2 + } + } else { + log.WithFields(log.Fields{"Topic": "bmp"}).Infof("BMP server is connected:%s", b.host) + return conn.(*net.TCPConn) + } + } +} + +func (b *bmpClient) Stop() { + close(b.dead) +} + +func (b *bmpClient) loop() { + for { + conn := b.tryConnect() + if conn == nil { + break + } + + if func() bool { + ops := []WatchOption{WatchPeerState(true)} + if b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_BOTH { + log.WithFields( + log.Fields{"Topic": "bmp"}, + ).Warn("both option for route-monitoring-policy is obsoleted") + } + if b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_PRE_POLICY || b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_ALL { + ops = append(ops, WatchUpdate(true)) + } + if b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_POST_POLICY || b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_ALL { + ops = append(ops, WatchPostUpdate(true)) + } + if b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_LOCAL_RIB || b.c.RouteMonitoringPolicy == config.BMP_ROUTE_MONITORING_POLICY_TYPE_ALL { + ops = append(ops, WatchBestPath(true)) + } + if b.c.RouteMirroringEnabled { + ops = append(ops, WatchMessage(false)) + } + w := b.s.Watch(ops...) + defer w.Stop() + + var tickerCh <-chan time.Time + if b.c.StatisticsTimeout == 0 { + log.WithFields(log.Fields{"Topic": "bmp"}).Debug("statistics reports disabled") + } else { + t := time.NewTicker(time.Duration(b.c.StatisticsTimeout) * time.Second) + defer t.Stop() + tickerCh = t.C + } + + write := func(msg *bmp.BMPMessage) error { + buf, _ := msg.Serialize() + _, err := conn.Write(buf) + if err != nil { + log.Warnf("failed to write to bmp server %s", b.host) + } + return err + } + + if err := write(bmp.NewBMPInitiation([]bmp.BMPInfoTLVInterface{})); err != nil { + return false + } + + for { + select { + case ev := <-w.Event(): + switch msg := ev.(type) { + case *WatchEventUpdate: + info := &table.PeerInfo{ + Address: msg.PeerAddress, + AS: msg.PeerAS, + ID: msg.PeerID, + } + if msg.Payload == nil { + var pathList []*table.Path + if msg.Init { + pathList = msg.PathList + } else { + for _, p := range msg.PathList { + if b.ribout.update(p) { + pathList = append(pathList, p) + } + } + } + for _, path := range pathList { + for _, u := range table.CreateUpdateMsgFromPaths([]*table.Path{path}) { + payload, _ := u.Serialize() + if err := write(bmpPeerRoute(bmp.BMP_PEER_TYPE_GLOBAL, msg.PostPolicy, 0, true, info, msg.Timestamp.Unix(), payload)); err != nil { + return false + } + } + } + } else { + if err := write(bmpPeerRoute(bmp.BMP_PEER_TYPE_GLOBAL, msg.PostPolicy, 0, msg.FourBytesAs, info, msg.Timestamp.Unix(), msg.Payload)); err != nil { + return false + } + } + case *WatchEventBestPath: + info := &table.PeerInfo{ + Address: net.ParseIP("0.0.0.0").To4(), + AS: b.s.bgpConfig.Global.Config.As, + ID: net.ParseIP(b.s.bgpConfig.Global.Config.RouterId).To4(), + } + for _, p := range msg.PathList { + u := table.CreateUpdateMsgFromPaths([]*table.Path{p})[0] + if payload, err := u.Serialize(); err != nil { + return false + } else if err = write(bmpPeerRoute(bmp.BMP_PEER_TYPE_LOCAL_RIB, false, 0, true, info, p.GetTimestamp().Unix(), payload)); err != nil { + return false + } + } + case *WatchEventPeerState: + if msg.State == bgp.BGP_FSM_ESTABLISHED { + if err := write(bmpPeerUp(msg, bmp.BMP_PEER_TYPE_GLOBAL, false, 0)); err != nil { + return false + } + } else { + if err := write(bmpPeerDown(msg, bmp.BMP_PEER_TYPE_GLOBAL, false, 0)); err != nil { + return false + } + } + case *WatchEventMessage: + info := &table.PeerInfo{ + Address: msg.PeerAddress, + AS: msg.PeerAS, + ID: msg.PeerID, + } + if err := write(bmpPeerRouteMirroring(bmp.BMP_PEER_TYPE_GLOBAL, 0, info, msg.Timestamp.Unix(), msg.Message)); err != nil { + return false + } + } + case <-tickerCh: + neighborList := b.s.GetNeighbor("", true) + for _, n := range neighborList { + if n.State.SessionState != config.SESSION_STATE_ESTABLISHED { + continue + } + if err := write(bmpPeerStats(bmp.BMP_PEER_TYPE_GLOBAL, 0, 0, n)); err != nil { + return false + } + } + case <-b.dead: + term := bmp.NewBMPTermination([]bmp.BMPTermTLVInterface{ + bmp.NewBMPTermTLV16(bmp.BMP_TERM_TLV_TYPE_REASON, bmp.BMP_TERM_REASON_PERMANENTLY_ADMIN), + }) + if err := write(term); err != nil { + return false + } + conn.Close() + return true + } + } + }() { + return + } + } +} + +type bmpClient struct { + s *BgpServer + dead chan struct{} + host string + c *config.BmpServerConfig + ribout ribout +} + +func bmpPeerUp(ev *WatchEventPeerState, t uint8, policy bool, pd uint64) *bmp.BMPMessage { + var flags uint8 = 0 + if policy { + flags |= bmp.BMP_PEER_FLAG_POST_POLICY + } + ph := bmp.NewBMPPeerHeader(t, flags, pd, ev.PeerAddress.String(), ev.PeerAS, ev.PeerID.String(), float64(ev.Timestamp.Unix())) + return bmp.NewBMPPeerUpNotification(*ph, ev.LocalAddress.String(), ev.LocalPort, ev.PeerPort, ev.SentOpen, ev.RecvOpen) +} + +func bmpPeerDown(ev *WatchEventPeerState, t uint8, policy bool, pd uint64) *bmp.BMPMessage { + var flags uint8 = 0 + if policy { + flags |= bmp.BMP_PEER_FLAG_POST_POLICY + } + ph := bmp.NewBMPPeerHeader(t, flags, pd, ev.PeerAddress.String(), ev.PeerAS, ev.PeerID.String(), float64(ev.Timestamp.Unix())) + return bmp.NewBMPPeerDownNotification(*ph, uint8(ev.StateReason.PeerDownReason), ev.StateReason.BGPNotification, ev.StateReason.Data) +} + +func bmpPeerRoute(t uint8, policy bool, pd uint64, fourBytesAs bool, peeri *table.PeerInfo, timestamp int64, payload []byte) *bmp.BMPMessage { + var flags uint8 = 0 + if policy { + flags |= bmp.BMP_PEER_FLAG_POST_POLICY + } + if !fourBytesAs { + flags |= bmp.BMP_PEER_FLAG_TWO_AS + } + ph := bmp.NewBMPPeerHeader(t, flags, pd, peeri.Address.String(), peeri.AS, peeri.ID.String(), float64(timestamp)) + m := bmp.NewBMPRouteMonitoring(*ph, nil) + body := m.Body.(*bmp.BMPRouteMonitoring) + body.BGPUpdatePayload = payload + return m +} + +func bmpPeerStats(peerType uint8, peerDist uint64, timestamp int64, neighConf *config.Neighbor) *bmp.BMPMessage { + var peerFlags uint8 = 0 + ph := bmp.NewBMPPeerHeader(peerType, peerFlags, peerDist, neighConf.State.NeighborAddress, neighConf.State.PeerAs, neighConf.State.RemoteRouterId, float64(timestamp)) + return bmp.NewBMPStatisticsReport( + *ph, + []bmp.BMPStatsTLVInterface{ + bmp.NewBMPStatsTLV64(bmp.BMP_STAT_TYPE_ADJ_RIB_IN, uint64(neighConf.State.AdjTable.Accepted)), + bmp.NewBMPStatsTLV64(bmp.BMP_STAT_TYPE_LOC_RIB, uint64(neighConf.State.AdjTable.Advertised+neighConf.State.AdjTable.Filtered)), + bmp.NewBMPStatsTLV32(bmp.BMP_STAT_TYPE_WITHDRAW_UPDATE, neighConf.State.Messages.Received.WithdrawUpdate), + bmp.NewBMPStatsTLV32(bmp.BMP_STAT_TYPE_WITHDRAW_PREFIX, neighConf.State.Messages.Received.WithdrawPrefix), + }, + ) +} + +func bmpPeerRouteMirroring(peerType uint8, peerDist uint64, peerInfo *table.PeerInfo, timestamp int64, msg *bgp.BGPMessage) *bmp.BMPMessage { + var peerFlags uint8 = 0 + ph := bmp.NewBMPPeerHeader(peerType, peerFlags, peerDist, peerInfo.Address.String(), peerInfo.AS, peerInfo.ID.String(), float64(timestamp)) + return bmp.NewBMPRouteMirroring( + *ph, + []bmp.BMPRouteMirrTLVInterface{ + // RFC7854: BGP Message TLV MUST occur last in the list of TLVs + bmp.NewBMPRouteMirrTLVBGPMsg(bmp.BMP_ROUTE_MIRRORING_TLV_TYPE_BGP_MSG, msg), + }, + ) +} + +func (b *bmpClientManager) addServer(c *config.BmpServerConfig) error { + host := net.JoinHostPort(c.Address, strconv.Itoa(int(c.Port))) + if _, y := b.clientMap[host]; y { + return fmt.Errorf("bmp client %s is already configured", host) + } + b.clientMap[host] = &bmpClient{ + s: b.s, + dead: make(chan struct{}), + host: host, + c: c, + ribout: newribout(), + } + go b.clientMap[host].loop() + return nil +} + +func (b *bmpClientManager) deleteServer(c *config.BmpServerConfig) error { + host := net.JoinHostPort(c.Address, strconv.Itoa(int(c.Port))) + if c, y := b.clientMap[host]; !y { + return fmt.Errorf("bmp client %s isn't found", host) + } else { + c.Stop() + delete(b.clientMap, host) + } + return nil +} + +type bmpClientManager struct { + s *BgpServer + clientMap map[string]*bmpClient +} + +func newBmpClientManager(s *BgpServer) *bmpClientManager { + return &bmpClientManager{ + s: s, + clientMap: make(map[string]*bmpClient), + } +} diff --git a/pkg/server/collector.go b/pkg/server/collector.go new file mode 100644 index 00000000..c219b215 --- /dev/null +++ b/pkg/server/collector.go @@ -0,0 +1,222 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "fmt" + "time" + + "github.com/influxdata/influxdb/client/v2" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + log "github.com/sirupsen/logrus" +) + +type Collector struct { + s *BgpServer + url string + dbName string + interval uint64 + client client.Client +} + +const ( + MEATUREMENT_UPDATE = "update" + MEATUREMENT_PEER = "peer" + MEATUREMENT_TABLE = "table" +) + +func (c *Collector) writePoints(points []*client.Point) error { + bp, _ := client.NewBatchPoints(client.BatchPointsConfig{ + Database: c.dbName, + Precision: "ms", + }) + bp.AddPoints(points) + return c.client.Write(bp) +} + +func (c *Collector) writePeer(msg *WatchEventPeerState) error { + var state string + switch msg.State { + case bgp.BGP_FSM_ESTABLISHED: + state = "Established" + case bgp.BGP_FSM_IDLE: + state = "Idle" + default: + return fmt.Errorf("unexpected fsm state %v", msg.State) + } + + tags := map[string]string{ + "PeerAddress": msg.PeerAddress.String(), + "PeerAS": fmt.Sprintf("%v", msg.PeerAS), + "State": state, + } + + fields := map[string]interface{}{ + "PeerID": msg.PeerID.String(), + } + + pt, err := client.NewPoint(MEATUREMENT_PEER, tags, fields, msg.Timestamp) + if err != nil { + return err + } + return c.writePoints([]*client.Point{pt}) +} + +func path2data(path *table.Path) (map[string]interface{}, map[string]string) { + fields := map[string]interface{}{ + "RouterID": path.GetSource().ID, + } + if asPath := path.GetAsPath(); asPath != nil { + fields["ASPath"] = asPath.String() + } + if origin, err := path.GetOrigin(); err == nil { + typ := "-" + switch origin { + case bgp.BGP_ORIGIN_ATTR_TYPE_IGP: + typ = "i" + case bgp.BGP_ORIGIN_ATTR_TYPE_EGP: + typ = "e" + case bgp.BGP_ORIGIN_ATTR_TYPE_INCOMPLETE: + typ = "?" + } + fields["Origin"] = typ + } + if med, err := path.GetMed(); err == nil { + fields["Med"] = med + } + + tags := map[string]string{ + "PeerAddress": path.GetSource().Address.String(), + "PeerAS": fmt.Sprintf("%v", path.GetSource().AS), + "Timestamp": path.GetTimestamp().String(), + } + if nexthop := path.GetNexthop(); len(nexthop) > 0 { + fields["NextHop"] = nexthop.String() + } + if originAS := path.GetSourceAs(); originAS != 0 { + fields["OriginAS"] = fmt.Sprintf("%v", originAS) + } + + if err := bgp.FlatUpdate(tags, path.GetNlri().Flat()); err != nil { + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Error("NLRI FlatUpdate failed") + } + for _, p := range path.GetPathAttrs() { + if err := bgp.FlatUpdate(tags, p.Flat()); err != nil { + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Error("PathAttr FlatUpdate failed") + } + } + return fields, tags +} + +func (c *Collector) writeUpdate(msg *WatchEventUpdate) error { + if len(msg.PathList) == 0 { + // EOR + return nil + } + now := time.Now() + points := make([]*client.Point, 0, len(msg.PathList)) + for _, path := range msg.PathList { + fields, tags := path2data(path) + tags["Withdraw"] = fmt.Sprintf("%v", path.IsWithdraw) + pt, err := client.NewPoint(MEATUREMENT_UPDATE, tags, fields, now) + if err != nil { + return fmt.Errorf("failed to write update, %v", err) + } + points = append(points, pt) + } + return c.writePoints(points) +} + +func (c *Collector) writeTable(msg *WatchEventAdjIn) error { + now := time.Now() + points := make([]*client.Point, 0, len(msg.PathList)) + for _, path := range msg.PathList { + fields, tags := path2data(path) + pt, err := client.NewPoint(MEATUREMENT_TABLE, tags, fields, now) + if err != nil { + return fmt.Errorf("failed to write table, %v", err) + } + points = append(points, pt) + } + return c.writePoints(points) +} + +func (c *Collector) loop() { + w := c.s.Watch(WatchPeerState(true), WatchUpdate(false)) + defer w.Stop() + + ticker := func() *time.Ticker { + if c.interval == 0 { + return &time.Ticker{} + } + return time.NewTicker(time.Second * time.Duration(c.interval)) + }() + + for { + select { + case <-ticker.C: + w.Generate(WATCH_EVENT_TYPE_PRE_UPDATE) + case ev := <-w.Event(): + switch msg := ev.(type) { + case *WatchEventUpdate: + if err := c.writeUpdate(msg); err != nil { + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Error("Failed to write update event message") + } + case *WatchEventPeerState: + if err := c.writePeer(msg); err != nil { + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Error("Failed to write state changed event message") + } + case *WatchEventAdjIn: + if err := c.writeTable(msg); err != nil { + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Error("Failed to write Adj-In event message") + } + } + } + } +} + +func NewCollector(s *BgpServer, url, dbName string, interval uint64) (*Collector, error) { + c, err := client.NewHTTPClient(client.HTTPConfig{ + Addr: url, + }) + if err != nil { + return nil, err + } + + _, _, err = c.Ping(0) + if err != nil { + log.Error("can not connect to InfluxDB") + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Error("Failed to connect to InfluxDB") + return nil, err + } + + q := client.NewQuery("CREATE DATABASE "+dbName, "", "") + if response, err := c.Query(q); err != nil || response.Error() != nil { + log.WithFields(log.Fields{"Type": "collector", "Error": err}).Errorf("Failed to create database:%s", dbName) + return nil, err + } + + collector := &Collector{ + s: s, + url: url, + dbName: dbName, + interval: interval, + client: c, + } + go collector.loop() + return collector, nil +} diff --git a/pkg/server/fsm.go b/pkg/server/fsm.go new file mode 100644 index 00000000..855066f4 --- /dev/null +++ b/pkg/server/fsm.go @@ -0,0 +1,1752 @@ +// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "fmt" + "io" + "math/rand" + "net" + "strconv" + "time" + + "github.com/eapache/channels" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/packet/bmp" + + log "github.com/sirupsen/logrus" + "gopkg.in/tomb.v2" +) + +type PeerDownReason int + +const ( + PEER_DOWN_REASON_UNKNOWN PeerDownReason = iota + PEER_DOWN_BY_LOCAL + PEER_DOWN_BY_LOCAL_WITHOUT_NOTIFICATION + PEER_DOWN_BY_REMOTE + PEER_DOWN_BY_REMOTE_WITHOUT_NOTIFICATION + PEER_DOWN_BY_BMP_CONFIGURATION +) + +type FsmStateReasonType uint8 + +const ( + FSM_DYING FsmStateReasonType = iota + FSM_ADMIN_DOWN + FSM_READ_FAILED + FSM_WRITE_FAILED + FSM_NOTIFICATION_SENT + FSM_NOTIFICATION_RECV + FSM_HOLD_TIMER_EXPIRED + FSM_IDLE_HOLD_TIMER_EXPIRED + FSM_RESTART_TIMER_EXPIRED + FSM_GRACEFUL_RESTART + FSM_INVALID_MSG + FSM_NEW_CONNECTION + FSM_OPEN_MSG_RECEIVED + FSM_OPEN_MSG_NEGOTIATED + FSM_HARD_RESET +) + +type FsmStateReason struct { + Type FsmStateReasonType + PeerDownReason PeerDownReason + BGPNotification *bgp.BGPMessage + Data []byte +} + +func NewFsmStateReason(typ FsmStateReasonType, notif *bgp.BGPMessage, data []byte) *FsmStateReason { + var reasonCode PeerDownReason + switch typ { + case FSM_DYING, FSM_INVALID_MSG, FSM_NOTIFICATION_SENT, FSM_HOLD_TIMER_EXPIRED, FSM_IDLE_HOLD_TIMER_EXPIRED, FSM_RESTART_TIMER_EXPIRED: + reasonCode = PEER_DOWN_BY_LOCAL + case FSM_ADMIN_DOWN: + reasonCode = PEER_DOWN_BY_LOCAL_WITHOUT_NOTIFICATION + case FSM_NOTIFICATION_RECV, FSM_GRACEFUL_RESTART, FSM_HARD_RESET: + reasonCode = PEER_DOWN_BY_REMOTE + case FSM_READ_FAILED, FSM_WRITE_FAILED: + reasonCode = PEER_DOWN_BY_REMOTE_WITHOUT_NOTIFICATION + } + return &FsmStateReason{ + Type: typ, + PeerDownReason: reasonCode, + BGPNotification: notif, + Data: data, + } +} + +func (r FsmStateReason) String() string { + switch r.Type { + case FSM_DYING: + return "dying" + case FSM_ADMIN_DOWN: + return "admin-down" + case FSM_READ_FAILED: + return "read-failed" + case FSM_WRITE_FAILED: + return "write-failed" + case FSM_NOTIFICATION_SENT: + body := r.BGPNotification.Body.(*bgp.BGPNotification) + return fmt.Sprintf("notification-sent %s", bgp.NewNotificationErrorCode(body.ErrorCode, body.ErrorSubcode).String()) + case FSM_NOTIFICATION_RECV: + body := r.BGPNotification.Body.(*bgp.BGPNotification) + return fmt.Sprintf("notification-received %s", bgp.NewNotificationErrorCode(body.ErrorCode, body.ErrorSubcode).String()) + case FSM_HOLD_TIMER_EXPIRED: + return "hold-timer-expired" + case FSM_IDLE_HOLD_TIMER_EXPIRED: + return "idle-hold-timer-expired" + case FSM_RESTART_TIMER_EXPIRED: + return "restart-timer-expired" + case FSM_GRACEFUL_RESTART: + return "graceful-restart" + case FSM_INVALID_MSG: + return "invalid-msg" + case FSM_NEW_CONNECTION: + return "new-connection" + case FSM_OPEN_MSG_RECEIVED: + return "open-msg-received" + case FSM_OPEN_MSG_NEGOTIATED: + return "open-msg-negotiated" + case FSM_HARD_RESET: + return "hard-reset" + default: + return "unknown" + } +} + +type FsmMsgType int + +const ( + _ FsmMsgType = iota + FSM_MSG_STATE_CHANGE + FSM_MSG_BGP_MESSAGE + FSM_MSG_ROUTE_REFRESH +) + +type FsmMsg struct { + MsgType FsmMsgType + MsgSrc string + MsgData interface{} + StateReason *FsmStateReason + PathList []*table.Path + timestamp time.Time + payload []byte + Version uint +} + +type FsmOutgoingMsg struct { + Paths []*table.Path + Notification *bgp.BGPMessage + StayIdle bool +} + +const ( + HOLDTIME_OPENSENT = 240 + HOLDTIME_IDLE = 5 +) + +type AdminState int + +const ( + ADMIN_STATE_UP AdminState = iota + ADMIN_STATE_DOWN + ADMIN_STATE_PFX_CT +) + +func (s AdminState) String() string { + switch s { + case ADMIN_STATE_UP: + return "ADMIN_STATE_UP" + case ADMIN_STATE_DOWN: + return "ADMIN_STATE_DOWN" + case ADMIN_STATE_PFX_CT: + return "ADMIN_STATE_PFX_CT" + default: + return "Unknown" + } +} + +type AdminStateOperation struct { + State AdminState + Communication []byte +} + +var fsmVersion uint + +type FSM struct { + t tomb.Tomb + gConf *config.Global + pConf *config.Neighbor + state bgp.FSMState + reason *FsmStateReason + conn net.Conn + connCh chan net.Conn + idleHoldTime float64 + opensentHoldTime float64 + adminState AdminState + adminStateCh chan AdminStateOperation + getActiveCh chan struct{} + h *FSMHandler + rfMap map[bgp.RouteFamily]bgp.BGPAddPathMode + capMap map[bgp.BGPCapabilityCode][]bgp.ParameterCapabilityInterface + recvOpen *bgp.BGPMessage + peerInfo *table.PeerInfo + policy *table.RoutingPolicy + gracefulRestartTimer *time.Timer + twoByteAsTrans bool + version uint + marshallingOptions *bgp.MarshallingOption +} + +func (fsm *FSM) bgpMessageStateUpdate(MessageType uint8, isIn bool) { + state := &fsm.pConf.State.Messages + timer := &fsm.pConf.Timers + if isIn { + state.Received.Total++ + } else { + state.Sent.Total++ + } + switch MessageType { + case bgp.BGP_MSG_OPEN: + if isIn { + state.Received.Open++ + } else { + state.Sent.Open++ + } + case bgp.BGP_MSG_UPDATE: + if isIn { + state.Received.Update++ + timer.State.UpdateRecvTime = time.Now().Unix() + } else { + state.Sent.Update++ + } + case bgp.BGP_MSG_NOTIFICATION: + if isIn { + state.Received.Notification++ + } else { + state.Sent.Notification++ + } + case bgp.BGP_MSG_KEEPALIVE: + if isIn { + state.Received.Keepalive++ + } else { + state.Sent.Keepalive++ + } + case bgp.BGP_MSG_ROUTE_REFRESH: + if isIn { + state.Received.Refresh++ + } else { + state.Sent.Refresh++ + } + default: + if isIn { + state.Received.Discarded++ + } else { + state.Sent.Discarded++ + } + } +} + +func (fsm *FSM) bmpStatsUpdate(statType uint16, increment int) { + stats := &fsm.pConf.State.Messages.Received + switch statType { + // TODO + // Support other stat types. + case bmp.BMP_STAT_TYPE_WITHDRAW_UPDATE: + stats.WithdrawUpdate += uint32(increment) + case bmp.BMP_STAT_TYPE_WITHDRAW_PREFIX: + stats.WithdrawPrefix += uint32(increment) + } +} + +func NewFSM(gConf *config.Global, pConf *config.Neighbor, policy *table.RoutingPolicy) *FSM { + adminState := ADMIN_STATE_UP + if pConf.Config.AdminDown { + adminState = ADMIN_STATE_DOWN + } + pConf.State.SessionState = config.IntToSessionStateMap[int(bgp.BGP_FSM_IDLE)] + pConf.Timers.State.Downtime = time.Now().Unix() + fsmVersion++ + fsm := &FSM{ + gConf: gConf, + pConf: pConf, + state: bgp.BGP_FSM_IDLE, + connCh: make(chan net.Conn, 1), + opensentHoldTime: float64(HOLDTIME_OPENSENT), + adminState: adminState, + adminStateCh: make(chan AdminStateOperation, 1), + getActiveCh: make(chan struct{}), + rfMap: make(map[bgp.RouteFamily]bgp.BGPAddPathMode), + capMap: make(map[bgp.BGPCapabilityCode][]bgp.ParameterCapabilityInterface), + peerInfo: table.NewPeerInfo(gConf, pConf), + policy: policy, + gracefulRestartTimer: time.NewTimer(time.Hour), + version: fsmVersion, + } + fsm.gracefulRestartTimer.Stop() + fsm.t.Go(fsm.connectLoop) + return fsm +} + +func (fsm *FSM) StateChange(nextState bgp.FSMState) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "old": fsm.state.String(), + "new": nextState.String(), + "reason": fsm.reason, + }).Debug("state changed") + fsm.state = nextState + switch nextState { + case bgp.BGP_FSM_ESTABLISHED: + fsm.pConf.Timers.State.Uptime = time.Now().Unix() + fsm.pConf.State.EstablishedCount++ + // reset the state set by the previous session + fsm.twoByteAsTrans = false + if _, y := fsm.capMap[bgp.BGP_CAP_FOUR_OCTET_AS_NUMBER]; !y { + fsm.twoByteAsTrans = true + break + } + y := func() bool { + for _, c := range capabilitiesFromConfig(fsm.pConf) { + switch c.(type) { + case *bgp.CapFourOctetASNumber: + return true + } + } + return false + }() + if !y { + fsm.twoByteAsTrans = true + } + case bgp.BGP_FSM_ACTIVE: + if !fsm.pConf.Transport.Config.PassiveMode { + fsm.getActiveCh <- struct{}{} + } + fallthrough + default: + fsm.pConf.Timers.State.Downtime = time.Now().Unix() + } +} + +func hostport(addr net.Addr) (string, uint16) { + if addr != nil { + host, port, err := net.SplitHostPort(addr.String()) + if err != nil { + return "", 0 + } + p, _ := strconv.ParseUint(port, 10, 16) + return host, uint16(p) + } + return "", 0 +} + +func (fsm *FSM) RemoteHostPort() (string, uint16) { + return hostport(fsm.conn.RemoteAddr()) + +} + +func (fsm *FSM) LocalHostPort() (string, uint16) { + return hostport(fsm.conn.LocalAddr()) +} + +func (fsm *FSM) sendNotificationFromErrorMsg(e *bgp.MessageError) (*bgp.BGPMessage, error) { + if fsm.h != nil && fsm.h.conn != nil { + m := bgp.NewBGPNotificationMessage(e.TypeCode, e.SubTypeCode, e.Data) + b, _ := m.Serialize() + _, err := fsm.h.conn.Write(b) + if err == nil { + fsm.bgpMessageStateUpdate(m.Header.Type, false) + fsm.h.sentNotification = m + } + fsm.h.conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "Data": e, + }).Warn("sent notification") + return m, nil + } + return nil, fmt.Errorf("can't send notification to %s since TCP connection is not established", fsm.pConf.State.NeighborAddress) +} + +func (fsm *FSM) sendNotification(code, subType uint8, data []byte, msg string) (*bgp.BGPMessage, error) { + e := bgp.NewMessageError(code, subType, data, msg) + return fsm.sendNotificationFromErrorMsg(e.(*bgp.MessageError)) +} + +func (fsm *FSM) connectLoop() error { + tick := int(fsm.pConf.Timers.Config.ConnectRetry) + if tick < MIN_CONNECT_RETRY { + tick = MIN_CONNECT_RETRY + } + + r := rand.New(rand.NewSource(time.Now().UnixNano())) + + timer := time.NewTimer(time.Duration(tick) * time.Second) + timer.Stop() + + connect := func() { + addr := fsm.pConf.State.NeighborAddress + port := int(bgp.BGP_PORT) + if fsm.pConf.Transport.Config.RemotePort != 0 { + port = int(fsm.pConf.Transport.Config.RemotePort) + } + laddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(fsm.pConf.Transport.Config.LocalAddress, "0")) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + }).Warn("failed to resolve local address: %s", err) + return + } + var conn net.Conn + d := TCPDialer{ + Dialer: net.Dialer{ + LocalAddr: laddr, + Timeout: time.Duration(MIN_CONNECT_RETRY-1) * time.Second, + }, + AuthPassword: fsm.pConf.Config.AuthPassword, + } + if fsm.pConf.TtlSecurity.Config.Enabled { + d.Ttl = 255 + d.TtlMin = fsm.pConf.TtlSecurity.Config.TtlMin + } else if fsm.pConf.Config.PeerAs != 0 && fsm.pConf.Config.PeerType == config.PEER_TYPE_EXTERNAL { + d.Ttl = 1 + if fsm.pConf.EbgpMultihop.Config.Enabled { + d.Ttl = fsm.pConf.EbgpMultihop.Config.MultihopTtl + } + } + conn, err = d.DialTCP(addr, port) + if err == nil { + select { + case fsm.connCh <- conn: + return + default: + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + }).Warn("active conn is closed to avoid being blocked") + } + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + }).Debugf("failed to connect: %s", err) + } + + if fsm.state == bgp.BGP_FSM_ACTIVE && !fsm.pConf.GracefulRestart.State.PeerRestarting { + timer.Reset(time.Duration(tick) * time.Second) + } + } + + for { + select { + case <-fsm.t.Dying(): + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + }).Debug("stop connect loop") + return nil + case <-timer.C: + if fsm.state == bgp.BGP_FSM_ACTIVE && !fsm.pConf.GracefulRestart.State.PeerRestarting { + go connect() + } + case <-fsm.getActiveCh: + timer.Reset(time.Duration(r.Intn(MIN_CONNECT_RETRY)+MIN_CONNECT_RETRY) * time.Second) + } + } +} + +type FSMHandler struct { + t tomb.Tomb + fsm *FSM + conn net.Conn + msgCh *channels.InfiniteChannel + stateReasonCh chan FsmStateReason + incoming *channels.InfiniteChannel + stateCh chan *FsmMsg + outgoing *channels.InfiniteChannel + holdTimerResetCh chan bool + sentNotification *bgp.BGPMessage +} + +func NewFSMHandler(fsm *FSM, incoming *channels.InfiniteChannel, stateCh chan *FsmMsg, outgoing *channels.InfiniteChannel) *FSMHandler { + h := &FSMHandler{ + fsm: fsm, + stateReasonCh: make(chan FsmStateReason, 2), + incoming: incoming, + stateCh: stateCh, + outgoing: outgoing, + holdTimerResetCh: make(chan bool, 2), + } + fsm.t.Go(h.loop) + return h +} + +func (h *FSMHandler) idle() (bgp.FSMState, *FsmStateReason) { + fsm := h.fsm + + idleHoldTimer := time.NewTimer(time.Second * time.Duration(fsm.idleHoldTime)) + for { + select { + case <-h.t.Dying(): + return -1, NewFsmStateReason(FSM_DYING, nil, nil) + case <-fsm.gracefulRestartTimer.C: + if fsm.pConf.GracefulRestart.State.PeerRestarting { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("graceful restart timer expired") + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_RESTART_TIMER_EXPIRED, nil, nil) + } + case conn, ok := <-fsm.connCh: + if !ok { + break + } + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("Closed an accepted connection") + case <-idleHoldTimer.C: + + if fsm.adminState == ADMIN_STATE_UP { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "Duration": fsm.idleHoldTime, + }).Debug("IdleHoldTimer expired") + fsm.idleHoldTime = HOLDTIME_IDLE + return bgp.BGP_FSM_ACTIVE, NewFsmStateReason(FSM_IDLE_HOLD_TIMER_EXPIRED, nil, nil) + + } else { + log.WithFields(log.Fields{"Topic": "Peer"}).Debug("IdleHoldTimer expired, but stay at idle because the admin state is DOWN") + } + + case stateOp := <-fsm.adminStateCh: + err := h.changeAdminState(stateOp.State) + if err == nil { + switch stateOp.State { + case ADMIN_STATE_DOWN: + // stop idle hold timer + idleHoldTimer.Stop() + + case ADMIN_STATE_UP: + // restart idle hold timer + idleHoldTimer.Reset(time.Second * time.Duration(fsm.idleHoldTime)) + } + } + } + } +} + +func (h *FSMHandler) active() (bgp.FSMState, *FsmStateReason) { + fsm := h.fsm + for { + select { + case <-h.t.Dying(): + return -1, NewFsmStateReason(FSM_DYING, nil, nil) + case conn, ok := <-fsm.connCh: + if !ok { + break + } + fsm.conn = conn + ttl := 0 + ttlMin := 0 + if fsm.pConf.TtlSecurity.Config.Enabled { + ttl = 255 + ttlMin = int(fsm.pConf.TtlSecurity.Config.TtlMin) + } else if fsm.pConf.Config.PeerAs != 0 && fsm.pConf.Config.PeerType == config.PEER_TYPE_EXTERNAL { + if fsm.pConf.EbgpMultihop.Config.Enabled { + ttl = int(fsm.pConf.EbgpMultihop.Config.MultihopTtl) + } else if fsm.pConf.Transport.Config.Ttl != 0 { + ttl = int(fsm.pConf.Transport.Config.Ttl) + } else { + ttl = 1 + } + } else if fsm.pConf.Transport.Config.Ttl != 0 { + ttl = int(fsm.pConf.Transport.Config.Ttl) + } + if ttl != 0 { + if err := SetTcpTTLSockopt(conn.(*net.TCPConn), ttl); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.Config.NeighborAddress, + "State": fsm.state.String(), + }).Warnf("cannot set TTL(=%d) for peer: %s", ttl, err) + } + } + if ttlMin != 0 { + if err := SetTcpMinTTLSockopt(conn.(*net.TCPConn), ttlMin); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.Config.NeighborAddress, + "State": fsm.state.String(), + }).Warnf("cannot set minimal TTL(=%d) for peer: %s", ttl, err) + } + } + // we don't implement delayed open timer so move to opensent right + // away. + return bgp.BGP_FSM_OPENSENT, NewFsmStateReason(FSM_NEW_CONNECTION, nil, nil) + case <-fsm.gracefulRestartTimer.C: + if fsm.pConf.GracefulRestart.State.PeerRestarting { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("graceful restart timer expired") + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_RESTART_TIMER_EXPIRED, nil, nil) + } + case err := <-h.stateReasonCh: + return bgp.BGP_FSM_IDLE, &err + case stateOp := <-fsm.adminStateCh: + err := h.changeAdminState(stateOp.State) + if err == nil { + switch stateOp.State { + case ADMIN_STATE_DOWN: + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_ADMIN_DOWN, nil, nil) + case ADMIN_STATE_UP: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "AdminState": stateOp.State.String(), + }).Panic("code logic bug") + } + } + } + } +} + +func capAddPathFromConfig(pConf *config.Neighbor) bgp.ParameterCapabilityInterface { + tuples := make([]*bgp.CapAddPathTuple, 0, len(pConf.AfiSafis)) + for _, af := range pConf.AfiSafis { + var mode bgp.BGPAddPathMode + if af.AddPaths.State.Receive { + mode |= bgp.BGP_ADD_PATH_RECEIVE + } + if af.AddPaths.State.SendMax > 0 { + mode |= bgp.BGP_ADD_PATH_SEND + } + if mode > 0 { + tuples = append(tuples, bgp.NewCapAddPathTuple(af.State.Family, mode)) + } + } + if len(tuples) == 0 { + return nil + } + return bgp.NewCapAddPath(tuples) +} + +func capabilitiesFromConfig(pConf *config.Neighbor) []bgp.ParameterCapabilityInterface { + caps := make([]bgp.ParameterCapabilityInterface, 0, 4) + caps = append(caps, bgp.NewCapRouteRefresh()) + for _, af := range pConf.AfiSafis { + caps = append(caps, bgp.NewCapMultiProtocol(af.State.Family)) + } + caps = append(caps, bgp.NewCapFourOctetASNumber(pConf.Config.LocalAs)) + + if c := pConf.GracefulRestart.Config; c.Enabled { + tuples := []*bgp.CapGracefulRestartTuple{} + ltuples := []*bgp.CapLongLivedGracefulRestartTuple{} + + // RFC 4724 4.1 + // To re-establish the session with its peer, the Restarting Speaker + // MUST set the "Restart State" bit in the Graceful Restart Capability + // of the OPEN message. + restarting := pConf.GracefulRestart.State.LocalRestarting + + if !c.HelperOnly { + for i, rf := range pConf.AfiSafis { + if m := rf.MpGracefulRestart.Config; m.Enabled { + // When restarting, always flag forwaring bit. + // This can be a lie, depending on how gobgpd is used. + // For a route-server use-case, since a route-server + // itself doesn't forward packets, and the dataplane + // is a l2 switch which continues to work with no + // relation to bgpd, this behavior is ok. + // TODO consideration of other use-cases + tuples = append(tuples, bgp.NewCapGracefulRestartTuple(rf.State.Family, restarting)) + pConf.AfiSafis[i].MpGracefulRestart.State.Advertised = true + } + if m := rf.LongLivedGracefulRestart.Config; m.Enabled { + ltuples = append(ltuples, bgp.NewCapLongLivedGracefulRestartTuple(rf.State.Family, restarting, m.RestartTime)) + } + } + } + restartTime := c.RestartTime + notification := c.NotificationEnabled + caps = append(caps, bgp.NewCapGracefulRestart(restarting, notification, restartTime, tuples)) + if c.LongLivedEnabled { + caps = append(caps, bgp.NewCapLongLivedGracefulRestart(ltuples)) + } + } + + // unnumbered BGP + if pConf.Config.NeighborInterface != "" { + tuples := []*bgp.CapExtendedNexthopTuple{} + families, _ := config.AfiSafis(pConf.AfiSafis).ToRfList() + for _, family := range families { + if family == bgp.RF_IPv6_UC { + continue + } + tuple := bgp.NewCapExtendedNexthopTuple(family, bgp.AFI_IP6) + tuples = append(tuples, tuple) + } + caps = append(caps, bgp.NewCapExtendedNexthop(tuples)) + } + + // ADD-PATH Capability + if c := capAddPathFromConfig(pConf); c != nil { + caps = append(caps, capAddPathFromConfig(pConf)) + } + + return caps +} + +func buildopen(gConf *config.Global, pConf *config.Neighbor) *bgp.BGPMessage { + caps := capabilitiesFromConfig(pConf) + opt := bgp.NewOptionParameterCapability(caps) + holdTime := uint16(pConf.Timers.Config.HoldTime) + as := pConf.Config.LocalAs + if as > (1<<16)-1 { + as = bgp.AS_TRANS + } + return bgp.NewBGPOpenMessage(uint16(as), holdTime, gConf.Config.RouterId, + []bgp.OptionParameterInterface{opt}) +} + +func readAll(conn net.Conn, length int) ([]byte, error) { + buf := make([]byte, length) + _, err := io.ReadFull(conn, buf) + if err != nil { + return nil, err + } + return buf, nil +} + +func getPathAttrFromBGPUpdate(m *bgp.BGPUpdate, typ bgp.BGPAttrType) bgp.PathAttributeInterface { + for _, a := range m.PathAttributes { + if a.GetType() == typ { + return a + } + } + return nil +} + +func hasOwnASLoop(ownAS uint32, limit int, asPath *bgp.PathAttributeAsPath) bool { + cnt := 0 + for _, param := range asPath.Value { + for _, as := range param.GetAS() { + if as == ownAS { + cnt++ + if cnt > limit { + return true + } + } + } + } + return false +} + +func extractRouteFamily(p *bgp.PathAttributeInterface) *bgp.RouteFamily { + attr := *p + + var afi uint16 + var safi uint8 + + switch a := attr.(type) { + case *bgp.PathAttributeMpReachNLRI: + afi = a.AFI + safi = a.SAFI + case *bgp.PathAttributeMpUnreachNLRI: + afi = a.AFI + safi = a.SAFI + default: + return nil + } + + rf := bgp.AfiSafiToRouteFamily(afi, safi) + return &rf +} + +func (h *FSMHandler) afiSafiDisable(rf bgp.RouteFamily) string { + n := bgp.AddressFamilyNameMap[rf] + + for i, a := range h.fsm.pConf.AfiSafis { + if string(a.Config.AfiSafiName) == n { + h.fsm.pConf.AfiSafis[i].State.Enabled = false + break + } + } + newList := make([]bgp.ParameterCapabilityInterface, 0) + for _, c := range h.fsm.capMap[bgp.BGP_CAP_MULTIPROTOCOL] { + if c.(*bgp.CapMultiProtocol).CapValue == rf { + continue + } + newList = append(newList, c) + } + h.fsm.capMap[bgp.BGP_CAP_MULTIPROTOCOL] = newList + return n +} + +func (h *FSMHandler) handlingError(m *bgp.BGPMessage, e error, useRevisedError bool) bgp.ErrorHandling { + handling := bgp.ERROR_HANDLING_NONE + if m.Header.Type == bgp.BGP_MSG_UPDATE && useRevisedError { + factor := e.(*bgp.MessageError) + handling = factor.ErrorHandling + switch handling { + case bgp.ERROR_HANDLING_ATTRIBUTE_DISCARD: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + "error": e, + }).Warn("Some attributes were discarded") + case bgp.ERROR_HANDLING_TREAT_AS_WITHDRAW: + m.Body = bgp.TreatAsWithdraw(m.Body.(*bgp.BGPUpdate)) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + "error": e, + }).Warn("the received Update message was treated as withdraw") + case bgp.ERROR_HANDLING_AFISAFI_DISABLE: + rf := extractRouteFamily(factor.ErrorAttribute) + if rf == nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + }).Warn("Error occurred during AFI/SAFI disabling") + } else { + n := h.afiSafiDisable(*rf) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + "error": e, + }).Warnf("Capability %s was disabled", n) + } + } + } else { + handling = bgp.ERROR_HANDLING_SESSION_RESET + } + return handling +} + +func (h *FSMHandler) recvMessageWithError() (*FsmMsg, error) { + sendToStateReasonCh := func(typ FsmStateReasonType, notif *bgp.BGPMessage) { + // probably doesn't happen but be cautious + select { + case h.stateReasonCh <- *NewFsmStateReason(typ, notif, nil): + default: + } + } + + headerBuf, err := readAll(h.conn, bgp.BGP_HEADER_LENGTH) + if err != nil { + sendToStateReasonCh(FSM_READ_FAILED, nil) + return nil, err + } + + hd := &bgp.BGPHeader{} + err = hd.DecodeFromBytes(headerBuf) + if err != nil { + h.fsm.bgpMessageStateUpdate(0, true) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + "error": err, + }).Warn("Session will be reset due to malformed BGP Header") + fmsg := &FsmMsg{ + MsgType: FSM_MSG_BGP_MESSAGE, + MsgSrc: h.fsm.pConf.State.NeighborAddress, + MsgData: err, + Version: h.fsm.version, + } + return fmsg, err + } + + bodyBuf, err := readAll(h.conn, int(hd.Len)-bgp.BGP_HEADER_LENGTH) + if err != nil { + sendToStateReasonCh(FSM_READ_FAILED, nil) + return nil, err + } + + now := time.Now() + useRevisedError := h.fsm.pConf.ErrorHandling.Config.TreatAsWithdraw + handling := bgp.ERROR_HANDLING_NONE + + m, err := bgp.ParseBGPBody(hd, bodyBuf, h.fsm.marshallingOptions) + if err != nil { + handling = h.handlingError(m, err, useRevisedError) + h.fsm.bgpMessageStateUpdate(0, true) + } else { + h.fsm.bgpMessageStateUpdate(m.Header.Type, true) + err = bgp.ValidateBGPMessage(m) + } + fmsg := &FsmMsg{ + MsgType: FSM_MSG_BGP_MESSAGE, + MsgSrc: h.fsm.pConf.State.NeighborAddress, + timestamp: now, + Version: h.fsm.version, + } + + switch handling { + case bgp.ERROR_HANDLING_AFISAFI_DISABLE: + fmsg.MsgData = m + return fmsg, nil + case bgp.ERROR_HANDLING_SESSION_RESET: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + "error": err, + }).Warn("Session will be reset due to malformed BGP message") + fmsg.MsgData = err + return fmsg, err + default: + fmsg.MsgData = m + if h.fsm.state == bgp.BGP_FSM_ESTABLISHED { + switch m.Header.Type { + case bgp.BGP_MSG_ROUTE_REFRESH: + fmsg.MsgType = FSM_MSG_ROUTE_REFRESH + case bgp.BGP_MSG_UPDATE: + body := m.Body.(*bgp.BGPUpdate) + isEBGP := h.fsm.pConf.IsEBGPPeer(h.fsm.gConf) + isConfed := h.fsm.pConf.IsConfederationMember(h.fsm.gConf) + + fmsg.payload = make([]byte, len(headerBuf)+len(bodyBuf)) + copy(fmsg.payload, headerBuf) + copy(fmsg.payload[len(headerBuf):], bodyBuf) + + ok, err := bgp.ValidateUpdateMsg(body, h.fsm.rfMap, isEBGP, isConfed) + if !ok { + handling = h.handlingError(m, err, useRevisedError) + } + if handling == bgp.ERROR_HANDLING_SESSION_RESET { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "State": h.fsm.state.String(), + "error": err, + }).Warn("Session will be reset due to malformed BGP update message") + fmsg.MsgData = err + return fmsg, err + } + + if routes := len(body.WithdrawnRoutes); routes > 0 { + h.fsm.bmpStatsUpdate(bmp.BMP_STAT_TYPE_WITHDRAW_UPDATE, 1) + h.fsm.bmpStatsUpdate(bmp.BMP_STAT_TYPE_WITHDRAW_PREFIX, routes) + } else if attr := getPathAttrFromBGPUpdate(body, bgp.BGP_ATTR_TYPE_MP_UNREACH_NLRI); attr != nil { + mpUnreach := attr.(*bgp.PathAttributeMpUnreachNLRI) + if routes = len(mpUnreach.Value); routes > 0 { + h.fsm.bmpStatsUpdate(bmp.BMP_STAT_TYPE_WITHDRAW_UPDATE, 1) + h.fsm.bmpStatsUpdate(bmp.BMP_STAT_TYPE_WITHDRAW_PREFIX, routes) + } + } + + table.UpdatePathAttrs4ByteAs(body) + if err = table.UpdatePathAggregator4ByteAs(body); err != nil { + fmsg.MsgData = err + return fmsg, err + } + + fmsg.PathList = table.ProcessMessage(m, h.fsm.peerInfo, fmsg.timestamp) + fallthrough + case bgp.BGP_MSG_KEEPALIVE: + // if the length of h.holdTimerResetCh + // isn't zero, the timer will be reset + // soon anyway. + select { + case h.holdTimerResetCh <- true: + default: + } + if m.Header.Type == bgp.BGP_MSG_KEEPALIVE { + return nil, nil + } + case bgp.BGP_MSG_NOTIFICATION: + body := m.Body.(*bgp.BGPNotification) + if body.ErrorCode == bgp.BGP_ERROR_CEASE && (body.ErrorSubcode == bgp.BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN || body.ErrorSubcode == bgp.BGP_ERROR_SUB_ADMINISTRATIVE_RESET) { + communication, rest := decodeAdministrativeCommunication(body.Data) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "Code": body.ErrorCode, + "Subcode": body.ErrorSubcode, + "Communicated-Reason": communication, + "Data": rest, + }).Warn("received notification") + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": h.fsm.pConf.State.NeighborAddress, + "Code": body.ErrorCode, + "Subcode": body.ErrorSubcode, + "Data": body.Data, + }).Warn("received notification") + } + + if s := h.fsm.pConf.GracefulRestart.State; s.Enabled && s.NotificationEnabled && body.ErrorCode == bgp.BGP_ERROR_CEASE && body.ErrorSubcode == bgp.BGP_ERROR_SUB_HARD_RESET { + sendToStateReasonCh(FSM_HARD_RESET, m) + } else { + sendToStateReasonCh(FSM_NOTIFICATION_RECV, m) + } + return nil, nil + } + } + } + return fmsg, nil +} + +func (h *FSMHandler) recvMessage() error { + defer h.msgCh.Close() + fmsg, _ := h.recvMessageWithError() + if fmsg != nil { + h.msgCh.In() <- fmsg + } + return nil +} + +func open2Cap(open *bgp.BGPOpen, n *config.Neighbor) (map[bgp.BGPCapabilityCode][]bgp.ParameterCapabilityInterface, map[bgp.RouteFamily]bgp.BGPAddPathMode) { + capMap := make(map[bgp.BGPCapabilityCode][]bgp.ParameterCapabilityInterface) + for _, p := range open.OptParams { + if paramCap, y := p.(*bgp.OptionParameterCapability); y { + for _, c := range paramCap.Capability { + m, ok := capMap[c.Code()] + if !ok { + m = make([]bgp.ParameterCapabilityInterface, 0, 1) + } + capMap[c.Code()] = append(m, c) + } + } + } + + // squash add path cap + if caps, y := capMap[bgp.BGP_CAP_ADD_PATH]; y { + items := make([]*bgp.CapAddPathTuple, 0, len(caps)) + for _, c := range caps { + items = append(items, c.(*bgp.CapAddPath).Tuples...) + } + capMap[bgp.BGP_CAP_ADD_PATH] = []bgp.ParameterCapabilityInterface{bgp.NewCapAddPath(items)} + } + + // remote open message may not include multi-protocol capability + if _, y := capMap[bgp.BGP_CAP_MULTIPROTOCOL]; !y { + capMap[bgp.BGP_CAP_MULTIPROTOCOL] = []bgp.ParameterCapabilityInterface{bgp.NewCapMultiProtocol(bgp.RF_IPv4_UC)} + } + + local := n.CreateRfMap() + remote := make(map[bgp.RouteFamily]bgp.BGPAddPathMode) + for _, c := range capMap[bgp.BGP_CAP_MULTIPROTOCOL] { + family := c.(*bgp.CapMultiProtocol).CapValue + remote[family] = bgp.BGP_ADD_PATH_NONE + for _, a := range capMap[bgp.BGP_CAP_ADD_PATH] { + for _, i := range a.(*bgp.CapAddPath).Tuples { + if i.RouteFamily == family { + remote[family] = i.Mode + } + } + } + } + negotiated := make(map[bgp.RouteFamily]bgp.BGPAddPathMode) + for family, mode := range local { + if m, y := remote[family]; y { + n := bgp.BGP_ADD_PATH_NONE + if mode&bgp.BGP_ADD_PATH_SEND > 0 && m&bgp.BGP_ADD_PATH_RECEIVE > 0 { + n |= bgp.BGP_ADD_PATH_SEND + } + if mode&bgp.BGP_ADD_PATH_RECEIVE > 0 && m&bgp.BGP_ADD_PATH_SEND > 0 { + n |= bgp.BGP_ADD_PATH_RECEIVE + } + negotiated[family] = n + } + } + return capMap, negotiated +} + +func (h *FSMHandler) opensent() (bgp.FSMState, *FsmStateReason) { + fsm := h.fsm + m := buildopen(fsm.gConf, fsm.pConf) + b, _ := m.Serialize() + fsm.conn.Write(b) + fsm.bgpMessageStateUpdate(m.Header.Type, false) + + h.msgCh = channels.NewInfiniteChannel() + h.conn = fsm.conn + + h.t.Go(h.recvMessage) + + // RFC 4271 P.60 + // sets its HoldTimer to a large value + // A HoldTimer value of 4 minutes is suggested as a "large value" + // for the HoldTimer + holdTimer := time.NewTimer(time.Second * time.Duration(fsm.opensentHoldTime)) + + for { + select { + case <-h.t.Dying(): + h.conn.Close() + return -1, NewFsmStateReason(FSM_DYING, nil, nil) + case conn, ok := <-fsm.connCh: + if !ok { + break + } + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("Closed an accepted connection") + case <-fsm.gracefulRestartTimer.C: + if fsm.pConf.GracefulRestart.State.PeerRestarting { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("graceful restart timer expired") + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_RESTART_TIMER_EXPIRED, nil, nil) + } + case i, ok := <-h.msgCh.Out(): + if !ok { + continue + } + e := i.(*FsmMsg) + switch e.MsgData.(type) { + case *bgp.BGPMessage: + m := e.MsgData.(*bgp.BGPMessage) + if m.Header.Type == bgp.BGP_MSG_OPEN { + fsm.recvOpen = m + body := m.Body.(*bgp.BGPOpen) + peerAs, err := bgp.ValidateOpenMsg(body, fsm.pConf.Config.PeerAs) + if err != nil { + m, _ := fsm.sendNotificationFromErrorMsg(err.(*bgp.MessageError)) + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_INVALID_MSG, m, nil) + } + + // ASN negotiation was skipped + if fsm.pConf.Config.PeerAs == 0 { + typ := config.PEER_TYPE_EXTERNAL + if fsm.peerInfo.LocalAS == peerAs { + typ = config.PEER_TYPE_INTERNAL + } + fsm.pConf.State.PeerType = typ + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Infof("skipped asn negotiation: peer-as: %d, peer-type: %s", peerAs, typ) + } else { + fsm.pConf.State.PeerType = fsm.pConf.Config.PeerType + } + fsm.pConf.State.PeerAs = peerAs + fsm.peerInfo.AS = peerAs + fsm.peerInfo.ID = body.ID + fsm.capMap, fsm.rfMap = open2Cap(body, fsm.pConf) + + if _, y := fsm.capMap[bgp.BGP_CAP_ADD_PATH]; y { + fsm.marshallingOptions = &bgp.MarshallingOption{ + AddPath: fsm.rfMap, + } + } else { + fsm.marshallingOptions = nil + } + + // calculate HoldTime + // RFC 4271 P.13 + // a BGP speaker MUST calculate the value of the Hold Timer + // by using the smaller of its configured Hold Time and the Hold Time + // received in the OPEN message. + holdTime := float64(body.HoldTime) + myHoldTime := fsm.pConf.Timers.Config.HoldTime + if holdTime > myHoldTime { + fsm.pConf.Timers.State.NegotiatedHoldTime = myHoldTime + } else { + fsm.pConf.Timers.State.NegotiatedHoldTime = holdTime + } + + keepalive := fsm.pConf.Timers.Config.KeepaliveInterval + if n := fsm.pConf.Timers.State.NegotiatedHoldTime; n < myHoldTime { + keepalive = n / 3 + } + fsm.pConf.Timers.State.KeepaliveInterval = keepalive + + gr, ok := fsm.capMap[bgp.BGP_CAP_GRACEFUL_RESTART] + if fsm.pConf.GracefulRestart.Config.Enabled && ok { + state := &fsm.pConf.GracefulRestart.State + state.Enabled = true + cap := gr[len(gr)-1].(*bgp.CapGracefulRestart) + state.PeerRestartTime = uint16(cap.Time) + + for _, t := range cap.Tuples { + n := bgp.AddressFamilyNameMap[bgp.AfiSafiToRouteFamily(t.AFI, t.SAFI)] + for i, a := range fsm.pConf.AfiSafis { + if string(a.Config.AfiSafiName) == n { + fsm.pConf.AfiSafis[i].MpGracefulRestart.State.Enabled = true + fsm.pConf.AfiSafis[i].MpGracefulRestart.State.Received = true + break + } + } + } + + // RFC 4724 4.1 + // To re-establish the session with its peer, the Restarting Speaker + // MUST set the "Restart State" bit in the Graceful Restart Capability + // of the OPEN message. + if fsm.pConf.GracefulRestart.State.PeerRestarting && cap.Flags&0x08 == 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("restart flag is not set") + // send notification? + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_INVALID_MSG, nil, nil) + } + + // RFC 4724 3 + // The most significant bit is defined as the Restart State (R) + // bit, ...(snip)... When set (value 1), this bit + // indicates that the BGP speaker has restarted, and its peer MUST + // NOT wait for the End-of-RIB marker from the speaker before + // advertising routing information to the speaker. + if fsm.pConf.GracefulRestart.State.LocalRestarting && cap.Flags&0x08 != 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Debug("peer has restarted, skipping wait for EOR") + for i := range fsm.pConf.AfiSafis { + fsm.pConf.AfiSafis[i].MpGracefulRestart.State.EndOfRibReceived = true + } + } + if fsm.pConf.GracefulRestart.Config.NotificationEnabled && cap.Flags&0x04 > 0 { + fsm.pConf.GracefulRestart.State.NotificationEnabled = true + } + } + llgr, ok2 := fsm.capMap[bgp.BGP_CAP_LONG_LIVED_GRACEFUL_RESTART] + if fsm.pConf.GracefulRestart.Config.LongLivedEnabled && ok && ok2 { + fsm.pConf.GracefulRestart.State.LongLivedEnabled = true + cap := llgr[len(llgr)-1].(*bgp.CapLongLivedGracefulRestart) + for _, t := range cap.Tuples { + n := bgp.AddressFamilyNameMap[bgp.AfiSafiToRouteFamily(t.AFI, t.SAFI)] + for i, a := range fsm.pConf.AfiSafis { + if string(a.Config.AfiSafiName) == n { + fsm.pConf.AfiSafis[i].LongLivedGracefulRestart.State.Enabled = true + fsm.pConf.AfiSafis[i].LongLivedGracefulRestart.State.Received = true + fsm.pConf.AfiSafis[i].LongLivedGracefulRestart.State.PeerRestartTime = t.RestartTime + break + } + } + } + } + + msg := bgp.NewBGPKeepAliveMessage() + b, _ := msg.Serialize() + fsm.conn.Write(b) + fsm.bgpMessageStateUpdate(msg.Header.Type, false) + return bgp.BGP_FSM_OPENCONFIRM, NewFsmStateReason(FSM_OPEN_MSG_RECEIVED, nil, nil) + } else { + // send notification? + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_INVALID_MSG, nil, nil) + } + case *bgp.MessageError: + m, _ := fsm.sendNotificationFromErrorMsg(e.MsgData.(*bgp.MessageError)) + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_INVALID_MSG, m, nil) + default: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Data": e.MsgData, + }).Panic("unknown msg type") + } + case err := <-h.stateReasonCh: + h.conn.Close() + return bgp.BGP_FSM_IDLE, &err + case <-holdTimer.C: + m, _ := fsm.sendNotification(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED, 0, nil, "hold timer expired") + h.t.Kill(nil) + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_HOLD_TIMER_EXPIRED, m, nil) + case stateOp := <-fsm.adminStateCh: + err := h.changeAdminState(stateOp.State) + if err == nil { + switch stateOp.State { + case ADMIN_STATE_DOWN: + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_ADMIN_DOWN, m, nil) + case ADMIN_STATE_UP: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "AdminState": stateOp.State.String(), + }).Panic("code logic bug") + } + } + } + } +} + +func keepaliveTicker(fsm *FSM) *time.Ticker { + negotiatedTime := fsm.pConf.Timers.State.NegotiatedHoldTime + if negotiatedTime == 0 { + return &time.Ticker{} + } + sec := time.Second * time.Duration(fsm.pConf.Timers.State.KeepaliveInterval) + if sec == 0 { + sec = time.Second + } + return time.NewTicker(sec) +} + +func (h *FSMHandler) openconfirm() (bgp.FSMState, *FsmStateReason) { + fsm := h.fsm + ticker := keepaliveTicker(fsm) + h.msgCh = channels.NewInfiniteChannel() + h.conn = fsm.conn + + h.t.Go(h.recvMessage) + + var holdTimer *time.Timer + if fsm.pConf.Timers.State.NegotiatedHoldTime == 0 { + holdTimer = &time.Timer{} + } else { + // RFC 4271 P.65 + // sets the HoldTimer according to the negotiated value + holdTimer = time.NewTimer(time.Second * time.Duration(fsm.pConf.Timers.State.NegotiatedHoldTime)) + } + + for { + select { + case <-h.t.Dying(): + h.conn.Close() + return -1, NewFsmStateReason(FSM_DYING, nil, nil) + case conn, ok := <-fsm.connCh: + if !ok { + break + } + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("Closed an accepted connection") + case <-fsm.gracefulRestartTimer.C: + if fsm.pConf.GracefulRestart.State.PeerRestarting { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("graceful restart timer expired") + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_RESTART_TIMER_EXPIRED, nil, nil) + } + case <-ticker.C: + m := bgp.NewBGPKeepAliveMessage() + b, _ := m.Serialize() + // TODO: check error + fsm.conn.Write(b) + fsm.bgpMessageStateUpdate(m.Header.Type, false) + case i, ok := <-h.msgCh.Out(): + if !ok { + continue + } + e := i.(*FsmMsg) + switch e.MsgData.(type) { + case *bgp.BGPMessage: + m := e.MsgData.(*bgp.BGPMessage) + if m.Header.Type == bgp.BGP_MSG_KEEPALIVE { + return bgp.BGP_FSM_ESTABLISHED, NewFsmStateReason(FSM_OPEN_MSG_NEGOTIATED, nil, nil) + } + // send notification ? + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_INVALID_MSG, nil, nil) + case *bgp.MessageError: + m, _ := fsm.sendNotificationFromErrorMsg(e.MsgData.(*bgp.MessageError)) + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_INVALID_MSG, m, nil) + default: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Data": e.MsgData, + }).Panic("unknown msg type") + } + case err := <-h.stateReasonCh: + h.conn.Close() + return bgp.BGP_FSM_IDLE, &err + case <-holdTimer.C: + m, _ := fsm.sendNotification(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED, 0, nil, "hold timer expired") + h.t.Kill(nil) + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_HOLD_TIMER_EXPIRED, m, nil) + case stateOp := <-fsm.adminStateCh: + err := h.changeAdminState(stateOp.State) + if err == nil { + switch stateOp.State { + case ADMIN_STATE_DOWN: + h.conn.Close() + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_ADMIN_DOWN, nil, nil) + case ADMIN_STATE_UP: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "AdminState": stateOp.State.String(), + }).Panic("code logic bug") + } + } + } + } +} + +func (h *FSMHandler) sendMessageloop() error { + conn := h.conn + fsm := h.fsm + ticker := keepaliveTicker(fsm) + send := func(m *bgp.BGPMessage) error { + if fsm.twoByteAsTrans && m.Header.Type == bgp.BGP_MSG_UPDATE { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Data": m, + }).Debug("update for 2byte AS peer") + table.UpdatePathAttrs2ByteAs(m.Body.(*bgp.BGPUpdate)) + table.UpdatePathAggregator2ByteAs(m.Body.(*bgp.BGPUpdate)) + } + b, err := m.Serialize(h.fsm.marshallingOptions) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Data": err, + }).Warn("failed to serialize") + fsm.bgpMessageStateUpdate(0, false) + return nil + } + if err := conn.SetWriteDeadline(time.Now().Add(time.Second * time.Duration(fsm.pConf.Timers.State.NegotiatedHoldTime))); err != nil { + h.stateReasonCh <- *NewFsmStateReason(FSM_WRITE_FAILED, nil, nil) + conn.Close() + return fmt.Errorf("failed to set write deadline") + } + _, err = conn.Write(b) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Data": err, + }).Warn("failed to send") + h.stateReasonCh <- *NewFsmStateReason(FSM_WRITE_FAILED, nil, nil) + conn.Close() + return fmt.Errorf("closed") + } + fsm.bgpMessageStateUpdate(m.Header.Type, false) + + switch m.Header.Type { + case bgp.BGP_MSG_NOTIFICATION: + body := m.Body.(*bgp.BGPNotification) + if body.ErrorCode == bgp.BGP_ERROR_CEASE && (body.ErrorSubcode == bgp.BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN || body.ErrorSubcode == bgp.BGP_ERROR_SUB_ADMINISTRATIVE_RESET) { + communication, rest := decodeAdministrativeCommunication(body.Data) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Code": body.ErrorCode, + "Subcode": body.ErrorSubcode, + "Communicated-Reason": communication, + "Data": rest, + }).Warn("sent notification") + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Code": body.ErrorCode, + "Subcode": body.ErrorSubcode, + "Data": body.Data, + }).Warn("sent notification") + } + h.stateReasonCh <- *NewFsmStateReason(FSM_NOTIFICATION_SENT, m, nil) + conn.Close() + return fmt.Errorf("closed") + case bgp.BGP_MSG_UPDATE: + update := m.Body.(*bgp.BGPUpdate) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "nlri": update.NLRI, + "withdrawals": update.WithdrawnRoutes, + "attributes": update.PathAttributes, + }).Debug("sent update") + default: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "data": m, + }).Debug("sent") + } + return nil + } + + for { + select { + case <-h.t.Dying(): + return nil + case o := <-h.outgoing.Out(): + m := o.(*FsmOutgoingMsg) + for _, msg := range table.CreateUpdateMsgFromPaths(m.Paths, h.fsm.marshallingOptions) { + if err := send(msg); err != nil { + return nil + } + } + if m.Notification != nil { + if m.StayIdle { + // current user is only prefix-limit + // fix me if this is not the case + h.changeAdminState(ADMIN_STATE_PFX_CT) + } + if err := send(m.Notification); err != nil { + return nil + } + } + case <-ticker.C: + if err := send(bgp.NewBGPKeepAliveMessage()); err != nil { + return nil + } + } + } +} + +func (h *FSMHandler) recvMessageloop() error { + for { + fmsg, err := h.recvMessageWithError() + if fmsg != nil { + h.msgCh.In() <- fmsg + } + if err != nil { + return nil + } + } +} + +func (h *FSMHandler) established() (bgp.FSMState, *FsmStateReason) { + fsm := h.fsm + h.conn = fsm.conn + h.t.Go(h.sendMessageloop) + h.msgCh = h.incoming + h.t.Go(h.recvMessageloop) + + var holdTimer *time.Timer + if fsm.pConf.Timers.State.NegotiatedHoldTime == 0 { + holdTimer = &time.Timer{} + } else { + holdTimer = time.NewTimer(time.Second * time.Duration(fsm.pConf.Timers.State.NegotiatedHoldTime)) + } + + fsm.gracefulRestartTimer.Stop() + + for { + select { + case <-h.t.Dying(): + return -1, NewFsmStateReason(FSM_DYING, nil, nil) + case conn, ok := <-fsm.connCh: + if !ok { + break + } + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("Closed an accepted connection") + case err := <-h.stateReasonCh: + h.conn.Close() + h.t.Kill(nil) + if s := fsm.pConf.GracefulRestart.State; s.Enabled && + (s.NotificationEnabled && err.Type == FSM_NOTIFICATION_RECV || + err.Type == FSM_READ_FAILED || + err.Type == FSM_WRITE_FAILED) { + err = *NewFsmStateReason(FSM_GRACEFUL_RESTART, nil, nil) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Info("peer graceful restart") + fsm.gracefulRestartTimer.Reset(time.Duration(fsm.pConf.GracefulRestart.State.PeerRestartTime) * time.Second) + } + return bgp.BGP_FSM_IDLE, &err + case <-holdTimer.C: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("hold timer expired") + m := bgp.NewBGPNotificationMessage(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED, 0, nil) + h.outgoing.In() <- &FsmOutgoingMsg{Notification: m} + return bgp.BGP_FSM_IDLE, NewFsmStateReason(FSM_HOLD_TIMER_EXPIRED, m, nil) + case <-h.holdTimerResetCh: + if fsm.pConf.Timers.State.NegotiatedHoldTime != 0 { + holdTimer.Reset(time.Second * time.Duration(fsm.pConf.Timers.State.NegotiatedHoldTime)) + } + case stateOp := <-fsm.adminStateCh: + err := h.changeAdminState(stateOp.State) + if err == nil { + switch stateOp.State { + case ADMIN_STATE_DOWN: + m := bgp.NewBGPNotificationMessage(bgp.BGP_ERROR_CEASE, bgp.BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN, stateOp.Communication) + h.outgoing.In() <- &FsmOutgoingMsg{Notification: m} + } + } + } + } +} + +func (h *FSMHandler) loop() error { + fsm := h.fsm + ch := make(chan bgp.FSMState) + oldState := fsm.state + + var reason *FsmStateReason + f := func() error { + nextState := bgp.FSMState(-1) + switch fsm.state { + case bgp.BGP_FSM_IDLE: + nextState, reason = h.idle() + // case bgp.BGP_FSM_CONNECT: + // nextState = h.connect() + case bgp.BGP_FSM_ACTIVE: + nextState, reason = h.active() + case bgp.BGP_FSM_OPENSENT: + nextState, reason = h.opensent() + case bgp.BGP_FSM_OPENCONFIRM: + nextState, reason = h.openconfirm() + case bgp.BGP_FSM_ESTABLISHED: + nextState, reason = h.established() + } + fsm.reason = reason + ch <- nextState + return nil + } + + h.t.Go(f) + + nextState := <-ch + + if nextState == bgp.BGP_FSM_ESTABLISHED && oldState == bgp.BGP_FSM_OPENCONFIRM { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Info("Peer Up") + } + + if oldState == bgp.BGP_FSM_ESTABLISHED { + // The main goroutine sent the notificaiton due to + // deconfiguration or something. + reason := fsm.reason + if fsm.h.sentNotification != nil { + reason.Type = FSM_NOTIFICATION_SENT + reason.PeerDownReason = PEER_DOWN_BY_LOCAL + reason.BGPNotification = fsm.h.sentNotification + } + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "Reason": reason.String(), + }).Info("Peer Down") + } + + e := time.AfterFunc(time.Second*120, func() { + log.WithFields(log.Fields{"Topic": "Peer"}).Fatalf("failed to free the fsm.h.t for %s %s %s", fsm.pConf.State.NeighborAddress, oldState, nextState) + }) + h.t.Wait() + e.Stop() + + // under zero means that tomb.Dying() + if nextState >= bgp.BGP_FSM_IDLE { + h.stateCh <- &FsmMsg{ + MsgType: FSM_MSG_STATE_CHANGE, + MsgSrc: fsm.pConf.State.NeighborAddress, + MsgData: nextState, + StateReason: reason, + Version: h.fsm.version, + } + } + return nil +} + +func (h *FSMHandler) changeAdminState(s AdminState) error { + fsm := h.fsm + if fsm.adminState != s { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + "AdminState": s.String(), + }).Debug("admin state changed") + + fsm.adminState = s + fsm.pConf.State.AdminDown = !fsm.pConf.State.AdminDown + + switch s { + case ADMIN_STATE_UP: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Info("Administrative start") + case ADMIN_STATE_DOWN: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Info("Administrative shutdown") + case ADMIN_STATE_PFX_CT: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Info("Administrative shutdown(Prefix limit reached)") + } + + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": fsm.pConf.State.NeighborAddress, + "State": fsm.state.String(), + }).Warn("cannot change to the same state") + + return fmt.Errorf("cannot change to the same state.") + } + return nil +} diff --git a/pkg/server/fsm_test.go b/pkg/server/fsm_test.go new file mode 100644 index 00000000..dad6563a --- /dev/null +++ b/pkg/server/fsm_test.go @@ -0,0 +1,345 @@ +// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "errors" + "net" + "strconv" + "sync" + "testing" + "time" + + "github.com/eapache/channels" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" +) + +type MockConnection struct { + *testing.T + net.Conn + recvCh chan chan byte + sendBuf [][]byte + currentCh chan byte + isClosed bool + wait int + mtx sync.Mutex +} + +func NewMockConnection(t *testing.T) *MockConnection { + m := &MockConnection{ + T: t, + recvCh: make(chan chan byte, 128), + sendBuf: make([][]byte, 0), + isClosed: false, + } + return m +} + +func (m *MockConnection) SetWriteDeadline(t time.Time) error { + return nil +} + +func (m *MockConnection) setData(data []byte) int { + dataChan := make(chan byte, 4096) + for _, b := range data { + dataChan <- b + } + m.recvCh <- dataChan + return len(dataChan) +} + +func (m *MockConnection) Read(buf []byte) (int, error) { + m.mtx.Lock() + closed := m.isClosed + m.mtx.Unlock() + if closed { + return 0, errors.New("already closed") + } + + if m.currentCh == nil { + m.currentCh = <-m.recvCh + } + + length := 0 + rest := len(buf) + for i := 0; i < rest; i++ { + if len(m.currentCh) > 0 { + val := <-m.currentCh + buf[i] = val + length++ + } else { + m.currentCh = nil + break + } + } + + m.Logf("%d bytes read from peer", length) + return length, nil +} + +func (m *MockConnection) Write(buf []byte) (int, error) { + time.Sleep(time.Duration(m.wait) * time.Millisecond) + m.sendBuf = append(m.sendBuf, buf) + msg, _ := bgp.ParseBGPMessage(buf) + m.Logf("%d bytes written by gobgp message type : %s", + len(buf), showMessageType(msg.Header.Type)) + return len(buf), nil +} + +func showMessageType(t uint8) string { + switch t { + case bgp.BGP_MSG_KEEPALIVE: + return "BGP_MSG_KEEPALIVE" + case bgp.BGP_MSG_NOTIFICATION: + return "BGP_MSG_NOTIFICATION" + case bgp.BGP_MSG_OPEN: + return "BGP_MSG_OPEN" + case bgp.BGP_MSG_UPDATE: + return "BGP_MSG_UPDATE" + case bgp.BGP_MSG_ROUTE_REFRESH: + return "BGP_MSG_ROUTE_REFRESH" + } + return strconv.Itoa(int(t)) +} + +func (m *MockConnection) Close() error { + m.mtx.Lock() + defer m.mtx.Unlock() + if !m.isClosed { + close(m.recvCh) + m.isClosed = true + } + return nil +} + +func (m *MockConnection) LocalAddr() net.Addr { + return &net.TCPAddr{ + IP: net.ParseIP("10.10.10.10"), + Port: bgp.BGP_PORT} +} + +func TestReadAll(t *testing.T) { + assert := assert.New(t) + m := NewMockConnection(t) + msg := open() + expected1, _ := msg.Header.Serialize() + expected2, _ := msg.Body.Serialize() + + pushBytes := func() { + m.Log("push 5 bytes") + m.setData(expected1[0:5]) + m.Log("push rest") + m.setData(expected1[5:]) + m.Log("push bytes at once") + m.setData(expected2) + } + + go pushBytes() + + var actual1 []byte + actual1, _ = readAll(m, bgp.BGP_HEADER_LENGTH) + m.Log(actual1) + assert.Equal(expected1, actual1) + + var actual2 []byte + actual2, _ = readAll(m, len(expected2)) + m.Log(actual2) + assert.Equal(expected2, actual2) +} + +func TestFSMHandlerOpensent_HoldTimerExpired(t *testing.T) { + assert := assert.New(t) + m := NewMockConnection(t) + + p, h := makePeerAndHandler() + + // push mock connection + p.fsm.conn = m + p.fsm.h = h + + // set keepalive ticker + p.fsm.pConf.Timers.State.NegotiatedHoldTime = 3 + + // set holdtime + p.fsm.opensentHoldTime = 2 + + state, _ := h.opensent() + + assert.Equal(bgp.BGP_FSM_IDLE, state) + lastMsg := m.sendBuf[len(m.sendBuf)-1] + sent, _ := bgp.ParseBGPMessage(lastMsg) + assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type) + assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode) + +} + +func TestFSMHandlerOpenconfirm_HoldTimerExpired(t *testing.T) { + assert := assert.New(t) + m := NewMockConnection(t) + + p, h := makePeerAndHandler() + + // push mock connection + p.fsm.conn = m + p.fsm.h = h + + // set up keepalive ticker + p.fsm.pConf.Timers.Config.KeepaliveInterval = 1 + + // set holdtime + p.fsm.pConf.Timers.State.NegotiatedHoldTime = 2 + state, _ := h.openconfirm() + + assert.Equal(bgp.BGP_FSM_IDLE, state) + lastMsg := m.sendBuf[len(m.sendBuf)-1] + sent, _ := bgp.ParseBGPMessage(lastMsg) + assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type) + assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode) + +} + +func TestFSMHandlerEstablish_HoldTimerExpired(t *testing.T) { + assert := assert.New(t) + m := NewMockConnection(t) + + p, h := makePeerAndHandler() + + // push mock connection + p.fsm.conn = m + p.fsm.h = h + + // set keepalive ticker + p.fsm.pConf.Timers.State.NegotiatedHoldTime = 3 + + msg := keepalive() + header, _ := msg.Header.Serialize() + body, _ := msg.Body.Serialize() + + pushPackets := func() { + // first keepalive from peer + m.setData(header) + m.setData(body) + } + + // set holdtime + p.fsm.pConf.Timers.Config.HoldTime = 2 + p.fsm.pConf.Timers.State.NegotiatedHoldTime = 2 + + go pushPackets() + state, _ := h.established() + time.Sleep(time.Second * 1) + assert.Equal(bgp.BGP_FSM_IDLE, state) + m.mtx.Lock() + lastMsg := m.sendBuf[len(m.sendBuf)-1] + m.mtx.Unlock() + sent, _ := bgp.ParseBGPMessage(lastMsg) + assert.Equal(uint8(bgp.BGP_MSG_NOTIFICATION), sent.Header.Type) + assert.Equal(uint8(bgp.BGP_ERROR_HOLD_TIMER_EXPIRED), sent.Body.(*bgp.BGPNotification).ErrorCode) +} + +func TestFSMHandlerOpenconfirm_HoldtimeZero(t *testing.T) { + log.SetLevel(log.DebugLevel) + assert := assert.New(t) + m := NewMockConnection(t) + + p, h := makePeerAndHandler() + + // push mock connection + p.fsm.conn = m + p.fsm.h = h + + // set up keepalive ticker + p.fsm.pConf.Timers.Config.KeepaliveInterval = 1 + // set holdtime + p.fsm.pConf.Timers.State.NegotiatedHoldTime = 0 + go h.openconfirm() + + time.Sleep(100 * time.Millisecond) + + assert.Equal(0, len(m.sendBuf)) + +} + +func TestFSMHandlerEstablished_HoldtimeZero(t *testing.T) { + log.SetLevel(log.DebugLevel) + assert := assert.New(t) + m := NewMockConnection(t) + + p, h := makePeerAndHandler() + + // push mock connection + p.fsm.conn = m + p.fsm.h = h + + // set holdtime + p.fsm.pConf.Timers.State.NegotiatedHoldTime = 0 + + go h.established() + + time.Sleep(100 * time.Millisecond) + + assert.Equal(0, len(m.sendBuf)) +} + +func TestCheckOwnASLoop(t *testing.T) { + assert := assert.New(t) + aspathParam := []bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{65100})} + aspath := bgp.NewPathAttributeAsPath(aspathParam) + assert.False(hasOwnASLoop(65100, 10, aspath)) + assert.True(hasOwnASLoop(65100, 0, aspath)) + assert.False(hasOwnASLoop(65200, 0, aspath)) +} + +func makePeerAndHandler() (*Peer, *FSMHandler) { + p := &Peer{ + fsm: NewFSM(&config.Global{}, &config.Neighbor{}, table.NewRoutingPolicy()), + outgoing: channels.NewInfiniteChannel(), + } + + h := &FSMHandler{ + fsm: p.fsm, + stateReasonCh: make(chan FsmStateReason, 2), + incoming: channels.NewInfiniteChannel(), + outgoing: p.outgoing, + } + + return p, h + +} + +func open() *bgp.BGPMessage { + p1 := bgp.NewOptionParameterCapability( + []bgp.ParameterCapabilityInterface{bgp.NewCapRouteRefresh()}) + p2 := bgp.NewOptionParameterCapability( + []bgp.ParameterCapabilityInterface{bgp.NewCapMultiProtocol(bgp.RF_IPv4_UC)}) + g := &bgp.CapGracefulRestartTuple{AFI: 4, SAFI: 2, Flags: 3} + p3 := bgp.NewOptionParameterCapability( + []bgp.ParameterCapabilityInterface{bgp.NewCapGracefulRestart(true, true, 100, + []*bgp.CapGracefulRestartTuple{g})}) + p4 := bgp.NewOptionParameterCapability( + []bgp.ParameterCapabilityInterface{bgp.NewCapFourOctetASNumber(100000)}) + return bgp.NewBGPOpenMessage(11033, 303, "100.4.10.3", + []bgp.OptionParameterInterface{p1, p2, p3, p4}) +} + +func keepalive() *bgp.BGPMessage { + return bgp.NewBGPKeepAliveMessage() +} diff --git a/pkg/server/grpc_server.go b/pkg/server/grpc_server.go new file mode 100644 index 00000000..37b7c57b --- /dev/null +++ b/pkg/server/grpc_server.go @@ -0,0 +1,3016 @@ +// Copyright (C) 2014-2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "bytes" + "fmt" + "io" + "net" + "reflect" + "regexp" + "strconv" + "strings" + "sync" + "time" + + farm "github.com/dgryski/go-farm" + "github.com/golang/protobuf/ptypes/any" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "google.golang.org/grpc" + + api "github.com/osrg/gobgp/api" + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/internal/pkg/zebra" + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +type Server struct { + bgpServer *BgpServer + grpcServer *grpc.Server + hosts string +} + +func NewGrpcServer(b *BgpServer, hosts string) *Server { + size := 256 << 20 + return NewServer(b, grpc.NewServer(grpc.MaxRecvMsgSize(size), grpc.MaxSendMsgSize(size)), hosts) +} + +func NewServer(b *BgpServer, g *grpc.Server, hosts string) *Server { + grpc.EnableTracing = false + s := &Server{ + bgpServer: b, + grpcServer: g, + hosts: hosts, + } + api.RegisterGobgpApiServer(g, s) + return s +} + +func (s *Server) Serve() error { + var wg sync.WaitGroup + l := strings.Split(s.hosts, ",") + wg.Add(len(l)) + + serve := func(host string) { + defer wg.Done() + lis, err := net.Listen("tcp", host) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "grpc", + "Key": host, + "Error": err, + }).Warn("listen failed") + return + } + err = s.grpcServer.Serve(lis) + log.WithFields(log.Fields{ + "Topic": "grpc", + "Key": host, + "Error": err, + }).Warn("accept failed") + } + + for _, host := range l { + go serve(host) + } + wg.Wait() + return nil +} + +func NewMpGracefulRestartFromConfigStruct(c *config.MpGracefulRestart) *api.MpGracefulRestart { + return &api.MpGracefulRestart{ + Config: &api.MpGracefulRestartConfig{ + Enabled: c.Config.Enabled, + }, + } +} + +func extractFamilyFromConfigAfiSafi(c *config.AfiSafi) uint32 { + if c == nil { + return 0 + } + // If address family value is already stored in AfiSafiState structure, + // we prefer to use this value. + if c.State.Family != 0 { + return uint32(c.State.Family) + } + // In case that Neighbor structure came from CLI or gRPC, address family + // value in AfiSafiState structure can be omitted. + // Here extracts value from AfiSafiName field in AfiSafiConfig structure. + if rf, err := bgp.GetRouteFamily(string(c.Config.AfiSafiName)); err == nil { + return uint32(rf) + } + // Ignores invalid address family name + return 0 +} + +func NewAfiSafiConfigFromConfigStruct(c *config.AfiSafi) *api.AfiSafiConfig { + return &api.AfiSafiConfig{ + Family: extractFamilyFromConfigAfiSafi(c), + Enabled: c.Config.Enabled, + } +} + +func NewApplyPolicyFromConfigStruct(c *config.ApplyPolicy) *api.ApplyPolicy { + applyPolicy := &api.ApplyPolicy{ + ImportPolicy: &api.PolicyAssignment{ + Type: api.PolicyType_IMPORT, + Default: api.RouteAction(c.Config.DefaultImportPolicy.ToInt()), + }, + ExportPolicy: &api.PolicyAssignment{ + Type: api.PolicyType_EXPORT, + Default: api.RouteAction(c.Config.DefaultExportPolicy.ToInt()), + }, + InPolicy: &api.PolicyAssignment{ + Type: api.PolicyType_IN, + Default: api.RouteAction(c.Config.DefaultInPolicy.ToInt()), + }, + } + + for _, pname := range c.Config.ImportPolicyList { + applyPolicy.ImportPolicy.Policies = append(applyPolicy.ImportPolicy.Policies, &api.Policy{Name: pname}) + } + for _, pname := range c.Config.ExportPolicyList { + applyPolicy.ExportPolicy.Policies = append(applyPolicy.ExportPolicy.Policies, &api.Policy{Name: pname}) + } + for _, pname := range c.Config.InPolicyList { + applyPolicy.InPolicy.Policies = append(applyPolicy.InPolicy.Policies, &api.Policy{Name: pname}) + } + + return applyPolicy +} + +func NewRouteSelectionOptionsFromConfigStruct(c *config.RouteSelectionOptions) *api.RouteSelectionOptions { + return &api.RouteSelectionOptions{ + Config: &api.RouteSelectionOptionsConfig{ + AlwaysCompareMed: c.Config.AlwaysCompareMed, + IgnoreAsPathLength: c.Config.IgnoreAsPathLength, + ExternalCompareRouterId: c.Config.ExternalCompareRouterId, + AdvertiseInactiveRoutes: c.Config.AdvertiseInactiveRoutes, + EnableAigp: c.Config.EnableAigp, + IgnoreNextHopIgpMetric: c.Config.IgnoreNextHopIgpMetric, + }, + } +} + +func NewUseMultiplePathsFromConfigStruct(c *config.UseMultiplePaths) *api.UseMultiplePaths { + return &api.UseMultiplePaths{ + Config: &api.UseMultiplePathsConfig{ + Enabled: c.Config.Enabled, + }, + Ebgp: &api.Ebgp{ + Config: &api.EbgpConfig{ + AllowMultipleAs: c.Ebgp.Config.AllowMultipleAs, + MaximumPaths: c.Ebgp.Config.MaximumPaths, + }, + }, + Ibgp: &api.Ibgp{ + Config: &api.IbgpConfig{ + MaximumPaths: c.Ibgp.Config.MaximumPaths, + }, + }, + } +} + +func NewPrefixLimitFromConfigStruct(c *config.AfiSafi) *api.PrefixLimit { + if c.PrefixLimit.Config.MaxPrefixes == 0 { + return nil + } + + return &api.PrefixLimit{ + Family: uint32(c.State.Family), + MaxPrefixes: c.PrefixLimit.Config.MaxPrefixes, + ShutdownThresholdPct: uint32(c.PrefixLimit.Config.ShutdownThresholdPct), + } +} + +func NewRouteTargetMembershipFromConfigStruct(c *config.RouteTargetMembership) *api.RouteTargetMembership { + return &api.RouteTargetMembership{ + Config: &api.RouteTargetMembershipConfig{ + DeferralTime: uint32(c.Config.DeferralTime), + }, + } +} + +func NewLongLivedGracefulRestartFromConfigStruct(c *config.LongLivedGracefulRestart) *api.LongLivedGracefulRestart { + return &api.LongLivedGracefulRestart{ + Config: &api.LongLivedGracefulRestartConfig{ + Enabled: c.Config.Enabled, + RestartTime: c.Config.RestartTime, + }, + } +} + +func NewAddPathsFromConfigStruct(c *config.AddPaths) *api.AddPaths { + return &api.AddPaths{ + Config: &api.AddPathsConfig{ + Receive: c.Config.Receive, + SendMax: uint32(c.Config.SendMax), + }, + } +} + +func NewAfiSafiFromConfigStruct(c *config.AfiSafi) *api.AfiSafi { + return &api.AfiSafi{ + MpGracefulRestart: NewMpGracefulRestartFromConfigStruct(&c.MpGracefulRestart), + Config: NewAfiSafiConfigFromConfigStruct(c), + ApplyPolicy: NewApplyPolicyFromConfigStruct(&c.ApplyPolicy), + RouteSelectionOptions: NewRouteSelectionOptionsFromConfigStruct(&c.RouteSelectionOptions), + UseMultiplePaths: NewUseMultiplePathsFromConfigStruct(&c.UseMultiplePaths), + PrefixLimits: NewPrefixLimitFromConfigStruct(c), + RouteTargetMembership: NewRouteTargetMembershipFromConfigStruct(&c.RouteTargetMembership), + LongLivedGracefulRestart: NewLongLivedGracefulRestartFromConfigStruct(&c.LongLivedGracefulRestart), + AddPaths: NewAddPathsFromConfigStruct(&c.AddPaths), + } +} + +func NewPeerFromConfigStruct(pconf *config.Neighbor) *api.Peer { + families := make([]uint32, 0, len(pconf.AfiSafis)) + prefixLimits := make([]*api.PrefixLimit, 0, len(pconf.AfiSafis)) + afiSafis := make([]*api.AfiSafi, 0, len(pconf.AfiSafis)) + for _, f := range pconf.AfiSafis { + families = append(families, extractFamilyFromConfigAfiSafi(&f)) + if prefixLimit := NewPrefixLimitFromConfigStruct(&f); prefixLimit != nil { + prefixLimits = append(prefixLimits, prefixLimit) + } + if afiSafi := NewAfiSafiFromConfigStruct(&f); afiSafi != nil { + afiSafis = append(afiSafis, afiSafi) + } + } + + timer := pconf.Timers + s := pconf.State + localAddress := pconf.Transport.Config.LocalAddress + if pconf.Transport.State.LocalAddress != "" { + localAddress = pconf.Transport.State.LocalAddress + } + remoteCap, err := api.MarshalCapabilities(pconf.State.RemoteCapabilityList) + if err != nil { + return nil + } + localCap, err := api.MarshalCapabilities(pconf.State.LocalCapabilityList) + if err != nil { + return nil + } + var removePrivateAs api.PeerConf_RemovePrivateAs + switch pconf.Config.RemovePrivateAs { + case config.REMOVE_PRIVATE_AS_OPTION_ALL: + removePrivateAs = api.PeerConf_ALL + case config.REMOVE_PRIVATE_AS_OPTION_REPLACE: + removePrivateAs = api.PeerConf_REPLACE + } + return &api.Peer{ + Families: families, + ApplyPolicy: NewApplyPolicyFromConfigStruct(&pconf.ApplyPolicy), + Conf: &api.PeerConf{ + NeighborAddress: pconf.Config.NeighborAddress, + Id: s.RemoteRouterId, + PeerAs: pconf.Config.PeerAs, + LocalAs: pconf.Config.LocalAs, + PeerType: uint32(pconf.Config.PeerType.ToInt()), + AuthPassword: pconf.Config.AuthPassword, + RouteFlapDamping: pconf.Config.RouteFlapDamping, + Description: pconf.Config.Description, + PeerGroup: pconf.Config.PeerGroup, + RemoteCap: remoteCap, + LocalCap: localCap, + PrefixLimits: prefixLimits, + LocalAddress: localAddress, + NeighborInterface: pconf.Config.NeighborInterface, + Vrf: pconf.Config.Vrf, + AllowOwnAs: uint32(pconf.AsPathOptions.Config.AllowOwnAs), + RemovePrivateAs: removePrivateAs, + ReplacePeerAs: pconf.AsPathOptions.Config.ReplacePeerAs, + }, + Info: &api.PeerState{ + BgpState: string(s.SessionState), + AdminState: api.PeerState_AdminState(s.AdminState.ToInt()), + Messages: &api.Messages{ + Received: &api.Message{ + NOTIFICATION: s.Messages.Received.Notification, + UPDATE: s.Messages.Received.Update, + OPEN: s.Messages.Received.Open, + KEEPALIVE: s.Messages.Received.Keepalive, + REFRESH: s.Messages.Received.Refresh, + DISCARDED: s.Messages.Received.Discarded, + TOTAL: s.Messages.Received.Total, + }, + Sent: &api.Message{ + NOTIFICATION: s.Messages.Sent.Notification, + UPDATE: s.Messages.Sent.Update, + OPEN: s.Messages.Sent.Open, + KEEPALIVE: s.Messages.Sent.Keepalive, + REFRESH: s.Messages.Sent.Refresh, + DISCARDED: s.Messages.Sent.Discarded, + TOTAL: s.Messages.Sent.Total, + }, + }, + Received: s.AdjTable.Received, + Accepted: s.AdjTable.Accepted, + Advertised: s.AdjTable.Advertised, + PeerAs: s.PeerAs, + PeerType: uint32(s.PeerType.ToInt()), + NeighborAddress: pconf.State.NeighborAddress, + }, + Timers: &api.Timers{ + Config: &api.TimersConfig{ + ConnectRetry: uint64(timer.Config.ConnectRetry), + HoldTime: uint64(timer.Config.HoldTime), + KeepaliveInterval: uint64(timer.Config.KeepaliveInterval), + }, + State: &api.TimersState{ + KeepaliveInterval: uint64(timer.State.KeepaliveInterval), + NegotiatedHoldTime: uint64(timer.State.NegotiatedHoldTime), + Uptime: uint64(timer.State.Uptime), + Downtime: uint64(timer.State.Downtime), + }, + }, + RouteReflector: &api.RouteReflector{ + RouteReflectorClient: pconf.RouteReflector.Config.RouteReflectorClient, + RouteReflectorClusterId: string(pconf.RouteReflector.State.RouteReflectorClusterId), + }, + RouteServer: &api.RouteServer{ + RouteServerClient: pconf.RouteServer.Config.RouteServerClient, + }, + GracefulRestart: &api.GracefulRestart{ + Enabled: pconf.GracefulRestart.Config.Enabled, + RestartTime: uint32(pconf.GracefulRestart.Config.RestartTime), + HelperOnly: pconf.GracefulRestart.Config.HelperOnly, + DeferralTime: uint32(pconf.GracefulRestart.Config.DeferralTime), + NotificationEnabled: pconf.GracefulRestart.Config.NotificationEnabled, + LonglivedEnabled: pconf.GracefulRestart.Config.LongLivedEnabled, + LocalRestarting: pconf.GracefulRestart.State.LocalRestarting, + }, + Transport: &api.Transport{ + RemotePort: uint32(pconf.Transport.Config.RemotePort), + LocalAddress: pconf.Transport.Config.LocalAddress, + PassiveMode: pconf.Transport.Config.PassiveMode, + }, + AfiSafis: afiSafis, + AddPaths: NewAddPathsFromConfigStruct(&pconf.AddPaths), + } +} + +func NewPeerGroupFromConfigStruct(pconf *config.PeerGroup) *api.PeerGroup { + families := make([]uint32, 0, len(pconf.AfiSafis)) + afiSafis := make([]*api.AfiSafi, 0, len(pconf.AfiSafis)) + for _, f := range pconf.AfiSafis { + families = append(families, extractFamilyFromConfigAfiSafi(&f)) + if afiSafi := NewAfiSafiFromConfigStruct(&f); afiSafi != nil { + afiSafis = append(afiSafis, afiSafi) + } + } + + timer := pconf.Timers + s := pconf.State + return &api.PeerGroup{ + Families: families, + ApplyPolicy: NewApplyPolicyFromConfigStruct(&pconf.ApplyPolicy), + Conf: &api.PeerGroupConf{ + PeerAs: pconf.Config.PeerAs, + LocalAs: pconf.Config.LocalAs, + PeerType: uint32(pconf.Config.PeerType.ToInt()), + AuthPassword: pconf.Config.AuthPassword, + RouteFlapDamping: pconf.Config.RouteFlapDamping, + Description: pconf.Config.Description, + PeerGroupName: pconf.Config.PeerGroupName, + }, + Info: &api.PeerGroupState{ + PeerAs: s.PeerAs, + PeerType: uint32(s.PeerType.ToInt()), + TotalPaths: s.TotalPaths, + TotalPrefixes: s.TotalPrefixes, + }, + Timers: &api.Timers{ + Config: &api.TimersConfig{ + ConnectRetry: uint64(timer.Config.ConnectRetry), + HoldTime: uint64(timer.Config.HoldTime), + KeepaliveInterval: uint64(timer.Config.KeepaliveInterval), + }, + State: &api.TimersState{ + KeepaliveInterval: uint64(timer.State.KeepaliveInterval), + NegotiatedHoldTime: uint64(timer.State.NegotiatedHoldTime), + Uptime: uint64(timer.State.Uptime), + Downtime: uint64(timer.State.Downtime), + }, + }, + RouteReflector: &api.RouteReflector{ + RouteReflectorClient: pconf.RouteReflector.Config.RouteReflectorClient, + RouteReflectorClusterId: string(pconf.RouteReflector.Config.RouteReflectorClusterId), + }, + RouteServer: &api.RouteServer{ + RouteServerClient: pconf.RouteServer.Config.RouteServerClient, + }, + GracefulRestart: &api.GracefulRestart{ + Enabled: pconf.GracefulRestart.Config.Enabled, + RestartTime: uint32(pconf.GracefulRestart.Config.RestartTime), + HelperOnly: pconf.GracefulRestart.Config.HelperOnly, + DeferralTime: uint32(pconf.GracefulRestart.Config.DeferralTime), + NotificationEnabled: pconf.GracefulRestart.Config.NotificationEnabled, + LonglivedEnabled: pconf.GracefulRestart.Config.LongLivedEnabled, + LocalRestarting: pconf.GracefulRestart.State.LocalRestarting, + }, + Transport: &api.Transport{ + RemotePort: uint32(pconf.Transport.Config.RemotePort), + LocalAddress: pconf.Transport.Config.LocalAddress, + PassiveMode: pconf.Transport.Config.PassiveMode, + }, + AfiSafis: afiSafis, + AddPaths: NewAddPathsFromConfigStruct(&pconf.AddPaths), + } +} + +func (s *Server) GetNeighbor(ctx context.Context, arg *api.GetNeighborRequest) (*api.GetNeighborResponse, error) { + if arg == nil { + return nil, fmt.Errorf("invalid request") + } + neighbors := s.bgpServer.GetNeighbor(arg.Address, arg.EnableAdvertised) + peers := make([]*api.Peer, 0, len(neighbors)) + for _, e := range neighbors { + peers = append(peers, NewPeerFromConfigStruct(e)) + } + return &api.GetNeighborResponse{Peers: peers}, nil +} + +func NewValidationFromTableStruct(v *table.Validation) *api.RPKIValidation { + if v == nil { + return &api.RPKIValidation{} + } + return &api.RPKIValidation{ + Reason: api.RPKIValidation_Reason(v.Reason.ToInt()), + Matched: NewRoaListFromTableStructList(v.Matched), + UnmatchedAs: NewRoaListFromTableStructList(v.UnmatchedAs), + UnmatchedLength: NewRoaListFromTableStructList(v.UnmatchedLength), + } +} + +func toPathAPI(binNlri []byte, binPattrs [][]byte, anyNlri *any.Any, anyPattrs []*any.Any, path *table.Path, v *table.Validation) *api.Path { + nlri := path.GetNlri() + family := uint32(path.GetRouteFamily()) + vv := config.RPKI_VALIDATION_RESULT_TYPE_NONE.ToInt() + if v != nil { + vv = v.Status.ToInt() + } + p := &api.Path{ + Nlri: binNlri, + Pattrs: binPattrs, + Age: path.GetTimestamp().Unix(), + IsWithdraw: path.IsWithdraw, + Validation: int32(vv), + ValidationDetail: NewValidationFromTableStruct(v), + Family: family, + Stale: path.IsStale(), + IsFromExternal: path.IsFromExternal(), + NoImplicitWithdraw: path.NoImplicitWithdraw(), + IsNexthopInvalid: path.IsNexthopInvalid, + Identifier: nlri.PathIdentifier(), + LocalIdentifier: nlri.PathLocalIdentifier(), + AnyNlri: anyNlri, + AnyPattrs: anyPattrs, + } + if s := path.GetSource(); s != nil { + p.SourceAsn = s.AS + p.SourceId = s.ID.String() + p.NeighborIp = s.Address.String() + } + return p +} + +func ToPathApi(path *table.Path, v *table.Validation) *api.Path { + nlri := path.GetNlri() + anyNlri := api.MarshalNLRI(nlri) + if path.IsWithdraw { + return toPathAPI(nil, nil, anyNlri, nil, path, v) + } + anyPattrs := api.MarshalPathAttributes(path.GetPathAttrs()) + return toPathAPI(nil, nil, anyNlri, anyPattrs, path, v) +} + +func getValidation(v []*table.Validation, i int) *table.Validation { + if v == nil { + return nil + } else { + return v[i] + } +} + +func (s *Server) GetRib(ctx context.Context, arg *api.GetRibRequest) (*api.GetRibResponse, error) { + if arg == nil || arg.Table == nil { + return nil, fmt.Errorf("invalid request") + } + f := func() []*table.LookupPrefix { + l := make([]*table.LookupPrefix, 0, len(arg.Table.Destinations)) + for _, p := range arg.Table.Destinations { + l = append(l, &table.LookupPrefix{ + Prefix: p.Prefix, + LookupOption: func() table.LookupOption { + if p.LongerPrefixes { + return table.LOOKUP_LONGER + } else if p.ShorterPrefixes { + return table.LOOKUP_SHORTER + } + return table.LOOKUP_EXACT + }(), + }) + } + return l + } + + var in bool + var err error + var tbl *table.Table + var v []*table.Validation + + family := bgp.RouteFamily(arg.Table.Family) + switch arg.Table.Type { + case api.Resource_LOCAL, api.Resource_GLOBAL: + tbl, v, err = s.bgpServer.GetRib(arg.Table.Name, family, f()) + case api.Resource_ADJ_IN: + in = true + fallthrough + case api.Resource_ADJ_OUT: + tbl, v, err = s.bgpServer.GetAdjRib(arg.Table.Name, family, in, f()) + case api.Resource_VRF: + tbl, err = s.bgpServer.GetVrfRib(arg.Table.Name, family, []*table.LookupPrefix{}) + default: + return nil, fmt.Errorf("unsupported resource type: %v", arg.Table.Type) + } + + if err != nil { + return nil, err + } + + tblDsts := tbl.GetDestinations() + dsts := make([]*api.Destination, 0, len(tblDsts)) + idx := 0 + for _, dst := range tblDsts { + dsts = append(dsts, &api.Destination{ + Prefix: dst.GetNlri().String(), + Paths: func(paths []*table.Path) []*api.Path { + l := make([]*api.Path, 0, len(paths)) + for i, p := range paths { + pp := ToPathApi(p, getValidation(v, idx)) + idx++ + switch arg.Table.Type { + case api.Resource_LOCAL, api.Resource_GLOBAL: + if i == 0 && !table.SelectionOptions.DisableBestPathSelection { + pp.Best = true + } + } + l = append(l, pp) + } + return l + }(dst.GetAllKnownPathList()), + }) + } + + return &api.GetRibResponse{Table: &api.Table{ + Type: arg.Table.Type, + Family: uint32(tbl.GetRoutefamily()), + Destinations: dsts}, + }, err +} + +func (s *Server) GetPath(arg *api.GetPathRequest, stream api.GobgpApi_GetPathServer) error { + f := func() []*table.LookupPrefix { + l := make([]*table.LookupPrefix, 0, len(arg.Prefixes)) + for _, p := range arg.Prefixes { + l = append(l, &table.LookupPrefix{ + Prefix: p.Prefix, + LookupOption: table.LookupOption(p.LookupOption), + }) + } + return l + } + + in := false + family := bgp.RouteFamily(arg.Family) + var tbl *table.Table + var err error + var v []*table.Validation + switch arg.Type { + case api.Resource_LOCAL, api.Resource_GLOBAL: + tbl, v, err = s.bgpServer.GetRib(arg.Name, family, f()) + case api.Resource_ADJ_IN: + in = true + fallthrough + case api.Resource_ADJ_OUT: + tbl, v, err = s.bgpServer.GetAdjRib(arg.Name, family, in, f()) + case api.Resource_VRF: + tbl, err = s.bgpServer.GetVrfRib(arg.Name, family, []*table.LookupPrefix{}) + default: + return fmt.Errorf("unsupported resource type: %v", arg.Type) + } + if err != nil { + return err + } + + idx := 0 + return func() error { + for _, dst := range tbl.GetDestinations() { + for i, path := range dst.GetAllKnownPathList() { + p := ToPathApi(path, getValidation(v, idx)) + idx++ + if i == 0 && !table.SelectionOptions.DisableBestPathSelection { + switch arg.Type { + case api.Resource_LOCAL, api.Resource_GLOBAL: + p.Best = true + } + } + if err := stream.Send(p); err != nil { + return err + } + } + } + return nil + }() +} + +func (s *Server) MonitorRib(arg *api.MonitorRibRequest, stream api.GobgpApi_MonitorRibServer) error { + if arg == nil || arg.Table == nil { + return fmt.Errorf("invalid request") + } + t := arg.Table + w, err := func() (*Watcher, error) { + switch t.Type { + case api.Resource_GLOBAL: + return s.bgpServer.Watch(WatchBestPath(arg.Current)), nil + case api.Resource_ADJ_IN: + if t.PostPolicy { + return s.bgpServer.Watch(WatchPostUpdate(arg.Current)), nil + } + return s.bgpServer.Watch(WatchUpdate(arg.Current)), nil + default: + return nil, fmt.Errorf("unsupported resource type: %v", t.Type) + } + }() + if err != nil { + return nil + } + + return func() error { + defer func() { w.Stop() }() + + sendPath := func(pathList []*table.Path) error { + dsts := make(map[string]*api.Destination) + for _, path := range pathList { + if path == nil || (t.Family != 0 && bgp.RouteFamily(t.Family) != path.GetRouteFamily()) { + continue + } + if dst, y := dsts[path.GetNlri().String()]; y { + dst.Paths = append(dst.Paths, ToPathApi(path, nil)) + } else { + dsts[path.GetNlri().String()] = &api.Destination{ + Prefix: path.GetNlri().String(), + Paths: []*api.Path{ToPathApi(path, nil)}, + } + } + } + for _, dst := range dsts { + if err := stream.Send(dst); err != nil { + return err + } + } + return nil + } + + for ev := range w.Event() { + switch msg := ev.(type) { + case *WatchEventBestPath: + if err := sendPath(func() []*table.Path { + if len(msg.MultiPathList) > 0 { + l := make([]*table.Path, 0) + for _, p := range msg.MultiPathList { + l = append(l, p...) + } + return l + } else { + return msg.PathList + } + }()); err != nil { + return err + } + case *WatchEventUpdate: + if err := sendPath(msg.PathList); err != nil { + return err + } + } + } + return nil + }() +} + +func (s *Server) MonitorPeerState(arg *api.Arguments, stream api.GobgpApi_MonitorPeerStateServer) error { + if arg == nil { + return fmt.Errorf("invalid request") + } + return func() error { + w := s.bgpServer.Watch(WatchPeerState(arg.Current)) + defer func() { w.Stop() }() + + for ev := range w.Event() { + switch msg := ev.(type) { + case *WatchEventPeerState: + if len(arg.Name) > 0 && arg.Name != msg.PeerAddress.String() && arg.Name != msg.PeerInterface { + continue + } + if err := stream.Send(&api.Peer{ + Conf: &api.PeerConf{ + PeerAs: msg.PeerAS, + LocalAs: msg.LocalAS, + NeighborAddress: msg.PeerAddress.String(), + Id: msg.PeerID.String(), + NeighborInterface: msg.PeerInterface, + }, + Info: &api.PeerState{ + PeerAs: msg.PeerAS, + LocalAs: msg.LocalAS, + NeighborAddress: msg.PeerAddress.String(), + BgpState: msg.State.String(), + AdminState: api.PeerState_AdminState(msg.AdminState), + }, + Transport: &api.Transport{ + LocalAddress: msg.LocalAddress.String(), + LocalPort: uint32(msg.LocalPort), + RemotePort: uint32(msg.PeerPort), + }, + }); err != nil { + return err + } + } + } + return nil + }() +} + +func (s *Server) ResetNeighbor(ctx context.Context, arg *api.ResetNeighborRequest) (*api.ResetNeighborResponse, error) { + return &api.ResetNeighborResponse{}, s.bgpServer.ResetNeighbor(arg.Address, arg.Communication) +} + +func (s *Server) SoftResetNeighbor(ctx context.Context, arg *api.SoftResetNeighborRequest) (*api.SoftResetNeighborResponse, error) { + var err error + addr := arg.Address + if addr == "all" { + addr = "" + } + family := bgp.RouteFamily(0) + switch arg.Direction { + case api.SoftResetNeighborRequest_IN: + err = s.bgpServer.SoftResetIn(addr, family) + case api.SoftResetNeighborRequest_OUT: + err = s.bgpServer.SoftResetOut(addr, family) + default: + err = s.bgpServer.SoftReset(addr, family) + } + return &api.SoftResetNeighborResponse{}, err +} + +func (s *Server) ShutdownNeighbor(ctx context.Context, arg *api.ShutdownNeighborRequest) (*api.ShutdownNeighborResponse, error) { + return &api.ShutdownNeighborResponse{}, s.bgpServer.ShutdownNeighbor(arg.Address, arg.Communication) +} + +func (s *Server) EnableNeighbor(ctx context.Context, arg *api.EnableNeighborRequest) (*api.EnableNeighborResponse, error) { + return &api.EnableNeighborResponse{}, s.bgpServer.EnableNeighbor(arg.Address) +} + +func (s *Server) DisableNeighbor(ctx context.Context, arg *api.DisableNeighborRequest) (*api.DisableNeighborResponse, error) { + return &api.DisableNeighborResponse{}, s.bgpServer.DisableNeighbor(arg.Address, arg.Communication) +} + +func (s *Server) UpdatePolicy(ctx context.Context, arg *api.UpdatePolicyRequest) (*api.UpdatePolicyResponse, error) { + rp, err := NewRoutingPolicyFromApiStruct(arg) + if err != nil { + return nil, err + } + return &api.UpdatePolicyResponse{}, s.bgpServer.UpdatePolicy(*rp) +} + +func NewAPIRoutingPolicyFromConfigStruct(c *config.RoutingPolicy) (*api.RoutingPolicy, error) { + definedSets, err := NewAPIDefinedSetsFromConfigStruct(&c.DefinedSets) + if err != nil { + return nil, err + } + policies := make([]*api.Policy, 0, len(c.PolicyDefinitions)) + for _, policy := range c.PolicyDefinitions { + policies = append(policies, toPolicyApi(&policy)) + } + + return &api.RoutingPolicy{ + DefinedSet: definedSets, + PolicyDefinition: policies, + }, nil +} + +func NewRoutingPolicyFromApiStruct(arg *api.UpdatePolicyRequest) (*config.RoutingPolicy, error) { + policyDefinitions := make([]config.PolicyDefinition, 0, len(arg.Policies)) + for _, p := range arg.Policies { + pd, err := NewConfigPolicyFromApiStruct(p) + if err != nil { + return nil, err + } + policyDefinitions = append(policyDefinitions, *pd) + } + + definedSets, err := NewConfigDefinedSetsFromApiStruct(arg.Sets) + if err != nil { + return nil, err + } + + return &config.RoutingPolicy{ + DefinedSets: *definedSets, + PolicyDefinitions: policyDefinitions, + }, nil +} + +func (s *Server) api2PathList(resource api.Resource, ApiPathList []*api.Path) ([]*table.Path, error) { + var pi *table.PeerInfo + + pathList := make([]*table.Path, 0, len(ApiPathList)) + for _, path := range ApiPathList { + var nlri bgp.AddrPrefixInterface + var nexthop string + + if path.SourceAsn != 0 { + pi = &table.PeerInfo{ + AS: path.SourceAsn, + LocalID: net.ParseIP(path.SourceId), + } + } + + nlri, err := path.GetNativeNlri() + if err != nil { + return nil, err + } + nlri.SetPathIdentifier(path.Identifier) + + attrList, err := path.GetNativePathAttributes() + if err != nil { + return nil, err + } + + pattrs := make([]bgp.PathAttributeInterface, 0) + seen := make(map[bgp.BGPAttrType]struct{}) + for _, attr := range attrList { + attrType := attr.GetType() + if _, ok := seen[attrType]; !ok { + seen[attrType] = struct{}{} + } else { + return nil, fmt.Errorf("duplicated path attribute type: %d", attrType) + } + + switch a := attr.(type) { + case *bgp.PathAttributeNextHop: + nexthop = a.Value.String() + case *bgp.PathAttributeMpReachNLRI: + nlri = a.Value[0] + nexthop = a.Nexthop.String() + default: + pattrs = append(pattrs, attr) + } + } + + if nlri == nil { + return nil, fmt.Errorf("nlri not found") + } else if !path.IsWithdraw && nexthop == "" { + return nil, fmt.Errorf("nexthop not found") + } + + if resource != api.Resource_VRF && bgp.RouteFamily(path.Family) == bgp.RF_IPv4_UC && net.ParseIP(nexthop).To4() != nil { + pattrs = append(pattrs, bgp.NewPathAttributeNextHop(nexthop)) + } else { + pattrs = append(pattrs, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) + } + + newPath := table.NewPath(pi, nlri, path.IsWithdraw, pattrs, time.Now(), path.NoImplicitWithdraw) + if !path.IsWithdraw { + total := bytes.NewBuffer(make([]byte, 0)) + for _, a := range newPath.GetPathAttrs() { + if a.GetType() == bgp.BGP_ATTR_TYPE_MP_REACH_NLRI { + continue + } + b, _ := a.Serialize() + total.Write(b) + } + newPath.SetHash(farm.Hash32(total.Bytes())) + } + newPath.SetIsFromExternal(path.IsFromExternal) + pathList = append(pathList, newPath) + } + return pathList, nil +} + +func (s *Server) AddPath(ctx context.Context, arg *api.AddPathRequest) (*api.AddPathResponse, error) { + pathList, err := s.api2PathList(arg.Resource, []*api.Path{arg.Path}) + var uuid []byte + if err == nil { + uuid, err = s.bgpServer.AddPath(arg.VrfId, pathList) + } + return &api.AddPathResponse{Uuid: uuid}, err +} + +func (s *Server) DeletePath(ctx context.Context, arg *api.DeletePathRequest) (*api.DeletePathResponse, error) { + pathList, err := func() ([]*table.Path, error) { + if arg.Path != nil { + arg.Path.IsWithdraw = true + return s.api2PathList(arg.Resource, []*api.Path{arg.Path}) + } + return []*table.Path{}, nil + }() + if err != nil { + return nil, err + } + return &api.DeletePathResponse{}, s.bgpServer.DeletePath(arg.Uuid, bgp.RouteFamily(arg.Family), arg.VrfId, pathList) +} + +func (s *Server) EnableMrt(ctx context.Context, arg *api.EnableMrtRequest) (*api.EnableMrtResponse, error) { + return &api.EnableMrtResponse{}, s.bgpServer.EnableMrt(&config.MrtConfig{ + RotationInterval: arg.Interval, + DumpType: config.IntToMrtTypeMap[int(arg.DumpType)], + FileName: arg.Filename, + }) +} + +func (s *Server) DisableMrt(ctx context.Context, arg *api.DisableMrtRequest) (*api.DisableMrtResponse, error) { + return &api.DisableMrtResponse{}, s.bgpServer.DisableMrt(&config.MrtConfig{}) +} + +func (s *Server) InjectMrt(stream api.GobgpApi_InjectMrtServer) error { + for { + arg, err := stream.Recv() + + if err == io.EOF { + break + } else if err != nil { + return err + } + + if arg.Resource != api.Resource_GLOBAL && arg.Resource != api.Resource_VRF { + return fmt.Errorf("unsupported resource: %s", arg.Resource) + } + + if pathList, err := s.api2PathList(arg.Resource, arg.Paths); err != nil { + return err + } else { + if _, err = s.bgpServer.AddPath("", pathList); err != nil { + return err + } + } + } + return stream.SendAndClose(&api.InjectMrtResponse{}) +} + +func (s *Server) AddBmp(ctx context.Context, arg *api.AddBmpRequest) (*api.AddBmpResponse, error) { + t, ok := config.IntToBmpRouteMonitoringPolicyTypeMap[int(arg.Type)] + if !ok { + return nil, fmt.Errorf("invalid bmp route monitoring policy: %d", arg.Type) + } + return &api.AddBmpResponse{}, s.bgpServer.AddBmp(&config.BmpServerConfig{ + Address: arg.Address, + Port: arg.Port, + RouteMonitoringPolicy: t, + }) +} + +func (s *Server) DeleteBmp(ctx context.Context, arg *api.DeleteBmpRequest) (*api.DeleteBmpResponse, error) { + return &api.DeleteBmpResponse{}, s.bgpServer.DeleteBmp(&config.BmpServerConfig{ + Address: arg.Address, + Port: arg.Port, + }) +} + +func (s *Server) ValidateRib(ctx context.Context, arg *api.ValidateRibRequest) (*api.ValidateRibResponse, error) { + return &api.ValidateRibResponse{}, nil +} + +func (s *Server) AddRpki(ctx context.Context, arg *api.AddRpkiRequest) (*api.AddRpkiResponse, error) { + return &api.AddRpkiResponse{}, s.bgpServer.AddRpki(&config.RpkiServerConfig{ + Address: arg.Address, + Port: arg.Port, + RecordLifetime: arg.Lifetime, + }) +} + +func (s *Server) DeleteRpki(ctx context.Context, arg *api.DeleteRpkiRequest) (*api.DeleteRpkiResponse, error) { + return &api.DeleteRpkiResponse{}, s.bgpServer.DeleteRpki(&config.RpkiServerConfig{ + Address: arg.Address, + Port: arg.Port, + }) +} + +func (s *Server) EnableRpki(ctx context.Context, arg *api.EnableRpkiRequest) (*api.EnableRpkiResponse, error) { + return &api.EnableRpkiResponse{}, s.bgpServer.EnableRpki(&config.RpkiServerConfig{ + Address: arg.Address, + }) +} + +func (s *Server) DisableRpki(ctx context.Context, arg *api.DisableRpkiRequest) (*api.DisableRpkiResponse, error) { + return &api.DisableRpkiResponse{}, s.bgpServer.DisableRpki(&config.RpkiServerConfig{ + Address: arg.Address, + }) +} + +func (s *Server) ResetRpki(ctx context.Context, arg *api.ResetRpkiRequest) (*api.ResetRpkiResponse, error) { + return &api.ResetRpkiResponse{}, s.bgpServer.ResetRpki(&config.RpkiServerConfig{ + Address: arg.Address, + }) +} + +func (s *Server) SoftResetRpki(ctx context.Context, arg *api.SoftResetRpkiRequest) (*api.SoftResetRpkiResponse, error) { + return &api.SoftResetRpkiResponse{}, s.bgpServer.SoftResetRpki(&config.RpkiServerConfig{ + Address: arg.Address, + }) +} + +func (s *Server) GetRpki(ctx context.Context, arg *api.GetRpkiRequest) (*api.GetRpkiResponse, error) { + servers, err := s.bgpServer.GetRpki() + if err != nil { + return nil, err + } + l := make([]*api.Rpki, 0, len(servers)) + for _, s := range servers { + received := &s.State.RpkiMessages.RpkiReceived + sent := &s.State.RpkiMessages.RpkiSent + rpki := &api.Rpki{ + Conf: &api.RPKIConf{ + Address: s.Config.Address, + RemotePort: strconv.Itoa(int(s.Config.Port)), + }, + State: &api.RPKIState{ + Uptime: s.State.Uptime, + Downtime: s.State.Downtime, + Up: s.State.Up, + RecordIpv4: s.State.RecordsV4, + RecordIpv6: s.State.RecordsV6, + PrefixIpv4: s.State.PrefixesV4, + PrefixIpv6: s.State.PrefixesV6, + Serial: s.State.SerialNumber, + ReceivedIpv4: received.Ipv4Prefix, + ReceivedIpv6: received.Ipv6Prefix, + SerialNotify: received.SerialNotify, + CacheReset: received.CacheReset, + CacheResponse: received.CacheResponse, + EndOfData: received.EndOfData, + Error: received.Error, + SerialQuery: sent.SerialQuery, + ResetQuery: sent.ResetQuery, + }, + } + l = append(l, rpki) + } + return &api.GetRpkiResponse{Servers: l}, nil +} + +func (s *Server) GetRoa(ctx context.Context, arg *api.GetRoaRequest) (*api.GetRoaResponse, error) { + roas, err := s.bgpServer.GetRoa(bgp.RouteFamily(arg.Family)) + if err != nil { + return nil, err + } + return &api.GetRoaResponse{Roas: NewRoaListFromTableStructList(roas)}, nil +} + +func (s *Server) EnableZebra(ctx context.Context, arg *api.EnableZebraRequest) (*api.EnableZebraResponse, error) { + for _, p := range arg.RouteTypes { + if _, err := zebra.RouteTypeFromString(p); err != nil { + return &api.EnableZebraResponse{}, err + } + } + return &api.EnableZebraResponse{}, s.bgpServer.StartZebraClient(&config.ZebraConfig{ + Url: arg.Url, + RedistributeRouteTypeList: arg.RouteTypes, + Version: uint8(arg.Version), + NexthopTriggerEnable: arg.NexthopTriggerEnable, + NexthopTriggerDelay: uint8(arg.NexthopTriggerDelay), + }) +} + +func (s *Server) GetVrf(ctx context.Context, arg *api.GetVrfRequest) (*api.GetVrfResponse, error) { + toApi := func(v *table.Vrf) *api.Vrf { + return &api.Vrf{ + Name: v.Name, + Rd: api.MarshalRD(v.Rd), + Id: v.Id, + ImportRt: api.MarshalRTs(v.ImportRt), + ExportRt: api.MarshalRTs(v.ExportRt), + } + } + vrfs := s.bgpServer.GetVrf() + l := make([]*api.Vrf, 0, len(vrfs)) + for _, v := range vrfs { + l = append(l, toApi(v)) + } + return &api.GetVrfResponse{Vrfs: l}, nil +} + +func (s *Server) AddVrf(ctx context.Context, arg *api.AddVrfRequest) (r *api.AddVrfResponse, err error) { + if arg == nil || arg.Vrf == nil { + return nil, fmt.Errorf("invalid request") + } + rd, err := api.UnmarshalRD(arg.Vrf.Rd) + if err != nil { + return nil, err + } + im, err := api.UnmarshalRTs(arg.Vrf.ImportRt) + if err != nil { + return nil, err + } + ex, err := api.UnmarshalRTs(arg.Vrf.ExportRt) + if err != nil { + return nil, err + } + return &api.AddVrfResponse{}, s.bgpServer.AddVrf(arg.Vrf.Name, arg.Vrf.Id, rd, im, ex) +} + +func (s *Server) DeleteVrf(ctx context.Context, arg *api.DeleteVrfRequest) (*api.DeleteVrfResponse, error) { + if arg == nil || arg.Vrf == nil { + return nil, fmt.Errorf("invalid request") + } + return &api.DeleteVrfResponse{}, s.bgpServer.DeleteVrf(arg.Vrf.Name) +} + +func ReadMpGracefulRestartFromAPIStruct(c *config.MpGracefulRestart, a *api.MpGracefulRestart) { + if c == nil || a == nil { + return + } + if a.Config != nil { + c.Config.Enabled = a.Config.Enabled + } +} + +func ReadAfiSafiConfigFromAPIStruct(c *config.AfiSafiConfig, a *api.AfiSafiConfig) { + if c == nil || a == nil { + return + } + c.AfiSafiName = config.AfiSafiType(bgp.RouteFamily(a.Family).String()) + c.Enabled = a.Enabled +} + +func ReadAfiSafiStateFromAPIStruct(s *config.AfiSafiState, a *api.AfiSafiConfig) { + if s == nil || a == nil { + return + } + // Store only address family value for the convenience + s.Family = bgp.RouteFamily(a.Family) +} + +func ReadPrefixLimitFromAPIStruct(c *config.PrefixLimit, a *api.PrefixLimit) { + if c == nil || a == nil { + return + } + c.Config.MaxPrefixes = a.MaxPrefixes + c.Config.ShutdownThresholdPct = config.Percentage(a.ShutdownThresholdPct) +} + +func ReadApplyPolicyFromAPIStruct(c *config.ApplyPolicy, a *api.ApplyPolicy) { + if c == nil || a == nil { + return + } + if a.ImportPolicy != nil { + c.Config.DefaultImportPolicy = config.IntToDefaultPolicyTypeMap[int(a.ImportPolicy.Default)] + for _, p := range a.ImportPolicy.Policies { + c.Config.ImportPolicyList = append(c.Config.ImportPolicyList, p.Name) + } + } + if a.ExportPolicy != nil { + c.Config.DefaultExportPolicy = config.IntToDefaultPolicyTypeMap[int(a.ExportPolicy.Default)] + for _, p := range a.ExportPolicy.Policies { + c.Config.ExportPolicyList = append(c.Config.ExportPolicyList, p.Name) + } + } + if a.InPolicy != nil { + c.Config.DefaultInPolicy = config.IntToDefaultPolicyTypeMap[int(a.InPolicy.Default)] + for _, p := range a.InPolicy.Policies { + c.Config.InPolicyList = append(c.Config.InPolicyList, p.Name) + } + } +} + +func ReadRouteSelectionOptionsFromAPIStruct(c *config.RouteSelectionOptions, a *api.RouteSelectionOptions) { + if c == nil || a == nil { + return + } + if a.Config != nil { + c.Config.AlwaysCompareMed = a.Config.AlwaysCompareMed + c.Config.IgnoreAsPathLength = a.Config.IgnoreAsPathLength + c.Config.ExternalCompareRouterId = a.Config.ExternalCompareRouterId + c.Config.AdvertiseInactiveRoutes = a.Config.AdvertiseInactiveRoutes + c.Config.EnableAigp = a.Config.EnableAigp + c.Config.IgnoreNextHopIgpMetric = a.Config.IgnoreNextHopIgpMetric + } +} + +func ReadUseMultiplePathsFromAPIStruct(c *config.UseMultiplePaths, a *api.UseMultiplePaths) { + if c == nil || a == nil { + return + } + if a.Config != nil { + c.Config.Enabled = a.Config.Enabled + } + if a.Ebgp != nil && a.Ebgp.Config != nil { + c.Ebgp = config.Ebgp{ + Config: config.EbgpConfig{ + AllowMultipleAs: a.Ebgp.Config.AllowMultipleAs, + MaximumPaths: a.Ebgp.Config.MaximumPaths, + }, + } + } + if a.Ibgp != nil && a.Ibgp.Config != nil { + c.Ibgp = config.Ibgp{ + Config: config.IbgpConfig{ + MaximumPaths: a.Ibgp.Config.MaximumPaths, + }, + } + } +} + +func ReadRouteTargetMembershipFromAPIStruct(c *config.RouteTargetMembership, a *api.RouteTargetMembership) { + if c == nil || a == nil { + return + } + if a.Config != nil { + c.Config.DeferralTime = uint16(a.Config.DeferralTime) + } +} + +func ReadLongLivedGracefulRestartFromAPIStruct(c *config.LongLivedGracefulRestart, a *api.LongLivedGracefulRestart) { + if c == nil || a == nil { + return + } + if a.Config != nil { + c.Config.Enabled = a.Config.Enabled + c.Config.RestartTime = a.Config.RestartTime + } +} + +func ReadAddPathsFromAPIStruct(c *config.AddPaths, a *api.AddPaths) { + if c == nil || a == nil { + return + } + if a.Config != nil { + c.Config.Receive = a.Config.Receive + c.Config.SendMax = uint8(a.Config.SendMax) + } +} + +func NewNeighborFromAPIStruct(a *api.Peer) (*config.Neighbor, error) { + pconf := &config.Neighbor{} + if a.Conf != nil { + pconf.Config.PeerAs = a.Conf.PeerAs + pconf.Config.LocalAs = a.Conf.LocalAs + pconf.Config.AuthPassword = a.Conf.AuthPassword + pconf.Config.RouteFlapDamping = a.Conf.RouteFlapDamping + pconf.Config.Description = a.Conf.Description + pconf.Config.PeerGroup = a.Conf.PeerGroup + pconf.Config.PeerType = config.IntToPeerTypeMap[int(a.Conf.PeerType)] + pconf.Config.NeighborAddress = a.Conf.NeighborAddress + pconf.Config.NeighborInterface = a.Conf.NeighborInterface + pconf.Config.Vrf = a.Conf.Vrf + pconf.AsPathOptions.Config.AllowOwnAs = uint8(a.Conf.AllowOwnAs) + pconf.AsPathOptions.Config.ReplacePeerAs = a.Conf.ReplacePeerAs + + switch a.Conf.RemovePrivateAs { + case api.PeerConf_ALL: + pconf.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_ALL + case api.PeerConf_REPLACE: + pconf.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_REPLACE + } + + localCaps, err := api.UnmarshalCapabilities(a.Conf.LocalCap) + if err != nil { + return nil, err + } + remoteCaps, err := api.UnmarshalCapabilities(a.Conf.RemoteCap) + if err != nil { + return nil, err + } + pconf.State.LocalCapabilityList = localCaps + pconf.State.RemoteCapabilityList = remoteCaps + + pconf.State.RemoteRouterId = a.Conf.Id + + for _, af := range a.AfiSafis { + afiSafi := config.AfiSafi{} + ReadMpGracefulRestartFromAPIStruct(&afiSafi.MpGracefulRestart, af.MpGracefulRestart) + ReadAfiSafiConfigFromAPIStruct(&afiSafi.Config, af.Config) + ReadAfiSafiStateFromAPIStruct(&afiSafi.State, af.Config) + ReadApplyPolicyFromAPIStruct(&afiSafi.ApplyPolicy, af.ApplyPolicy) + ReadRouteSelectionOptionsFromAPIStruct(&afiSafi.RouteSelectionOptions, af.RouteSelectionOptions) + ReadUseMultiplePathsFromAPIStruct(&afiSafi.UseMultiplePaths, af.UseMultiplePaths) + ReadPrefixLimitFromAPIStruct(&afiSafi.PrefixLimit, af.PrefixLimits) + ReadRouteTargetMembershipFromAPIStruct(&afiSafi.RouteTargetMembership, af.RouteTargetMembership) + ReadLongLivedGracefulRestartFromAPIStruct(&afiSafi.LongLivedGracefulRestart, af.LongLivedGracefulRestart) + ReadAddPathsFromAPIStruct(&afiSafi.AddPaths, af.AddPaths) + pconf.AfiSafis = append(pconf.AfiSafis, afiSafi) + } + // For the backward compatibility, we override AfiSafi configurations + // with Peer.Families. + for _, family := range a.Families { + found := false + for _, afiSafi := range pconf.AfiSafis { + if uint32(afiSafi.State.Family) == family { + // If Peer.Families contains the same address family, + // we enable this address family. + afiSafi.Config.Enabled = true + found = true + } + } + if !found { + // If Peer.Families does not contain the same address family, + // we append AfiSafi structure with the default value. + pconf.AfiSafis = append(pconf.AfiSafis, config.AfiSafi{ + Config: config.AfiSafiConfig{ + AfiSafiName: config.AfiSafiType(bgp.RouteFamily(family).String()), + Enabled: true, + }, + }) + } + } + // For the backward compatibility, we override AfiSafi configurations + // with Peer.Conf.PrefixLimits. + for _, prefixLimit := range a.Conf.PrefixLimits { + for _, afiSafi := range pconf.AfiSafis { + // If Peer.Conf.PrefixLimits contains the configuration for + // the same address family, we override AfiSafi.PrefixLimit. + if uint32(afiSafi.State.Family) == prefixLimit.Family { + ReadPrefixLimitFromAPIStruct(&afiSafi.PrefixLimit, prefixLimit) + } + } + } + } + + if a.Timers != nil { + if a.Timers.Config != nil { + pconf.Timers.Config.ConnectRetry = float64(a.Timers.Config.ConnectRetry) + pconf.Timers.Config.HoldTime = float64(a.Timers.Config.HoldTime) + pconf.Timers.Config.KeepaliveInterval = float64(a.Timers.Config.KeepaliveInterval) + pconf.Timers.Config.MinimumAdvertisementInterval = float64(a.Timers.Config.MinimumAdvertisementInterval) + } + if a.Timers.State != nil { + pconf.Timers.State.KeepaliveInterval = float64(a.Timers.State.KeepaliveInterval) + pconf.Timers.State.NegotiatedHoldTime = float64(a.Timers.State.NegotiatedHoldTime) + pconf.Timers.State.Uptime = int64(a.Timers.State.Uptime) + pconf.Timers.State.Downtime = int64(a.Timers.State.Downtime) + } + } + if a.RouteReflector != nil { + pconf.RouteReflector.Config.RouteReflectorClusterId = config.RrClusterIdType(a.RouteReflector.RouteReflectorClusterId) + pconf.RouteReflector.Config.RouteReflectorClient = a.RouteReflector.RouteReflectorClient + } + if a.RouteServer != nil { + pconf.RouteServer.Config.RouteServerClient = a.RouteServer.RouteServerClient + } + if a.GracefulRestart != nil { + pconf.GracefulRestart.Config.Enabled = a.GracefulRestart.Enabled + pconf.GracefulRestart.Config.RestartTime = uint16(a.GracefulRestart.RestartTime) + pconf.GracefulRestart.Config.HelperOnly = a.GracefulRestart.HelperOnly + pconf.GracefulRestart.Config.DeferralTime = uint16(a.GracefulRestart.DeferralTime) + pconf.GracefulRestart.Config.NotificationEnabled = a.GracefulRestart.NotificationEnabled + pconf.GracefulRestart.Config.LongLivedEnabled = a.GracefulRestart.LonglivedEnabled + pconf.GracefulRestart.State.LocalRestarting = a.GracefulRestart.LocalRestarting + } + ReadApplyPolicyFromAPIStruct(&pconf.ApplyPolicy, a.ApplyPolicy) + if a.Transport != nil { + pconf.Transport.Config.LocalAddress = a.Transport.LocalAddress + pconf.Transport.Config.PassiveMode = a.Transport.PassiveMode + pconf.Transport.Config.RemotePort = uint16(a.Transport.RemotePort) + } + if a.EbgpMultihop != nil { + pconf.EbgpMultihop.Config.Enabled = a.EbgpMultihop.Enabled + pconf.EbgpMultihop.Config.MultihopTtl = uint8(a.EbgpMultihop.MultihopTtl) + } + if a.Info != nil { + pconf.State.SessionState = config.SessionState(a.Info.BgpState) + pconf.State.AdminState = config.IntToAdminStateMap[int(a.Info.AdminState)] + + pconf.State.AdjTable.Received = a.Info.Received + pconf.State.AdjTable.Accepted = a.Info.Accepted + pconf.State.AdjTable.Advertised = a.Info.Advertised + pconf.State.PeerAs = a.Info.PeerAs + pconf.State.PeerType = config.IntToPeerTypeMap[int(a.Info.PeerType)] + pconf.State.NeighborAddress = a.Info.NeighborAddress + + if a.Info.Messages != nil { + if a.Info.Messages.Sent != nil { + pconf.State.Messages.Sent.Update = a.Info.Messages.Sent.UPDATE + pconf.State.Messages.Sent.Notification = a.Info.Messages.Sent.NOTIFICATION + pconf.State.Messages.Sent.Open = a.Info.Messages.Sent.OPEN + pconf.State.Messages.Sent.Refresh = a.Info.Messages.Sent.REFRESH + pconf.State.Messages.Sent.Keepalive = a.Info.Messages.Sent.KEEPALIVE + pconf.State.Messages.Sent.Discarded = a.Info.Messages.Sent.DISCARDED + pconf.State.Messages.Sent.Total = a.Info.Messages.Sent.TOTAL + } + if a.Info.Messages.Received != nil { + pconf.State.Messages.Received.Update = a.Info.Messages.Received.UPDATE + pconf.State.Messages.Received.Open = a.Info.Messages.Received.OPEN + pconf.State.Messages.Received.Refresh = a.Info.Messages.Received.REFRESH + pconf.State.Messages.Received.Keepalive = a.Info.Messages.Received.KEEPALIVE + pconf.State.Messages.Received.Discarded = a.Info.Messages.Received.DISCARDED + pconf.State.Messages.Received.Total = a.Info.Messages.Received.TOTAL + } + } + } + ReadAddPathsFromAPIStruct(&pconf.AddPaths, a.AddPaths) + return pconf, nil +} + +func NewPeerGroupFromAPIStruct(a *api.PeerGroup) (*config.PeerGroup, error) { + pconf := &config.PeerGroup{} + if a.Conf != nil { + pconf.Config.PeerAs = a.Conf.PeerAs + pconf.Config.LocalAs = a.Conf.LocalAs + pconf.Config.AuthPassword = a.Conf.AuthPassword + pconf.Config.RouteFlapDamping = a.Conf.RouteFlapDamping + pconf.Config.Description = a.Conf.Description + pconf.Config.PeerGroupName = a.Conf.PeerGroupName + + switch a.Conf.RemovePrivateAs { + case api.PeerGroupConf_ALL: + pconf.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_ALL + case api.PeerGroupConf_REPLACE: + pconf.Config.RemovePrivateAs = config.REMOVE_PRIVATE_AS_OPTION_REPLACE + } + + for _, af := range a.AfiSafis { + afiSafi := config.AfiSafi{} + ReadMpGracefulRestartFromAPIStruct(&afiSafi.MpGracefulRestart, af.MpGracefulRestart) + ReadAfiSafiConfigFromAPIStruct(&afiSafi.Config, af.Config) + ReadAfiSafiStateFromAPIStruct(&afiSafi.State, af.Config) + ReadApplyPolicyFromAPIStruct(&afiSafi.ApplyPolicy, af.ApplyPolicy) + ReadRouteSelectionOptionsFromAPIStruct(&afiSafi.RouteSelectionOptions, af.RouteSelectionOptions) + ReadUseMultiplePathsFromAPIStruct(&afiSafi.UseMultiplePaths, af.UseMultiplePaths) + ReadPrefixLimitFromAPIStruct(&afiSafi.PrefixLimit, af.PrefixLimits) + ReadRouteTargetMembershipFromAPIStruct(&afiSafi.RouteTargetMembership, af.RouteTargetMembership) + ReadLongLivedGracefulRestartFromAPIStruct(&afiSafi.LongLivedGracefulRestart, af.LongLivedGracefulRestart) + ReadAddPathsFromAPIStruct(&afiSafi.AddPaths, af.AddPaths) + pconf.AfiSafis = append(pconf.AfiSafis, afiSafi) + } + // For the backward compatibility, we override AfiSafi configurations + // with Peer.Families. + for _, family := range a.Families { + found := false + for _, afiSafi := range pconf.AfiSafis { + if uint32(afiSafi.State.Family) == family { + // If Peer.Families contains the same address family, + // we enable this address family. + afiSafi.Config.Enabled = true + found = true + } + } + if !found { + // If Peer.Families does not contain the same address family, + // we append AfiSafi structure with the default value. + pconf.AfiSafis = append(pconf.AfiSafis, config.AfiSafi{ + Config: config.AfiSafiConfig{ + AfiSafiName: config.AfiSafiType(bgp.RouteFamily(family).String()), + Enabled: true, + }, + }) + } + } + } + + if a.Timers != nil { + if a.Timers.Config != nil { + pconf.Timers.Config.ConnectRetry = float64(a.Timers.Config.ConnectRetry) + pconf.Timers.Config.HoldTime = float64(a.Timers.Config.HoldTime) + pconf.Timers.Config.KeepaliveInterval = float64(a.Timers.Config.KeepaliveInterval) + pconf.Timers.Config.MinimumAdvertisementInterval = float64(a.Timers.Config.MinimumAdvertisementInterval) + } + if a.Timers.State != nil { + pconf.Timers.State.KeepaliveInterval = float64(a.Timers.State.KeepaliveInterval) + pconf.Timers.State.NegotiatedHoldTime = float64(a.Timers.State.NegotiatedHoldTime) + pconf.Timers.State.Uptime = int64(a.Timers.State.Uptime) + pconf.Timers.State.Downtime = int64(a.Timers.State.Downtime) + } + } + if a.RouteReflector != nil { + pconf.RouteReflector.Config.RouteReflectorClusterId = config.RrClusterIdType(a.RouteReflector.RouteReflectorClusterId) + pconf.RouteReflector.Config.RouteReflectorClient = a.RouteReflector.RouteReflectorClient + } + if a.RouteServer != nil { + pconf.RouteServer.Config.RouteServerClient = a.RouteServer.RouteServerClient + } + if a.GracefulRestart != nil { + pconf.GracefulRestart.Config.Enabled = a.GracefulRestart.Enabled + pconf.GracefulRestart.Config.RestartTime = uint16(a.GracefulRestart.RestartTime) + pconf.GracefulRestart.Config.HelperOnly = a.GracefulRestart.HelperOnly + pconf.GracefulRestart.Config.DeferralTime = uint16(a.GracefulRestart.DeferralTime) + pconf.GracefulRestart.Config.NotificationEnabled = a.GracefulRestart.NotificationEnabled + pconf.GracefulRestart.Config.LongLivedEnabled = a.GracefulRestart.LonglivedEnabled + pconf.GracefulRestart.State.LocalRestarting = a.GracefulRestart.LocalRestarting + } + ReadApplyPolicyFromAPIStruct(&pconf.ApplyPolicy, a.ApplyPolicy) + if a.Transport != nil { + pconf.Transport.Config.LocalAddress = a.Transport.LocalAddress + pconf.Transport.Config.PassiveMode = a.Transport.PassiveMode + pconf.Transport.Config.RemotePort = uint16(a.Transport.RemotePort) + } + if a.EbgpMultihop != nil { + pconf.EbgpMultihop.Config.Enabled = a.EbgpMultihop.Enabled + pconf.EbgpMultihop.Config.MultihopTtl = uint8(a.EbgpMultihop.MultihopTtl) + } + if a.Info != nil { + pconf.State.TotalPaths = a.Info.TotalPaths + pconf.State.TotalPrefixes = a.Info.TotalPrefixes + pconf.State.PeerAs = a.Info.PeerAs + pconf.State.PeerType = config.IntToPeerTypeMap[int(a.Info.PeerType)] + } + ReadAddPathsFromAPIStruct(&pconf.AddPaths, a.AddPaths) + return pconf, nil +} + +func (s *Server) AddNeighbor(ctx context.Context, arg *api.AddNeighborRequest) (*api.AddNeighborResponse, error) { + c, err := NewNeighborFromAPIStruct(arg.Peer) + if err != nil { + return nil, err + } + return &api.AddNeighborResponse{}, s.bgpServer.AddNeighbor(c) +} + +func (s *Server) DeleteNeighbor(ctx context.Context, arg *api.DeleteNeighborRequest) (*api.DeleteNeighborResponse, error) { + return &api.DeleteNeighborResponse{}, s.bgpServer.DeleteNeighbor(&config.Neighbor{Config: config.NeighborConfig{ + NeighborAddress: arg.Peer.Conf.NeighborAddress, + NeighborInterface: arg.Peer.Conf.NeighborInterface, + }}) +} + +func (s *Server) UpdateNeighbor(ctx context.Context, arg *api.UpdateNeighborRequest) (*api.UpdateNeighborResponse, error) { + c, err := NewNeighborFromAPIStruct(arg.Peer) + if err != nil { + return nil, err + } + needsSoftResetIn, err := s.bgpServer.UpdateNeighbor(c) + if err != nil { + return nil, err + } + if arg.DoSoftResetIn && needsSoftResetIn { + return &api.UpdateNeighborResponse{NeedsSoftResetIn: false}, s.bgpServer.SoftResetIn("", bgp.RouteFamily(0)) + } + return &api.UpdateNeighborResponse{NeedsSoftResetIn: needsSoftResetIn}, nil +} + +func (s *Server) AddPeerGroup(ctx context.Context, arg *api.AddPeerGroupRequest) (*api.AddPeerGroupResponse, error) { + c, err := NewPeerGroupFromAPIStruct(arg.PeerGroup) + if err != nil { + return nil, err + } + return &api.AddPeerGroupResponse{}, s.bgpServer.AddPeerGroup(c) +} + +func (s *Server) DeletePeerGroup(ctx context.Context, arg *api.DeletePeerGroupRequest) (*api.DeletePeerGroupResponse, error) { + return &api.DeletePeerGroupResponse{}, s.bgpServer.DeletePeerGroup(&config.PeerGroup{Config: config.PeerGroupConfig{ + PeerGroupName: arg.PeerGroup.Conf.PeerGroupName, + }}) +} + +func (s *Server) UpdatePeerGroup(ctx context.Context, arg *api.UpdatePeerGroupRequest) (*api.UpdatePeerGroupResponse, error) { + c, err := NewPeerGroupFromAPIStruct(arg.PeerGroup) + if err != nil { + return nil, err + } + needsSoftResetIn, err := s.bgpServer.UpdatePeerGroup(c) + if err != nil { + return nil, err + } + if arg.DoSoftResetIn && needsSoftResetIn { + return &api.UpdatePeerGroupResponse{NeedsSoftResetIn: false}, s.bgpServer.SoftResetIn("", bgp.RouteFamily(0)) + } + return &api.UpdatePeerGroupResponse{NeedsSoftResetIn: needsSoftResetIn}, nil +} + +func (s *Server) AddDynamicNeighbor(ctx context.Context, arg *api.AddDynamicNeighborRequest) (*api.AddDynamicNeighborResponse, error) { + return &api.AddDynamicNeighborResponse{}, s.bgpServer.AddDynamicNeighbor(&config.DynamicNeighbor{Config: config.DynamicNeighborConfig{ + Prefix: arg.DynamicNeighbor.Prefix, + PeerGroup: arg.DynamicNeighbor.PeerGroup, + }}) +} + +func NewPrefixFromApiStruct(a *api.Prefix) (*table.Prefix, error) { + _, prefix, err := net.ParseCIDR(a.IpPrefix) + if err != nil { + return nil, err + } + rf := bgp.RF_IPv4_UC + if strings.Contains(a.IpPrefix, ":") { + rf = bgp.RF_IPv6_UC + } + return &table.Prefix{ + Prefix: prefix, + AddressFamily: rf, + MasklengthRangeMin: uint8(a.MaskLengthMin), + MasklengthRangeMax: uint8(a.MaskLengthMax), + }, nil +} + +func NewConfigPrefixFromAPIStruct(a *api.Prefix) (*config.Prefix, error) { + _, prefix, err := net.ParseCIDR(a.IpPrefix) + if err != nil { + return nil, err + } + return &config.Prefix{ + IpPrefix: prefix.String(), + MasklengthRange: fmt.Sprintf("%d..%d", a.MaskLengthMin, a.MaskLengthMax), + }, nil +} + +func NewAPIPrefixFromConfigStruct(c config.Prefix) (*api.Prefix, error) { + min, max, err := config.ParseMaskLength(c.IpPrefix, c.MasklengthRange) + if err != nil { + return nil, err + } + return &api.Prefix{ + IpPrefix: c.IpPrefix, + MaskLengthMin: uint32(min), + MaskLengthMax: uint32(max), + }, nil +} + +func NewAPIDefinedSetFromTableStruct(t table.DefinedSet) (*api.DefinedSet, error) { + a := &api.DefinedSet{ + Type: api.DefinedType(t.Type()), + Name: t.Name(), + } + switch t.Type() { + case table.DEFINED_TYPE_PREFIX: + s := t.(*table.PrefixSet) + c := s.ToConfig() + for _, p := range c.PrefixList { + ap, err := NewAPIPrefixFromConfigStruct(p) + if err != nil { + return nil, err + } + a.Prefixes = append(a.Prefixes, ap) + } + case table.DEFINED_TYPE_NEIGHBOR: + s := t.(*table.NeighborSet) + c := s.ToConfig() + a.List = append(a.List, c.NeighborInfoList...) + case table.DEFINED_TYPE_AS_PATH: + s := t.(*table.AsPathSet) + c := s.ToConfig() + a.List = append(a.List, c.AsPathList...) + case table.DEFINED_TYPE_COMMUNITY: + s := t.(*table.CommunitySet) + c := s.ToConfig() + a.List = append(a.List, c.CommunityList...) + case table.DEFINED_TYPE_EXT_COMMUNITY: + s := t.(*table.ExtCommunitySet) + c := s.ToConfig() + a.List = append(a.List, c.ExtCommunityList...) + case table.DEFINED_TYPE_LARGE_COMMUNITY: + s := t.(*table.LargeCommunitySet) + c := s.ToConfig() + a.List = append(a.List, c.LargeCommunityList...) + default: + return nil, fmt.Errorf("invalid defined type") + } + return a, nil +} + +func NewAPIDefinedSetsFromConfigStruct(t *config.DefinedSets) ([]*api.DefinedSet, error) { + definedSets := make([]*api.DefinedSet, 0) + + for _, ps := range t.PrefixSets { + prefixes := make([]*api.Prefix, 0) + for _, p := range ps.PrefixList { + ap, err := NewAPIPrefixFromConfigStruct(p) + if err != nil { + return nil, err + } + prefixes = append(prefixes, ap) + } + definedSets = append(definedSets, &api.DefinedSet{ + Type: api.DefinedType_PREFIX, + Name: ps.PrefixSetName, + Prefixes: prefixes, + }) + } + + for _, ns := range t.NeighborSets { + definedSets = append(definedSets, &api.DefinedSet{ + Type: api.DefinedType_NEIGHBOR, + Name: ns.NeighborSetName, + List: ns.NeighborInfoList, + }) + } + + bs := t.BgpDefinedSets + for _, cs := range bs.CommunitySets { + definedSets = append(definedSets, &api.DefinedSet{ + Type: api.DefinedType_COMMUNITY, + Name: cs.CommunitySetName, + List: cs.CommunityList, + }) + } + + for _, es := range bs.ExtCommunitySets { + definedSets = append(definedSets, &api.DefinedSet{ + Type: api.DefinedType_EXT_COMMUNITY, + Name: es.ExtCommunitySetName, + List: es.ExtCommunityList, + }) + } + + for _, ls := range bs.LargeCommunitySets { + definedSets = append(definedSets, &api.DefinedSet{ + Type: api.DefinedType_LARGE_COMMUNITY, + Name: ls.LargeCommunitySetName, + List: ls.LargeCommunityList, + }) + } + + for _, as := range bs.AsPathSets { + definedSets = append(definedSets, &api.DefinedSet{ + Type: api.DefinedType_AS_PATH, + Name: as.AsPathSetName, + List: as.AsPathList, + }) + } + + return definedSets, nil +} + +func NewConfigDefinedSetsFromApiStruct(a []*api.DefinedSet) (*config.DefinedSets, error) { + ps := make([]config.PrefixSet, 0) + ns := make([]config.NeighborSet, 0) + as := make([]config.AsPathSet, 0) + cs := make([]config.CommunitySet, 0) + es := make([]config.ExtCommunitySet, 0) + ls := make([]config.LargeCommunitySet, 0) + + for _, ds := range a { + if ds.Name == "" { + return nil, fmt.Errorf("empty neighbor set name") + } + switch table.DefinedType(ds.Type) { + case table.DEFINED_TYPE_PREFIX: + prefixes := make([]config.Prefix, 0, len(ds.Prefixes)) + for _, p := range ds.Prefixes { + prefix, err := NewConfigPrefixFromAPIStruct(p) + if err != nil { + return nil, err + } + prefixes = append(prefixes, *prefix) + } + ps = append(ps, config.PrefixSet{ + PrefixSetName: ds.Name, + PrefixList: prefixes, + }) + case table.DEFINED_TYPE_NEIGHBOR: + ns = append(ns, config.NeighborSet{ + NeighborSetName: ds.Name, + NeighborInfoList: ds.List, + }) + case table.DEFINED_TYPE_AS_PATH: + as = append(as, config.AsPathSet{ + AsPathSetName: ds.Name, + AsPathList: ds.List, + }) + case table.DEFINED_TYPE_COMMUNITY: + cs = append(cs, config.CommunitySet{ + CommunitySetName: ds.Name, + CommunityList: ds.List, + }) + case table.DEFINED_TYPE_EXT_COMMUNITY: + es = append(es, config.ExtCommunitySet{ + ExtCommunitySetName: ds.Name, + ExtCommunityList: ds.List, + }) + case table.DEFINED_TYPE_LARGE_COMMUNITY: + ls = append(ls, config.LargeCommunitySet{ + LargeCommunitySetName: ds.Name, + LargeCommunityList: ds.List, + }) + default: + return nil, fmt.Errorf("invalid defined type") + } + } + + return &config.DefinedSets{ + PrefixSets: ps, + NeighborSets: ns, + BgpDefinedSets: config.BgpDefinedSets{ + AsPathSets: as, + CommunitySets: cs, + ExtCommunitySets: es, + LargeCommunitySets: ls, + }, + }, nil +} + +func NewDefinedSetFromApiStruct(a *api.DefinedSet) (table.DefinedSet, error) { + if a.Name == "" { + return nil, fmt.Errorf("empty neighbor set name") + } + switch table.DefinedType(a.Type) { + case table.DEFINED_TYPE_PREFIX: + prefixes := make([]*table.Prefix, 0, len(a.Prefixes)) + for _, p := range a.Prefixes { + prefix, err := NewPrefixFromApiStruct(p) + if err != nil { + return nil, err + } + prefixes = append(prefixes, prefix) + } + return table.NewPrefixSetFromApiStruct(a.Name, prefixes) + case table.DEFINED_TYPE_NEIGHBOR: + list := make([]net.IPNet, 0, len(a.List)) + for _, x := range a.List { + _, addr, err := net.ParseCIDR(x) + if err != nil { + return nil, fmt.Errorf("invalid address or prefix: %s", x) + } + list = append(list, *addr) + } + return table.NewNeighborSetFromApiStruct(a.Name, list) + case table.DEFINED_TYPE_AS_PATH: + return table.NewAsPathSet(config.AsPathSet{ + AsPathSetName: a.Name, + AsPathList: a.List, + }) + case table.DEFINED_TYPE_COMMUNITY: + return table.NewCommunitySet(config.CommunitySet{ + CommunitySetName: a.Name, + CommunityList: a.List, + }) + case table.DEFINED_TYPE_EXT_COMMUNITY: + return table.NewExtCommunitySet(config.ExtCommunitySet{ + ExtCommunitySetName: a.Name, + ExtCommunityList: a.List, + }) + case table.DEFINED_TYPE_LARGE_COMMUNITY: + return table.NewLargeCommunitySet(config.LargeCommunitySet{ + LargeCommunitySetName: a.Name, + LargeCommunityList: a.List, + }) + default: + return nil, fmt.Errorf("invalid defined type") + } +} + +var _regexpPrefixMaskLengthRange = regexp.MustCompile(`(\d+)\.\.(\d+)`) + +func (s *Server) GetDefinedSet(ctx context.Context, arg *api.GetDefinedSetRequest) (*api.GetDefinedSetResponse, error) { + cd, err := s.bgpServer.GetDefinedSet(table.DefinedType(arg.Type), arg.Name) + if err != nil { + return nil, err + } + sets := make([]*api.DefinedSet, 0) + for _, cs := range cd.PrefixSets { + ad := &api.DefinedSet{ + Type: api.DefinedType_PREFIX, + Name: cs.PrefixSetName, + Prefixes: func() []*api.Prefix { + l := make([]*api.Prefix, 0, len(cs.PrefixList)) + for _, p := range cs.PrefixList { + elems := _regexpPrefixMaskLengthRange.FindStringSubmatch(p.MasklengthRange) + min, _ := strconv.ParseUint(elems[1], 10, 32) + max, _ := strconv.ParseUint(elems[2], 10, 32) + + l = append(l, &api.Prefix{IpPrefix: p.IpPrefix, MaskLengthMin: uint32(min), MaskLengthMax: uint32(max)}) + } + return l + }(), + } + sets = append(sets, ad) + + } + for _, cs := range cd.NeighborSets { + ad := &api.DefinedSet{ + Type: api.DefinedType_NEIGHBOR, + Name: cs.NeighborSetName, + List: cs.NeighborInfoList, + } + sets = append(sets, ad) + } + for _, cs := range cd.BgpDefinedSets.CommunitySets { + ad := &api.DefinedSet{ + Type: api.DefinedType_COMMUNITY, + Name: cs.CommunitySetName, + List: cs.CommunityList, + } + sets = append(sets, ad) + } + for _, cs := range cd.BgpDefinedSets.ExtCommunitySets { + ad := &api.DefinedSet{ + Type: api.DefinedType_EXT_COMMUNITY, + Name: cs.ExtCommunitySetName, + List: cs.ExtCommunityList, + } + sets = append(sets, ad) + } + for _, cs := range cd.BgpDefinedSets.LargeCommunitySets { + ad := &api.DefinedSet{ + Type: api.DefinedType_LARGE_COMMUNITY, + Name: cs.LargeCommunitySetName, + List: cs.LargeCommunityList, + } + sets = append(sets, ad) + } + for _, cs := range cd.BgpDefinedSets.AsPathSets { + ad := &api.DefinedSet{ + Type: api.DefinedType_AS_PATH, + Name: cs.AsPathSetName, + List: cs.AsPathList, + } + sets = append(sets, ad) + } + + return &api.GetDefinedSetResponse{Sets: sets}, nil +} + +func (s *Server) AddDefinedSet(ctx context.Context, arg *api.AddDefinedSetRequest) (*api.AddDefinedSetResponse, error) { + if arg == nil || arg.Set == nil { + return nil, fmt.Errorf("invalid request") + } + set, err := NewDefinedSetFromApiStruct(arg.Set) + if err != nil { + return nil, err + } + return &api.AddDefinedSetResponse{}, s.bgpServer.AddDefinedSet(set) +} + +func (s *Server) DeleteDefinedSet(ctx context.Context, arg *api.DeleteDefinedSetRequest) (*api.DeleteDefinedSetResponse, error) { + if arg == nil || arg.Set == nil { + return nil, fmt.Errorf("invalid request") + } + set, err := NewDefinedSetFromApiStruct(arg.Set) + if err != nil { + return nil, err + } + return &api.DeleteDefinedSetResponse{}, s.bgpServer.DeleteDefinedSet(set, arg.All) +} + +func (s *Server) ReplaceDefinedSet(ctx context.Context, arg *api.ReplaceDefinedSetRequest) (*api.ReplaceDefinedSetResponse, error) { + if arg == nil || arg.Set == nil { + return nil, fmt.Errorf("invalid request") + } + set, err := NewDefinedSetFromApiStruct(arg.Set) + if err != nil { + return nil, err + } + return &api.ReplaceDefinedSetResponse{}, s.bgpServer.ReplaceDefinedSet(set) +} + +func NewAPIStatementFromTableStruct(t *table.Statement) *api.Statement { + return toStatementApi(t.ToConfig()) +} + +var _regexpMedActionType = regexp.MustCompile(`([+-]?)(\d+)`) + +func toStatementApi(s *config.Statement) *api.Statement { + cs := &api.Conditions{} + if s.Conditions.MatchPrefixSet.PrefixSet != "" { + o, _ := table.NewMatchOption(s.Conditions.MatchPrefixSet.MatchSetOptions) + cs.PrefixSet = &api.MatchSet{ + Type: api.MatchType(o), + Name: s.Conditions.MatchPrefixSet.PrefixSet, + } + } + if s.Conditions.MatchNeighborSet.NeighborSet != "" { + o, _ := table.NewMatchOption(s.Conditions.MatchNeighborSet.MatchSetOptions) + cs.NeighborSet = &api.MatchSet{ + Type: api.MatchType(o), + Name: s.Conditions.MatchNeighborSet.NeighborSet, + } + } + if s.Conditions.BgpConditions.AsPathLength.Operator != "" { + cs.AsPathLength = &api.AsPathLength{ + Length: s.Conditions.BgpConditions.AsPathLength.Value, + Type: api.AsPathLengthType(s.Conditions.BgpConditions.AsPathLength.Operator.ToInt()), + } + } + if s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet != "" { + cs.AsPathSet = &api.MatchSet{ + Type: api.MatchType(s.Conditions.BgpConditions.MatchAsPathSet.MatchSetOptions.ToInt()), + Name: s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet, + } + } + if s.Conditions.BgpConditions.MatchCommunitySet.CommunitySet != "" { + cs.CommunitySet = &api.MatchSet{ + Type: api.MatchType(s.Conditions.BgpConditions.MatchCommunitySet.MatchSetOptions.ToInt()), + Name: s.Conditions.BgpConditions.MatchCommunitySet.CommunitySet, + } + } + if s.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet != "" { + cs.ExtCommunitySet = &api.MatchSet{ + Type: api.MatchType(s.Conditions.BgpConditions.MatchExtCommunitySet.MatchSetOptions.ToInt()), + Name: s.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet, + } + } + if s.Conditions.BgpConditions.MatchLargeCommunitySet.LargeCommunitySet != "" { + cs.LargeCommunitySet = &api.MatchSet{ + Type: api.MatchType(s.Conditions.BgpConditions.MatchLargeCommunitySet.MatchSetOptions.ToInt()), + Name: s.Conditions.BgpConditions.MatchLargeCommunitySet.LargeCommunitySet, + } + } + if s.Conditions.BgpConditions.RouteType != "" { + cs.RouteType = api.Conditions_RouteType(s.Conditions.BgpConditions.RouteType.ToInt()) + } + if len(s.Conditions.BgpConditions.NextHopInList) > 0 { + cs.NextHopInList = s.Conditions.BgpConditions.NextHopInList + } + if s.Conditions.BgpConditions.AfiSafiInList != nil { + afiSafiIn := make([]api.Family, 0) + for _, afiSafiType := range s.Conditions.BgpConditions.AfiSafiInList { + if mapped, ok := bgp.AddressFamilyValueMap[string(afiSafiType)]; ok { + afiSafiIn = append(afiSafiIn, api.Family(mapped)) + } + } + cs.AfiSafiIn = afiSafiIn + } + cs.RpkiResult = int32(s.Conditions.BgpConditions.RpkiValidationResult.ToInt()) + as := &api.Actions{ + RouteAction: func() api.RouteAction { + switch s.Actions.RouteDisposition { + case config.ROUTE_DISPOSITION_ACCEPT_ROUTE: + return api.RouteAction_ACCEPT + case config.ROUTE_DISPOSITION_REJECT_ROUTE: + return api.RouteAction_REJECT + } + return api.RouteAction_NONE + }(), + Community: func() *api.CommunityAction { + if len(s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList) == 0 { + return nil + } + return &api.CommunityAction{ + Type: api.CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetCommunity.Options)]), + Communities: s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList} + }(), + Med: func() *api.MedAction { + medStr := strings.TrimSpace(string(s.Actions.BgpActions.SetMed)) + if len(medStr) == 0 { + return nil + } + matches := _regexpMedActionType.FindStringSubmatch(medStr) + if len(matches) == 0 { + return nil + } + action := api.MedActionType_MED_REPLACE + switch matches[1] { + case "+", "-": + action = api.MedActionType_MED_MOD + } + value, err := strconv.ParseInt(matches[1]+matches[2], 10, 64) + if err != nil { + return nil + } + return &api.MedAction{ + Value: value, + Type: action, + } + }(), + AsPrepend: func() *api.AsPrependAction { + if len(s.Actions.BgpActions.SetAsPathPrepend.As) == 0 { + return nil + } + var asn uint64 + useleft := false + if s.Actions.BgpActions.SetAsPathPrepend.As != "last-as" { + asn, _ = strconv.ParseUint(s.Actions.BgpActions.SetAsPathPrepend.As, 10, 32) + } else { + useleft = true + } + return &api.AsPrependAction{ + Asn: uint32(asn), + Repeat: uint32(s.Actions.BgpActions.SetAsPathPrepend.RepeatN), + UseLeftMost: useleft, + } + }(), + ExtCommunity: func() *api.CommunityAction { + if len(s.Actions.BgpActions.SetExtCommunity.SetExtCommunityMethod.CommunitiesList) == 0 { + return nil + } + return &api.CommunityAction{ + Type: api.CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetExtCommunity.Options)]), + Communities: s.Actions.BgpActions.SetExtCommunity.SetExtCommunityMethod.CommunitiesList, + } + }(), + LargeCommunity: func() *api.CommunityAction { + if len(s.Actions.BgpActions.SetLargeCommunity.SetLargeCommunityMethod.CommunitiesList) == 0 { + return nil + } + return &api.CommunityAction{ + Type: api.CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetLargeCommunity.Options)]), + Communities: s.Actions.BgpActions.SetLargeCommunity.SetLargeCommunityMethod.CommunitiesList, + } + }(), + Nexthop: func() *api.NexthopAction { + if len(string(s.Actions.BgpActions.SetNextHop)) == 0 { + return nil + } + + if string(s.Actions.BgpActions.SetNextHop) == "self" { + return &api.NexthopAction{ + Self: true, + } + } + return &api.NexthopAction{ + Address: string(s.Actions.BgpActions.SetNextHop), + } + }(), + LocalPref: func() *api.LocalPrefAction { + if s.Actions.BgpActions.SetLocalPref == 0 { + return nil + } + return &api.LocalPrefAction{Value: s.Actions.BgpActions.SetLocalPref} + }(), + } + return &api.Statement{ + Name: s.Name, + Conditions: cs, + Actions: as, + } +} + +func toConfigMatchSetOption(a api.MatchType) (config.MatchSetOptionsType, error) { + var typ config.MatchSetOptionsType + switch a { + case api.MatchType_ANY: + typ = config.MATCH_SET_OPTIONS_TYPE_ANY + case api.MatchType_ALL: + typ = config.MATCH_SET_OPTIONS_TYPE_ALL + case api.MatchType_INVERT: + typ = config.MATCH_SET_OPTIONS_TYPE_INVERT + default: + return typ, fmt.Errorf("invalid match type") + } + return typ, nil +} + +func toConfigMatchSetOptionRestricted(a api.MatchType) (config.MatchSetOptionsRestrictedType, error) { + var typ config.MatchSetOptionsRestrictedType + switch a { + case api.MatchType_ANY: + typ = config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY + case api.MatchType_INVERT: + typ = config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT + default: + return typ, fmt.Errorf("invalid match type") + } + return typ, nil +} + +func NewPrefixConditionFromApiStruct(a *api.MatchSet) (*table.PrefixCondition, error) { + if a == nil { + return nil, nil + } + typ, err := toConfigMatchSetOptionRestricted(a.Type) + if err != nil { + return nil, err + } + c := config.MatchPrefixSet{ + PrefixSet: a.Name, + MatchSetOptions: typ, + } + return table.NewPrefixCondition(c) +} + +func NewNeighborConditionFromApiStruct(a *api.MatchSet) (*table.NeighborCondition, error) { + if a == nil { + return nil, nil + } + typ, err := toConfigMatchSetOptionRestricted(a.Type) + if err != nil { + return nil, err + } + c := config.MatchNeighborSet{ + NeighborSet: a.Name, + MatchSetOptions: typ, + } + return table.NewNeighborCondition(c) +} + +func NewAsPathLengthConditionFromApiStruct(a *api.AsPathLength) (*table.AsPathLengthCondition, error) { + if a == nil { + return nil, nil + } + return table.NewAsPathLengthCondition(config.AsPathLength{ + Operator: config.IntToAttributeComparisonMap[int(a.Type)], + Value: a.Length, + }) +} + +func NewAsPathConditionFromApiStruct(a *api.MatchSet) (*table.AsPathCondition, error) { + if a == nil { + return nil, nil + } + typ, err := toConfigMatchSetOption(a.Type) + if err != nil { + return nil, err + } + c := config.MatchAsPathSet{ + AsPathSet: a.Name, + MatchSetOptions: typ, + } + return table.NewAsPathCondition(c) +} + +func NewRpkiValidationConditionFromApiStruct(a int32) (*table.RpkiValidationCondition, error) { + if a < 1 { + return nil, nil + } + return table.NewRpkiValidationCondition(config.IntToRpkiValidationResultTypeMap[int(a)]) +} + +func NewRouteTypeConditionFromApiStruct(a api.Conditions_RouteType) (*table.RouteTypeCondition, error) { + if a == 0 { + return nil, nil + } + typ, ok := config.IntToRouteTypeMap[int(a)] + if !ok { + return nil, fmt.Errorf("invalid route type: %d", a) + } + return table.NewRouteTypeCondition(typ) +} + +func NewCommunityConditionFromApiStruct(a *api.MatchSet) (*table.CommunityCondition, error) { + if a == nil { + return nil, nil + } + typ, err := toConfigMatchSetOption(a.Type) + if err != nil { + return nil, err + } + c := config.MatchCommunitySet{ + CommunitySet: a.Name, + MatchSetOptions: typ, + } + return table.NewCommunityCondition(c) +} + +func NewExtCommunityConditionFromApiStruct(a *api.MatchSet) (*table.ExtCommunityCondition, error) { + if a == nil { + return nil, nil + } + typ, err := toConfigMatchSetOption(a.Type) + if err != nil { + return nil, err + } + c := config.MatchExtCommunitySet{ + ExtCommunitySet: a.Name, + MatchSetOptions: typ, + } + return table.NewExtCommunityCondition(c) +} + +func NewLargeCommunityConditionFromApiStruct(a *api.MatchSet) (*table.LargeCommunityCondition, error) { + if a == nil { + return nil, nil + } + typ, err := toConfigMatchSetOption(a.Type) + if err != nil { + return nil, err + } + c := config.MatchLargeCommunitySet{ + LargeCommunitySet: a.Name, + MatchSetOptions: typ, + } + return table.NewLargeCommunityCondition(c) +} + +func NewNextHopConditionFromApiStruct(a []string) (*table.NextHopCondition, error) { + if a == nil { + return nil, nil + } + + return table.NewNextHopCondition(a) +} + +func NewAfiSafiInConditionFromApiStruct(a []api.Family) (*table.AfiSafiInCondition, error) { + if a == nil { + return nil, nil + } + afiSafiTypes := make([]config.AfiSafiType, 0, len(a)) + for _, aType := range a { + if configType, ok := bgp.AddressFamilyNameMap[bgp.RouteFamily(aType)]; ok { + afiSafiTypes = append(afiSafiTypes, config.AfiSafiType(configType)) + } else { + return nil, fmt.Errorf("unknown afi-safi-in type value: %d", aType) + } + } + return table.NewAfiSafiInCondition(afiSafiTypes) +} + +func NewRoutingActionFromApiStruct(a api.RouteAction) (*table.RoutingAction, error) { + if a == api.RouteAction_NONE { + return nil, nil + } + accept := false + if a == api.RouteAction_ACCEPT { + accept = true + } + return &table.RoutingAction{ + AcceptRoute: accept, + }, nil +} + +func NewCommunityActionFromApiStruct(a *api.CommunityAction) (*table.CommunityAction, error) { + if a == nil { + return nil, nil + } + return table.NewCommunityAction(config.SetCommunity{ + Options: string(config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)]), + SetCommunityMethod: config.SetCommunityMethod{ + CommunitiesList: a.Communities, + }, + }) +} + +func NewExtCommunityActionFromApiStruct(a *api.CommunityAction) (*table.ExtCommunityAction, error) { + if a == nil { + return nil, nil + } + return table.NewExtCommunityAction(config.SetExtCommunity{ + Options: string(config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)]), + SetExtCommunityMethod: config.SetExtCommunityMethod{ + CommunitiesList: a.Communities, + }, + }) +} + +func NewLargeCommunityActionFromApiStruct(a *api.CommunityAction) (*table.LargeCommunityAction, error) { + if a == nil { + return nil, nil + } + return table.NewLargeCommunityAction(config.SetLargeCommunity{ + Options: config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)], + SetLargeCommunityMethod: config.SetLargeCommunityMethod{ + CommunitiesList: a.Communities, + }, + }) +} + +func NewMedActionFromApiStruct(a *api.MedAction) (*table.MedAction, error) { + if a == nil { + return nil, nil + } + return table.NewMedActionFromApiStruct(table.MedActionType(a.Type), a.Value), nil +} + +func NewLocalPrefActionFromApiStruct(a *api.LocalPrefAction) (*table.LocalPrefAction, error) { + if a == nil || a.Value == 0 { + return nil, nil + } + return table.NewLocalPrefAction(a.Value) +} + +func NewAsPathPrependActionFromApiStruct(a *api.AsPrependAction) (*table.AsPathPrependAction, error) { + if a == nil { + return nil, nil + } + return table.NewAsPathPrependAction(config.SetAsPathPrepend{ + RepeatN: uint8(a.Repeat), + As: func() string { + if a.UseLeftMost { + return "last-as" + } + return fmt.Sprintf("%d", a.Asn) + }(), + }) +} + +func NewNexthopActionFromApiStruct(a *api.NexthopAction) (*table.NexthopAction, error) { + if a == nil { + return nil, nil + } + return table.NewNexthopAction(config.BgpNextHopType( + func() string { + if a.Self { + return "self" + } + return a.Address + }(), + )) +} + +func NewStatementFromApiStruct(a *api.Statement) (*table.Statement, error) { + if a.Name == "" { + return nil, fmt.Errorf("empty statement name") + } + var ra table.Action + var as []table.Action + var cs []table.Condition + var err error + if a.Conditions != nil { + cfs := []func() (table.Condition, error){ + func() (table.Condition, error) { + return NewPrefixConditionFromApiStruct(a.Conditions.PrefixSet) + }, + func() (table.Condition, error) { + return NewNeighborConditionFromApiStruct(a.Conditions.NeighborSet) + }, + func() (table.Condition, error) { + return NewAsPathLengthConditionFromApiStruct(a.Conditions.AsPathLength) + }, + func() (table.Condition, error) { + return NewRpkiValidationConditionFromApiStruct(a.Conditions.RpkiResult) + }, + func() (table.Condition, error) { + return NewRouteTypeConditionFromApiStruct(a.Conditions.RouteType) + }, + func() (table.Condition, error) { + return NewAsPathConditionFromApiStruct(a.Conditions.AsPathSet) + }, + func() (table.Condition, error) { + return NewCommunityConditionFromApiStruct(a.Conditions.CommunitySet) + }, + func() (table.Condition, error) { + return NewExtCommunityConditionFromApiStruct(a.Conditions.ExtCommunitySet) + }, + func() (table.Condition, error) { + return NewLargeCommunityConditionFromApiStruct(a.Conditions.LargeCommunitySet) + }, + func() (table.Condition, error) { + return NewNextHopConditionFromApiStruct(a.Conditions.NextHopInList) + }, + func() (table.Condition, error) { + return NewAfiSafiInConditionFromApiStruct(a.Conditions.AfiSafiIn) + }, + } + cs = make([]table.Condition, 0, len(cfs)) + for _, f := range cfs { + c, err := f() + if err != nil { + return nil, err + } + if !reflect.ValueOf(c).IsNil() { + cs = append(cs, c) + } + } + } + if a.Actions != nil { + ra, err = NewRoutingActionFromApiStruct(a.Actions.RouteAction) + if err != nil { + return nil, err + } + afs := []func() (table.Action, error){ + func() (table.Action, error) { + return NewCommunityActionFromApiStruct(a.Actions.Community) + }, + func() (table.Action, error) { + return NewExtCommunityActionFromApiStruct(a.Actions.ExtCommunity) + }, + func() (table.Action, error) { + return NewLargeCommunityActionFromApiStruct(a.Actions.LargeCommunity) + }, + func() (table.Action, error) { + return NewMedActionFromApiStruct(a.Actions.Med) + }, + func() (table.Action, error) { + return NewLocalPrefActionFromApiStruct(a.Actions.LocalPref) + }, + func() (table.Action, error) { + return NewAsPathPrependActionFromApiStruct(a.Actions.AsPrepend) + }, + func() (table.Action, error) { + return NewNexthopActionFromApiStruct(a.Actions.Nexthop) + }, + } + as = make([]table.Action, 0, len(afs)) + for _, f := range afs { + a, err := f() + if err != nil { + return nil, err + } + if !reflect.ValueOf(a).IsNil() { + as = append(as, a) + } + } + } + return &table.Statement{ + Name: a.Name, + Conditions: cs, + RouteAction: ra, + ModActions: as, + }, nil +} + +func (s *Server) GetStatement(ctx context.Context, arg *api.GetStatementRequest) (*api.GetStatementResponse, error) { + l := make([]*api.Statement, 0) + for _, s := range s.bgpServer.GetStatement() { + l = append(l, toStatementApi(s)) + } + return &api.GetStatementResponse{Statements: l}, nil +} + +func (s *Server) AddStatement(ctx context.Context, arg *api.AddStatementRequest) (*api.AddStatementResponse, error) { + if arg == nil || arg.Statement == nil { + return nil, fmt.Errorf("invalid request") + } + st, err := NewStatementFromApiStruct(arg.Statement) + if err == nil { + err = s.bgpServer.AddStatement(st) + } + return &api.AddStatementResponse{}, err +} + +func (s *Server) DeleteStatement(ctx context.Context, arg *api.DeleteStatementRequest) (*api.DeleteStatementResponse, error) { + if arg == nil || arg.Statement == nil { + return nil, fmt.Errorf("invalid request") + } + st, err := NewStatementFromApiStruct(arg.Statement) + if err == nil { + err = s.bgpServer.DeleteStatement(st, arg.All) + } + return &api.DeleteStatementResponse{}, err +} + +func (s *Server) ReplaceStatement(ctx context.Context, arg *api.ReplaceStatementRequest) (*api.ReplaceStatementResponse, error) { + if arg == nil || arg.Statement == nil { + return nil, fmt.Errorf("invalid request") + } + st, err := NewStatementFromApiStruct(arg.Statement) + if err == nil { + err = s.bgpServer.ReplaceStatement(st) + } + return &api.ReplaceStatementResponse{}, err +} + +func NewAPIPolicyFromTableStruct(p *table.Policy) *api.Policy { + return toPolicyApi(p.ToConfig()) +} + +func toPolicyApi(p *config.PolicyDefinition) *api.Policy { + return &api.Policy{ + Name: p.Name, + Statements: func() []*api.Statement { + l := make([]*api.Statement, 0) + for _, s := range p.Statements { + l = append(l, toStatementApi(&s)) + } + return l + }(), + } +} + +func NewAPIPolicyAssignmentFromTableStruct(t *table.PolicyAssignment) *api.PolicyAssignment { + return &api.PolicyAssignment{ + Type: func() api.PolicyType { + switch t.Type { + case table.POLICY_DIRECTION_IMPORT: + return api.PolicyType_IMPORT + case table.POLICY_DIRECTION_EXPORT: + return api.PolicyType_EXPORT + } + log.Errorf("invalid policy-type: %s", t.Type) + return api.PolicyType(-1) + }(), + Default: func() api.RouteAction { + switch t.Default { + case table.ROUTE_TYPE_ACCEPT: + return api.RouteAction_ACCEPT + case table.ROUTE_TYPE_REJECT: + return api.RouteAction_REJECT + } + return api.RouteAction_NONE + }(), + Name: t.Name, + Resource: func() api.Resource { + if t.Name != "" { + return api.Resource_LOCAL + } + return api.Resource_GLOBAL + }(), + Policies: func() []*api.Policy { + l := make([]*api.Policy, 0) + for _, p := range t.Policies { + l = append(l, NewAPIPolicyFromTableStruct(p)) + } + return l + }(), + } +} + +func NewConfigPolicyFromApiStruct(a *api.Policy) (*config.PolicyDefinition, error) { + if a.Name == "" { + return nil, fmt.Errorf("empty policy name") + } + stmts := make([]config.Statement, 0, len(a.Statements)) + for idx, x := range a.Statements { + if x.Name == "" { + x.Name = fmt.Sprintf("%s_stmt%d", a.Name, idx) + } + y, err := NewStatementFromApiStruct(x) + if err != nil { + return nil, err + } + stmt := y.ToConfig() + stmts = append(stmts, *stmt) + } + return &config.PolicyDefinition{ + Name: a.Name, + Statements: stmts, + }, nil +} + +func NewPolicyFromApiStruct(a *api.Policy) (*table.Policy, error) { + if a.Name == "" { + return nil, fmt.Errorf("empty policy name") + } + stmts := make([]*table.Statement, 0, len(a.Statements)) + for idx, x := range a.Statements { + if x.Name == "" { + x.Name = fmt.Sprintf("%s_stmt%d", a.Name, idx) + } + y, err := NewStatementFromApiStruct(x) + if err != nil { + return nil, err + } + stmts = append(stmts, y) + } + return &table.Policy{ + Name: a.Name, + Statements: stmts, + }, nil +} + +func NewRoaListFromTableStructList(origin []*table.ROA) []*api.Roa { + l := make([]*api.Roa, 0) + for _, r := range origin { + host, port, _ := net.SplitHostPort(r.Src) + l = append(l, &api.Roa{ + As: r.AS, + Maxlen: uint32(r.MaxLen), + Prefixlen: uint32(r.Prefix.Length), + Prefix: r.Prefix.Prefix.String(), + Conf: &api.RPKIConf{ + Address: host, + RemotePort: port, + }, + }) + } + return l +} + +func (s *Server) GetPolicy(ctx context.Context, arg *api.GetPolicyRequest) (*api.GetPolicyResponse, error) { + l := make([]*api.Policy, 0) + for _, p := range s.bgpServer.GetPolicy() { + l = append(l, toPolicyApi(p)) + } + return &api.GetPolicyResponse{Policies: l}, nil +} + +func (s *Server) AddPolicy(ctx context.Context, arg *api.AddPolicyRequest) (*api.AddPolicyResponse, error) { + if arg == nil || arg.Policy == nil { + return nil, fmt.Errorf("invalid request") + } + x, err := NewPolicyFromApiStruct(arg.Policy) + if err != nil { + return nil, err + } + return &api.AddPolicyResponse{}, s.bgpServer.AddPolicy(x, arg.ReferExistingStatements) +} + +func (s *Server) DeletePolicy(ctx context.Context, arg *api.DeletePolicyRequest) (*api.DeletePolicyResponse, error) { + if arg == nil || arg.Policy == nil { + return nil, fmt.Errorf("invalid request") + } + x, err := NewPolicyFromApiStruct(arg.Policy) + if err != nil { + return nil, err + } + return &api.DeletePolicyResponse{}, s.bgpServer.DeletePolicy(x, arg.All, arg.PreserveStatements) +} + +func (s *Server) ReplacePolicy(ctx context.Context, arg *api.ReplacePolicyRequest) (*api.ReplacePolicyResponse, error) { + if arg == nil || arg.Policy == nil { + return nil, fmt.Errorf("invalid request") + } + x, err := NewPolicyFromApiStruct(arg.Policy) + if err != nil { + return nil, err + } + return &api.ReplacePolicyResponse{}, s.bgpServer.ReplacePolicy(x, arg.ReferExistingStatements, arg.PreserveStatements) +} + +func toPolicyAssignmentName(a *api.PolicyAssignment) (string, table.PolicyDirection, error) { + switch a.Resource { + case api.Resource_GLOBAL: + switch a.Type { + case api.PolicyType_IMPORT: + return "", table.POLICY_DIRECTION_IMPORT, nil + case api.PolicyType_EXPORT: + return "", table.POLICY_DIRECTION_EXPORT, nil + default: + return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid policy type") + } + case api.Resource_LOCAL: + switch a.Type { + case api.PolicyType_IMPORT: + return a.Name, table.POLICY_DIRECTION_IMPORT, nil + case api.PolicyType_EXPORT: + return a.Name, table.POLICY_DIRECTION_EXPORT, nil + default: + return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid policy type") + } + default: + return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid resource type") + } + +} + +func (s *Server) GetPolicyAssignment(ctx context.Context, arg *api.GetPolicyAssignmentRequest) (*api.GetPolicyAssignmentResponse, error) { + if arg == nil || arg.Assignment == nil { + return nil, fmt.Errorf("invalid request") + } + name, dir, err := toPolicyAssignmentName(arg.Assignment) + if err != nil { + return nil, err + } + def, pols, err := s.bgpServer.GetPolicyAssignment(name, dir) + if err != nil { + return nil, err + } + policies := make([]*table.Policy, 0, len(pols)) + for _, p := range pols { + t, err := table.NewPolicy(*p) + if err != nil { + return nil, err + } + policies = append(policies, t) + } + t := &table.PolicyAssignment{ + Name: name, + Type: dir, + Default: def, + Policies: policies, + } + return &api.GetPolicyAssignmentResponse{NewAPIPolicyAssignmentFromTableStruct(t)}, err +} + +func defaultRouteType(d api.RouteAction) table.RouteType { + switch d { + case api.RouteAction_ACCEPT: + return table.ROUTE_TYPE_ACCEPT + case api.RouteAction_REJECT: + return table.ROUTE_TYPE_REJECT + default: + return table.ROUTE_TYPE_NONE + } +} + +func toPolicyDefinition(policies []*api.Policy) []*config.PolicyDefinition { + l := make([]*config.PolicyDefinition, 0, len(policies)) + for _, p := range policies { + l = append(l, &config.PolicyDefinition{Name: p.Name}) + } + return l +} + +func (s *Server) AddPolicyAssignment(ctx context.Context, arg *api.AddPolicyAssignmentRequest) (*api.AddPolicyAssignmentResponse, error) { + if arg == nil || arg.Assignment == nil { + return nil, fmt.Errorf("invalid request") + } + name, dir, err := toPolicyAssignmentName(arg.Assignment) + if err != nil { + return nil, err + } + return &api.AddPolicyAssignmentResponse{}, s.bgpServer.AddPolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), defaultRouteType(arg.Assignment.Default)) +} + +func (s *Server) DeletePolicyAssignment(ctx context.Context, arg *api.DeletePolicyAssignmentRequest) (*api.DeletePolicyAssignmentResponse, error) { + if arg == nil || arg.Assignment == nil { + return nil, fmt.Errorf("invalid request") + } + name, dir, err := toPolicyAssignmentName(arg.Assignment) + if err != nil { + return nil, err + } + return &api.DeletePolicyAssignmentResponse{}, s.bgpServer.DeletePolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), arg.All) +} + +func (s *Server) ReplacePolicyAssignment(ctx context.Context, arg *api.ReplacePolicyAssignmentRequest) (*api.ReplacePolicyAssignmentResponse, error) { + if arg == nil || arg.Assignment == nil { + return nil, fmt.Errorf("invalid request") + } + name, dir, err := toPolicyAssignmentName(arg.Assignment) + if err != nil { + return nil, err + } + return &api.ReplacePolicyAssignmentResponse{}, s.bgpServer.ReplacePolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), defaultRouteType(arg.Assignment.Default)) +} + +func (s *Server) GetServer(ctx context.Context, arg *api.GetServerRequest) (*api.GetServerResponse, error) { + g := s.bgpServer.GetServer() + return &api.GetServerResponse{ + Global: &api.Global{ + As: g.Config.As, + RouterId: g.Config.RouterId, + ListenPort: g.Config.Port, + ListenAddresses: g.Config.LocalAddressList, + UseMultiplePaths: g.UseMultiplePaths.Config.Enabled, + }, + }, nil +} + +func NewGlobalFromAPIStruct(a *api.Global) *config.Global { + families := make([]config.AfiSafi, 0, len(a.Families)) + for _, f := range a.Families { + name := config.IntToAfiSafiTypeMap[int(f)] + rf, _ := bgp.GetRouteFamily(string(name)) + families = append(families, config.AfiSafi{ + Config: config.AfiSafiConfig{ + AfiSafiName: name, + Enabled: true, + }, + State: config.AfiSafiState{ + AfiSafiName: name, + Enabled: true, + Family: rf, + }, + }) + } + + applyPolicy := &config.ApplyPolicy{} + ReadApplyPolicyFromAPIStruct(applyPolicy, a.ApplyPolicy) + + global := &config.Global{ + Config: config.GlobalConfig{ + As: a.As, + RouterId: a.RouterId, + Port: a.ListenPort, + LocalAddressList: a.ListenAddresses, + }, + ApplyPolicy: *applyPolicy, + AfiSafis: families, + UseMultiplePaths: config.UseMultiplePaths{ + Config: config.UseMultiplePathsConfig{ + Enabled: a.UseMultiplePaths, + }, + }, + } + if a.RouteSelectionOptions != nil { + global.RouteSelectionOptions = config.RouteSelectionOptions{ + Config: config.RouteSelectionOptionsConfig{ + AlwaysCompareMed: a.RouteSelectionOptions.AlwaysCompareMed, + IgnoreAsPathLength: a.RouteSelectionOptions.IgnoreAsPathLength, + ExternalCompareRouterId: a.RouteSelectionOptions.ExternalCompareRouterId, + AdvertiseInactiveRoutes: a.RouteSelectionOptions.AdvertiseInactiveRoutes, + EnableAigp: a.RouteSelectionOptions.EnableAigp, + IgnoreNextHopIgpMetric: a.RouteSelectionOptions.IgnoreNextHopIgpMetric, + DisableBestPathSelection: a.RouteSelectionOptions.DisableBestPathSelection, + }, + } + } + if a.DefaultRouteDistance != nil { + global.DefaultRouteDistance = config.DefaultRouteDistance{ + Config: config.DefaultRouteDistanceConfig{ + ExternalRouteDistance: uint8(a.DefaultRouteDistance.ExternalRouteDistance), + InternalRouteDistance: uint8(a.DefaultRouteDistance.InternalRouteDistance), + }, + } + } + if a.Confederation != nil { + global.Confederation = config.Confederation{ + Config: config.ConfederationConfig{ + Enabled: a.Confederation.Enabled, + Identifier: a.Confederation.Identifier, + MemberAsList: a.Confederation.MemberAsList, + }, + } + } + if a.GracefulRestart != nil { + global.GracefulRestart = config.GracefulRestart{ + Config: config.GracefulRestartConfig{ + Enabled: a.GracefulRestart.Enabled, + RestartTime: uint16(a.GracefulRestart.RestartTime), + StaleRoutesTime: float64(a.GracefulRestart.StaleRoutesTime), + HelperOnly: a.GracefulRestart.HelperOnly, + DeferralTime: uint16(a.GracefulRestart.DeferralTime), + NotificationEnabled: a.GracefulRestart.NotificationEnabled, + LongLivedEnabled: a.GracefulRestart.LonglivedEnabled, + }, + } + } + return global +} + +func NewGlobalFromConfigStruct(c *config.Global) *api.Global { + families := make([]uint32, 0, len(c.AfiSafis)) + for _, f := range c.AfiSafis { + families = append(families, uint32(config.AfiSafiTypeToIntMap[f.Config.AfiSafiName])) + } + + applyPolicy := NewApplyPolicyFromConfigStruct(&c.ApplyPolicy) + + return &api.Global{ + As: c.Config.As, + RouterId: c.Config.RouterId, + ListenPort: c.Config.Port, + ListenAddresses: c.Config.LocalAddressList, + Families: families, + UseMultiplePaths: c.UseMultiplePaths.Config.Enabled, + RouteSelectionOptions: &api.RouteSelectionOptionsConfig{ + AlwaysCompareMed: c.RouteSelectionOptions.Config.AlwaysCompareMed, + IgnoreAsPathLength: c.RouteSelectionOptions.Config.IgnoreAsPathLength, + ExternalCompareRouterId: c.RouteSelectionOptions.Config.ExternalCompareRouterId, + AdvertiseInactiveRoutes: c.RouteSelectionOptions.Config.AdvertiseInactiveRoutes, + EnableAigp: c.RouteSelectionOptions.Config.EnableAigp, + IgnoreNextHopIgpMetric: c.RouteSelectionOptions.Config.IgnoreNextHopIgpMetric, + DisableBestPathSelection: c.RouteSelectionOptions.Config.DisableBestPathSelection, + }, + DefaultRouteDistance: &api.DefaultRouteDistance{ + ExternalRouteDistance: uint32(c.DefaultRouteDistance.Config.ExternalRouteDistance), + InternalRouteDistance: uint32(c.DefaultRouteDistance.Config.InternalRouteDistance), + }, + Confederation: &api.Confederation{ + Enabled: c.Confederation.Config.Enabled, + Identifier: c.Confederation.Config.Identifier, + MemberAsList: c.Confederation.Config.MemberAsList, + }, + GracefulRestart: &api.GracefulRestart{ + Enabled: c.GracefulRestart.Config.Enabled, + RestartTime: uint32(c.GracefulRestart.Config.RestartTime), + StaleRoutesTime: uint32(c.GracefulRestart.Config.StaleRoutesTime), + HelperOnly: c.GracefulRestart.Config.HelperOnly, + DeferralTime: uint32(c.GracefulRestart.Config.DeferralTime), + NotificationEnabled: c.GracefulRestart.Config.NotificationEnabled, + LonglivedEnabled: c.GracefulRestart.Config.LongLivedEnabled, + }, + ApplyPolicy: applyPolicy, + } +} + +func (s *Server) StartServer(ctx context.Context, arg *api.StartServerRequest) (*api.StartServerResponse, error) { + if arg == nil || arg.Global == nil { + return nil, fmt.Errorf("invalid request") + } + g := arg.Global + if net.ParseIP(g.RouterId) == nil { + return nil, fmt.Errorf("invalid router-id format: %s", g.RouterId) + } + + global := NewGlobalFromAPIStruct(arg.Global) + + return &api.StartServerResponse{}, s.bgpServer.Start(global) +} + +func (s *Server) StopServer(ctx context.Context, arg *api.StopServerRequest) (*api.StopServerResponse, error) { + return &api.StopServerResponse{}, s.bgpServer.Stop() +} + +func (s *Server) GetRibInfo(ctx context.Context, arg *api.GetRibInfoRequest) (*api.GetRibInfoResponse, error) { + if arg == nil || arg.Info == nil { + return nil, fmt.Errorf("invalid request") + } + family := bgp.RouteFamily(arg.Info.Family) + var in bool + var err error + var info *table.TableInfo + switch arg.Info.Type { + case api.Resource_GLOBAL, api.Resource_LOCAL: + info, err = s.bgpServer.GetRibInfo(arg.Info.Name, family) + case api.Resource_ADJ_IN: + in = true + fallthrough + case api.Resource_ADJ_OUT: + info, err = s.bgpServer.GetAdjRibInfo(arg.Info.Name, family, in) + default: + return nil, fmt.Errorf("unsupported resource type: %s", arg.Info.Type) + } + + if err != nil { + return nil, err + } + + return &api.GetRibInfoResponse{ + Info: &api.TableInfo{ + Type: arg.Info.Type, + Family: arg.Info.Family, + Name: arg.Info.Name, + NumDestination: uint64(info.NumDestination), + NumPath: uint64(info.NumPath), + NumAccepted: uint64(info.NumAccepted), + }, + }, nil +} + +func (s *Server) AddCollector(ctx context.Context, arg *api.AddCollectorRequest) (*api.AddCollectorResponse, error) { + return &api.AddCollectorResponse{}, s.bgpServer.AddCollector(&config.CollectorConfig{ + Url: arg.Url, + DbName: arg.DbName, + TableDumpInterval: arg.TableDumpInterval, + }) +} + +func (s *Server) Shutdown(ctx context.Context, arg *api.ShutdownRequest) (*api.ShutdownResponse, error) { + s.bgpServer.Shutdown() + return &api.ShutdownResponse{}, nil +} diff --git a/pkg/server/mrt.go b/pkg/server/mrt.go new file mode 100644 index 00000000..c654cb78 --- /dev/null +++ b/pkg/server/mrt.go @@ -0,0 +1,409 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "bytes" + "fmt" + "os" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/packet/mrt" + + log "github.com/sirupsen/logrus" +) + +const ( + MIN_ROTATION_INTERVAL = 60 + MIN_DUMP_INTERVAL = 60 +) + +type mrtWriter struct { + dead chan struct{} + s *BgpServer + c *config.MrtConfig + file *os.File + rotationInterval uint64 + dumpInterval uint64 +} + +func (m *mrtWriter) Stop() { + close(m.dead) +} + +func (m *mrtWriter) loop() error { + ops := []WatchOption{} + switch m.c.DumpType { + case config.MRT_TYPE_UPDATES: + ops = append(ops, WatchUpdate(false)) + case config.MRT_TYPE_TABLE: + if len(m.c.TableName) > 0 { + ops = append(ops, WatchTableName(m.c.TableName)) + } + } + w := m.s.Watch(ops...) + rotator := func() *time.Ticker { + if m.rotationInterval == 0 { + return &time.Ticker{} + } + return time.NewTicker(time.Second * time.Duration(m.rotationInterval)) + }() + dump := func() *time.Ticker { + if m.dumpInterval == 0 { + return &time.Ticker{} + } + return time.NewTicker(time.Second * time.Duration(m.dumpInterval)) + }() + + defer func() { + if m.file != nil { + m.file.Close() + } + if m.rotationInterval != 0 { + rotator.Stop() + } + if m.dumpInterval != 0 { + dump.Stop() + } + w.Stop() + }() + + for { + serialize := func(ev WatchEvent) []*mrt.MRTMessage { + msg := make([]*mrt.MRTMessage, 0, 1) + switch e := ev.(type) { + case *WatchEventUpdate: + if e.Init { + return nil + } + mp := mrt.NewBGP4MPMessage(e.PeerAS, e.LocalAS, 0, e.PeerAddress.String(), e.LocalAddress.String(), e.FourBytesAs, nil) + mp.BGPMessagePayload = e.Payload + isAddPath := e.Neighbor.IsAddPathReceiveEnabled(e.PathList[0].GetRouteFamily()) + subtype := mrt.MESSAGE + switch { + case isAddPath && e.FourBytesAs: + subtype = mrt.MESSAGE_AS4_ADDPATH + case isAddPath: + subtype = mrt.MESSAGE_ADDPATH + case e.FourBytesAs: + subtype = mrt.MESSAGE_AS4 + } + if bm, err := mrt.NewMRTMessage(uint32(e.Timestamp.Unix()), mrt.BGP4MP, subtype, mp); err != nil { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Data": e, + "Error": err, + }).Warnf("Failed to create MRT BGP4MP message (subtype %d)", subtype) + } else { + msg = append(msg, bm) + } + case *WatchEventTable: + t := uint32(time.Now().Unix()) + + peers := make([]*mrt.Peer, 1, len(e.Neighbor)+1) + // Adding dummy Peer record for locally generated routes + peers[0] = mrt.NewPeer("0.0.0.0", "0.0.0.0", 0, true) + neighborMap := make(map[string]*config.Neighbor) + for _, pconf := range e.Neighbor { + peers = append(peers, mrt.NewPeer(pconf.State.RemoteRouterId, pconf.State.NeighborAddress, pconf.Config.PeerAs, true)) + neighborMap[pconf.State.NeighborAddress] = pconf + } + + if bm, err := mrt.NewMRTMessage(t, mrt.TABLE_DUMPv2, mrt.PEER_INDEX_TABLE, mrt.NewPeerIndexTable(e.RouterId, "", peers)); err != nil { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Data": e, + "Error": err, + }).Warnf("Failed to create MRT TABLE_DUMPv2 message (subtype %d)", mrt.PEER_INDEX_TABLE) + break + } else { + msg = append(msg, bm) + } + + idx := func(p *table.Path) uint16 { + for i, pconf := range e.Neighbor { + if p.GetSource().Address.String() == pconf.State.NeighborAddress { + return uint16(i) + } + } + return uint16(len(e.Neighbor)) + } + + subtype := func(p *table.Path, isAddPath bool) mrt.MRTSubTypeTableDumpv2 { + t := mrt.RIB_GENERIC + switch p.GetRouteFamily() { + case bgp.RF_IPv4_UC: + t = mrt.RIB_IPV4_UNICAST + case bgp.RF_IPv4_MC: + t = mrt.RIB_IPV4_MULTICAST + case bgp.RF_IPv6_UC: + t = mrt.RIB_IPV6_UNICAST + case bgp.RF_IPv6_MC: + t = mrt.RIB_IPV6_MULTICAST + } + if isAddPath { + // Shift non-additional-path version to *_ADDPATH + t += 6 + } + return t + } + + seq := uint32(0) + appendTableDumpMsg := func(path *table.Path, entries []*mrt.RibEntry, isAddPath bool) { + st := subtype(path, isAddPath) + if bm, err := mrt.NewMRTMessage(t, mrt.TABLE_DUMPv2, st, mrt.NewRib(seq, path.GetNlri(), entries)); err != nil { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Data": e, + "Error": err, + }).Warnf("Failed to create MRT TABLE_DUMPv2 message (subtype %d)", st) + } else { + msg = append(msg, bm) + seq++ + } + } + for _, pathList := range e.PathList { + entries := make([]*mrt.RibEntry, 0, len(pathList)) + entriesAddPath := make([]*mrt.RibEntry, 0, len(pathList)) + for _, path := range pathList { + isAddPath := false + if path.IsLocal() { + isAddPath = true + } else if neighbor, ok := neighborMap[path.GetSource().Address.String()]; ok { + isAddPath = neighbor.IsAddPathReceiveEnabled(path.GetRouteFamily()) + } + if !isAddPath { + entries = append(entries, mrt.NewRibEntry(idx(path), uint32(path.GetTimestamp().Unix()), 0, path.GetPathAttrs(), false)) + } else { + entriesAddPath = append(entriesAddPath, mrt.NewRibEntry(idx(path), uint32(path.GetTimestamp().Unix()), path.GetNlri().PathIdentifier(), path.GetPathAttrs(), true)) + } + } + if len(entries) > 0 { + appendTableDumpMsg(pathList[0], entries, false) + } + if len(entriesAddPath) > 0 { + appendTableDumpMsg(pathList[0], entriesAddPath, true) + } + } + } + return msg + } + + drain := func(ev WatchEvent) { + events := make([]WatchEvent, 0, 1+len(w.Event())) + if ev != nil { + events = append(events, ev) + } + + for len(w.Event()) > 0 { + events = append(events, <-w.Event()) + } + + w := func(buf []byte) { + if _, err := m.file.Write(buf); err == nil { + m.file.Sync() + } else { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Error": err, + }).Warn("Can't write to destination MRT file") + } + } + + var b bytes.Buffer + for _, e := range events { + for _, m := range serialize(e) { + if buf, err := m.Serialize(); err != nil { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Data": e, + "Error": err, + }).Warn("Failed to serialize event") + } else { + b.Write(buf) + if b.Len() > 1*1000*1000 { + w(b.Bytes()) + b.Reset() + } + } + } + } + if b.Len() > 0 { + w(b.Bytes()) + } + } + rotate := func() { + m.file.Close() + file, err := mrtFileOpen(m.c.FileName, m.rotationInterval) + if err == nil { + m.file = file + } else { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Error": err, + }).Warn("can't rotate MRT file") + } + } + + select { + case <-m.dead: + drain(nil) + return nil + case e := <-w.Event(): + drain(e) + if m.c.DumpType == config.MRT_TYPE_TABLE && m.rotationInterval != 0 { + rotate() + } + case <-rotator.C: + if m.c.DumpType == config.MRT_TYPE_UPDATES { + rotate() + } else { + w.Generate(WATCH_EVENT_TYPE_TABLE) + } + case <-dump.C: + w.Generate(WATCH_EVENT_TYPE_TABLE) + } + } +} + +func mrtFileOpen(filename string, interval uint64) (*os.File, error) { + realname := filename + if interval != 0 { + realname = time.Now().Format(filename) + } + log.WithFields(log.Fields{ + "Topic": "mrt", + "Filename": realname, + "Dump Interval": interval, + }).Debug("Setting new MRT destination file") + + i := len(realname) + for i > 0 && os.IsPathSeparator(realname[i-1]) { + // skip trailing path separators + i-- + } + j := i + + for j > 0 && !os.IsPathSeparator(realname[j-1]) { + j-- + } + + if j > 0 { + if err := os.MkdirAll(realname[0:j-1], 0755); err != nil { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Error": err, + }).Warn("can't create MRT destination directory") + return nil, err + } + } + + file, err := os.OpenFile(realname, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "mrt", + "Error": err, + }).Warn("can't create MRT destination file") + } + return file, err +} + +func newMrtWriter(s *BgpServer, c *config.MrtConfig, rInterval, dInterval uint64) (*mrtWriter, error) { + file, err := mrtFileOpen(c.FileName, rInterval) + if err != nil { + return nil, err + } + m := mrtWriter{ + s: s, + c: c, + file: file, + rotationInterval: rInterval, + dumpInterval: dInterval, + } + go m.loop() + return &m, nil +} + +type mrtManager struct { + bgpServer *BgpServer + writer map[string]*mrtWriter +} + +func (m *mrtManager) enable(c *config.MrtConfig) error { + if _, ok := m.writer[c.FileName]; ok { + return fmt.Errorf("%s already exists", c.FileName) + } + + rInterval := c.RotationInterval + dInterval := c.DumpInterval + + setRotationMin := func() { + if rInterval < MIN_ROTATION_INTERVAL { + log.WithFields(log.Fields{ + "Topic": "MRT", + }).Infof("minimum mrt rotation interval is %d seconds", MIN_ROTATION_INTERVAL) + rInterval = MIN_ROTATION_INTERVAL + } + } + + if c.DumpType == config.MRT_TYPE_TABLE { + if rInterval == 0 { + if dInterval < MIN_DUMP_INTERVAL { + log.WithFields(log.Fields{ + "Topic": "MRT", + }).Infof("minimum mrt dump interval is %d seconds", MIN_DUMP_INTERVAL) + dInterval = MIN_DUMP_INTERVAL + } + } else if dInterval == 0 { + setRotationMin() + } else { + return fmt.Errorf("can't specify both intervals in the table dump type") + } + } else if c.DumpType == config.MRT_TYPE_UPDATES { + // ignore the dump interval + dInterval = 0 + if len(c.TableName) > 0 { + return fmt.Errorf("can't specify the table name with the update dump type") + } + setRotationMin() + } + + w, err := newMrtWriter(m.bgpServer, c, rInterval, dInterval) + if err == nil { + m.writer[c.FileName] = w + } + return err +} + +func (m *mrtManager) disable(c *config.MrtConfig) error { + w, ok := m.writer[c.FileName] + if !ok { + return fmt.Errorf("%s doesn't exists", c.FileName) + } + w.Stop() + delete(m.writer, c.FileName) + return nil +} + +func newMrtManager(s *BgpServer) *mrtManager { + return &mrtManager{ + bgpServer: s, + writer: make(map[string]*mrtWriter), + } +} diff --git a/pkg/server/peer.go b/pkg/server/peer.go new file mode 100644 index 00000000..1f3dd8d8 --- /dev/null +++ b/pkg/server/peer.go @@ -0,0 +1,522 @@ +// Copyright (C) 2014-2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "fmt" + "net" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/eapache/channels" + log "github.com/sirupsen/logrus" +) + +const ( + FLOP_THRESHOLD = time.Second * 30 + MIN_CONNECT_RETRY = 10 +) + +type PeerGroup struct { + Conf *config.PeerGroup + members map[string]config.Neighbor + dynamicNeighbors map[string]*config.DynamicNeighbor +} + +func NewPeerGroup(c *config.PeerGroup) *PeerGroup { + return &PeerGroup{ + Conf: c, + members: make(map[string]config.Neighbor), + dynamicNeighbors: make(map[string]*config.DynamicNeighbor), + } +} + +func (pg *PeerGroup) AddMember(c config.Neighbor) { + pg.members[c.State.NeighborAddress] = c +} + +func (pg *PeerGroup) DeleteMember(c config.Neighbor) { + delete(pg.members, c.State.NeighborAddress) +} + +func (pg *PeerGroup) AddDynamicNeighbor(c *config.DynamicNeighbor) { + pg.dynamicNeighbors[c.Config.Prefix] = c +} + +func newDynamicPeer(g *config.Global, neighborAddress string, pg *config.PeerGroup, loc *table.TableManager, policy *table.RoutingPolicy) *Peer { + conf := config.Neighbor{ + Config: config.NeighborConfig{ + PeerGroup: pg.Config.PeerGroupName, + }, + State: config.NeighborState{ + NeighborAddress: neighborAddress, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + PassiveMode: true, + }, + }, + } + if err := config.OverwriteNeighborConfigWithPeerGroup(&conf, pg); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": neighborAddress, + }).Debugf("Can't overwrite neighbor config: %s", err) + return nil + } + if err := config.SetDefaultNeighborConfigValues(&conf, pg, g); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": neighborAddress, + }).Debugf("Can't set default config: %s", err) + return nil + } + peer := NewPeer(g, &conf, loc, policy) + peer.fsm.state = bgp.BGP_FSM_ACTIVE + return peer +} + +type Peer struct { + tableId string + fsm *FSM + adjRibIn *table.AdjRib + outgoing *channels.InfiniteChannel + policy *table.RoutingPolicy + localRib *table.TableManager + prefixLimitWarned map[bgp.RouteFamily]bool + llgrEndChs []chan struct{} +} + +func NewPeer(g *config.Global, conf *config.Neighbor, loc *table.TableManager, policy *table.RoutingPolicy) *Peer { + peer := &Peer{ + outgoing: channels.NewInfiniteChannel(), + localRib: loc, + policy: policy, + fsm: NewFSM(g, conf, policy), + prefixLimitWarned: make(map[bgp.RouteFamily]bool), + } + if peer.isRouteServerClient() { + peer.tableId = conf.State.NeighborAddress + } else { + peer.tableId = table.GLOBAL_RIB_NAME + } + rfs, _ := config.AfiSafis(conf.AfiSafis).ToRfList() + peer.adjRibIn = table.NewAdjRib(rfs) + return peer +} + +func (peer *Peer) AS() uint32 { + return peer.fsm.pConf.State.PeerAs +} + +func (peer *Peer) ID() string { + return peer.fsm.pConf.State.NeighborAddress +} + +func (peer *Peer) TableID() string { + return peer.tableId +} + +func (peer *Peer) isIBGPPeer() bool { + return peer.fsm.pConf.State.PeerAs == peer.fsm.gConf.Config.As +} + +func (peer *Peer) isRouteServerClient() bool { + return peer.fsm.pConf.RouteServer.Config.RouteServerClient +} + +func (peer *Peer) isRouteReflectorClient() bool { + return peer.fsm.pConf.RouteReflector.Config.RouteReflectorClient +} + +func (peer *Peer) isGracefulRestartEnabled() bool { + return peer.fsm.pConf.GracefulRestart.State.Enabled +} + +func (peer *Peer) getAddPathMode(family bgp.RouteFamily) bgp.BGPAddPathMode { + if mode, y := peer.fsm.rfMap[family]; y { + return mode + } + return bgp.BGP_ADD_PATH_NONE +} + +func (peer *Peer) isAddPathReceiveEnabled(family bgp.RouteFamily) bool { + return (peer.getAddPathMode(family) & bgp.BGP_ADD_PATH_RECEIVE) > 0 +} + +func (peer *Peer) isAddPathSendEnabled(family bgp.RouteFamily) bool { + return (peer.getAddPathMode(family) & bgp.BGP_ADD_PATH_SEND) > 0 +} + +func (peer *Peer) isDynamicNeighbor() bool { + return peer.fsm.pConf.Config.NeighborAddress == "" && peer.fsm.pConf.Config.NeighborInterface == "" +} + +func (peer *Peer) recvedAllEOR() bool { + for _, a := range peer.fsm.pConf.AfiSafis { + if s := a.MpGracefulRestart.State; s.Enabled && !s.EndOfRibReceived { + return false + } + } + return true +} + +func (peer *Peer) configuredRFlist() []bgp.RouteFamily { + rfs, _ := config.AfiSafis(peer.fsm.pConf.AfiSafis).ToRfList() + return rfs +} + +func (peer *Peer) negotiatedRFList() []bgp.RouteFamily { + l := make([]bgp.RouteFamily, 0, len(peer.fsm.rfMap)) + for family, _ := range peer.fsm.rfMap { + l = append(l, family) + } + return l +} + +func (peer *Peer) toGlobalFamilies(families []bgp.RouteFamily) []bgp.RouteFamily { + if peer.fsm.pConf.Config.Vrf != "" { + fs := make([]bgp.RouteFamily, 0, len(families)) + for _, f := range families { + switch f { + case bgp.RF_IPv4_UC: + fs = append(fs, bgp.RF_IPv4_VPN) + case bgp.RF_IPv6_UC: + fs = append(fs, bgp.RF_IPv6_VPN) + default: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Family": f, + "VRF": peer.fsm.pConf.Config.Vrf, + }).Warn("invalid family configured for neighbor with vrf") + } + } + families = fs + } + return families +} + +func classifyFamilies(all, part []bgp.RouteFamily) ([]bgp.RouteFamily, []bgp.RouteFamily) { + a := []bgp.RouteFamily{} + b := []bgp.RouteFamily{} + for _, f := range all { + p := true + for _, g := range part { + if f == g { + p = false + a = append(a, f) + break + } + } + if p { + b = append(b, f) + } + } + return a, b +} + +func (peer *Peer) forwardingPreservedFamilies() ([]bgp.RouteFamily, []bgp.RouteFamily) { + list := []bgp.RouteFamily{} + for _, a := range peer.fsm.pConf.AfiSafis { + if s := a.MpGracefulRestart.State; s.Enabled && s.Received { + list = append(list, a.State.Family) + } + } + return classifyFamilies(peer.configuredRFlist(), list) +} + +func (peer *Peer) llgrFamilies() ([]bgp.RouteFamily, []bgp.RouteFamily) { + list := []bgp.RouteFamily{} + for _, a := range peer.fsm.pConf.AfiSafis { + if a.LongLivedGracefulRestart.State.Enabled { + list = append(list, a.State.Family) + } + } + return classifyFamilies(peer.configuredRFlist(), list) +} + +func (peer *Peer) isLLGREnabledFamily(family bgp.RouteFamily) bool { + if !peer.fsm.pConf.GracefulRestart.Config.LongLivedEnabled { + return false + } + fs, _ := peer.llgrFamilies() + for _, f := range fs { + if f == family { + return true + } + } + return false +} + +func (peer *Peer) llgrRestartTime(family bgp.RouteFamily) uint32 { + for _, a := range peer.fsm.pConf.AfiSafis { + if a.State.Family == family { + return a.LongLivedGracefulRestart.State.PeerRestartTime + } + } + return 0 +} + +func (peer *Peer) llgrRestartTimerExpired(family bgp.RouteFamily) bool { + all := true + for _, a := range peer.fsm.pConf.AfiSafis { + if a.State.Family == family { + a.LongLivedGracefulRestart.State.PeerRestartTimerExpired = true + } + s := a.LongLivedGracefulRestart.State + if s.Received && !s.PeerRestartTimerExpired { + all = false + } + } + return all +} + +func (peer *Peer) markLLGRStale(fs []bgp.RouteFamily) []*table.Path { + paths := peer.adjRibIn.PathList(fs, true) + for i, p := range paths { + doStale := true + for _, c := range p.GetCommunities() { + if c == uint32(bgp.COMMUNITY_NO_LLGR) { + doStale = false + p = p.Clone(true) + break + } + } + if doStale { + p = p.Clone(false) + p.SetCommunities([]uint32{uint32(bgp.COMMUNITY_LLGR_STALE)}, false) + } + paths[i] = p + } + return paths +} + +func (peer *Peer) stopPeerRestarting() { + peer.fsm.pConf.GracefulRestart.State.PeerRestarting = false + for _, ch := range peer.llgrEndChs { + close(ch) + } + peer.llgrEndChs = make([]chan struct{}, 0) + +} + +func (peer *Peer) filterPathFromSourcePeer(path, old *table.Path) *table.Path { + if peer.ID() != path.GetSource().Address.String() { + return path + } + + // Note: Multiple paths having the same prefix could exist the withdrawals + // list in the case of Route Server setup with import policies modifying + // paths. In such case, gobgp sends duplicated update messages; withdraw + // messages for the same prefix. + if !peer.isRouteServerClient() { + if peer.isRouteReflectorClient() && path.GetRouteFamily() == bgp.RF_RTC_UC { + // When the peer is a Route Reflector client and the given path + // contains the Route Tartget Membership NLRI, the path should not + // be withdrawn in order to signal the client to distribute routes + // with the specific RT to Route Reflector. + return path + } else if !path.IsWithdraw && old != nil && old.GetSource().Address.String() != peer.ID() { + // Say, peer A and B advertized same prefix P, and best path + // calculation chose a path from B as best. When B withdraws prefix + // P, best path calculation chooses the path from A as best. For + // peers other than A, this path should be advertised (as implicit + // withdrawal). However for A, we should advertise the withdrawal + // path. Thing is same when peer A and we advertized prefix P (as + // local route), then, we withdraws the prefix. + return old.Clone(true) + } + } + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Data": path, + }).Debug("From me, ignore.") + return nil +} + +func (peer *Peer) doPrefixLimit(k bgp.RouteFamily, c *config.PrefixLimitConfig) *bgp.BGPMessage { + if maxPrefixes := int(c.MaxPrefixes); maxPrefixes > 0 { + count := peer.adjRibIn.Count([]bgp.RouteFamily{k}) + pct := int(c.ShutdownThresholdPct) + if pct > 0 && !peer.prefixLimitWarned[k] && count > (maxPrefixes*pct/100) { + peer.prefixLimitWarned[k] = true + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": k.String(), + }).Warnf("prefix limit %d%% reached", pct) + } + if count > maxPrefixes { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": k.String(), + }).Warnf("prefix limit reached") + return bgp.NewBGPNotificationMessage(bgp.BGP_ERROR_CEASE, bgp.BGP_ERROR_SUB_MAXIMUM_NUMBER_OF_PREFIXES_REACHED, nil) + } + } + return nil + +} + +func (peer *Peer) updatePrefixLimitConfig(c []config.AfiSafi) error { + x := peer.fsm.pConf.AfiSafis + y := c + if len(x) != len(y) { + return fmt.Errorf("changing supported afi-safi is not allowed") + } + m := make(map[bgp.RouteFamily]config.PrefixLimitConfig) + for _, e := range x { + m[e.State.Family] = e.PrefixLimit.Config + } + for _, e := range y { + if p, ok := m[e.State.Family]; !ok { + return fmt.Errorf("changing supported afi-safi is not allowed") + } else if !p.Equal(&e.PrefixLimit.Config) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": e.Config.AfiSafiName, + "OldMaxPrefixes": p.MaxPrefixes, + "NewMaxPrefixes": e.PrefixLimit.Config.MaxPrefixes, + "OldShutdownThresholdPct": p.ShutdownThresholdPct, + "NewShutdownThresholdPct": e.PrefixLimit.Config.ShutdownThresholdPct, + }).Warnf("update prefix limit configuration") + peer.prefixLimitWarned[e.State.Family] = false + if msg := peer.doPrefixLimit(e.State.Family, &e.PrefixLimit.Config); msg != nil { + sendFsmOutgoingMsg(peer, nil, msg, true) + } + } + } + peer.fsm.pConf.AfiSafis = c + return nil +} + +func (peer *Peer) handleUpdate(e *FsmMsg) ([]*table.Path, []bgp.RouteFamily, *bgp.BGPMessage) { + m := e.MsgData.(*bgp.BGPMessage) + update := m.Body.(*bgp.BGPUpdate) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + "nlri": update.NLRI, + "withdrawals": update.WithdrawnRoutes, + "attributes": update.PathAttributes, + }).Debug("received update") + peer.fsm.pConf.Timers.State.UpdateRecvTime = time.Now().Unix() + if len(e.PathList) > 0 { + paths := make([]*table.Path, 0, len(e.PathList)) + eor := []bgp.RouteFamily{} + for _, path := range e.PathList { + if path.IsEOR() { + family := path.GetRouteFamily() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "AddressFamily": family, + }).Debug("EOR received") + eor = append(eor, family) + continue + } + // RFC4271 9.1.2 Phase 2: Route Selection + // + // If the AS_PATH attribute of a BGP route contains an AS loop, the BGP + // route should be excluded from the Phase 2 decision function. + if aspath := path.GetAsPath(); aspath != nil { + if hasOwnASLoop(peer.fsm.peerInfo.LocalAS, int(peer.fsm.pConf.AsPathOptions.Config.AllowOwnAs), aspath) { + path.SetAsLooped(true) + continue + } + } + paths = append(paths, path) + } + peer.adjRibIn.Update(e.PathList) + for _, af := range peer.fsm.pConf.AfiSafis { + if msg := peer.doPrefixLimit(af.State.Family, &af.PrefixLimit.Config); msg != nil { + return nil, nil, msg + } + } + return paths, eor, nil + } + return nil, nil, nil +} + +func (peer *Peer) startFSMHandler(incoming *channels.InfiniteChannel, stateCh chan *FsmMsg) { + peer.fsm.h = NewFSMHandler(peer.fsm, incoming, stateCh, peer.outgoing) +} + +func (peer *Peer) StaleAll(rfList []bgp.RouteFamily) []*table.Path { + return peer.adjRibIn.StaleAll(rfList) +} + +func (peer *Peer) PassConn(conn *net.TCPConn) { + select { + case peer.fsm.connCh <- conn: + default: + conn.Close() + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Warn("accepted conn is closed to avoid be blocked") + } +} + +func (peer *Peer) DropAll(rfList []bgp.RouteFamily) { + peer.adjRibIn.Drop(rfList) +} + +func (peer *Peer) stopFSM() error { + failed := false + addr := peer.fsm.pConf.State.NeighborAddress + t1 := time.AfterFunc(time.Minute*5, func() { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Warnf("Failed to free the fsm.h.t for %s", addr) + failed = true + }) + peer.fsm.h.t.Kill(nil) + peer.fsm.h.t.Wait() + t1.Stop() + if !failed { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Debug("freed fsm.h.t") + cleanInfiniteChannel(peer.outgoing) + } + failed = false + t2 := time.AfterFunc(time.Minute*5, func() { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Warnf("Failed to free the fsm.t for %s", addr) + failed = true + }) + peer.fsm.t.Kill(nil) + peer.fsm.t.Wait() + t2.Stop() + if !failed { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Debug("freed fsm.t") + return nil + } + return fmt.Errorf("Failed to free FSM for %s", addr) +} diff --git a/pkg/server/rpki.go b/pkg/server/rpki.go new file mode 100644 index 00000000..606b18ab --- /dev/null +++ b/pkg/server/rpki.go @@ -0,0 +1,712 @@ +// Copyright (C) 2015,2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "encoding/binary" + "fmt" + "io" + "net" + "sort" + "strconv" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + "github.com/osrg/gobgp/pkg/packet/rtr" + + "github.com/armon/go-radix" + log "github.com/sirupsen/logrus" + "golang.org/x/net/context" +) + +const ( + CONNECT_RETRY_INTERVAL = 30 +) + +func before(a, b uint32) bool { + return int32(a-b) < 0 +} + +type RoaBucket struct { + Prefix *table.IPPrefix + entries []*table.ROA +} + +func (r *RoaBucket) GetEntries() []*table.ROA { + return r.entries +} + +type roas []*table.ROA + +func (r roas) Len() int { + return len(r) +} + +func (r roas) Swap(i, j int) { + r[i], r[j] = r[j], r[i] +} + +func (r roas) Less(i, j int) bool { + r1 := r[i] + r2 := r[j] + + if r1.MaxLen < r2.MaxLen { + return true + } else if r1.MaxLen > r2.MaxLen { + return false + } + + if r1.AS < r2.AS { + return true + } + return false +} + +type ROAEventType uint8 + +const ( + CONNECTED ROAEventType = iota + DISCONNECTED + RTR + LIFETIMEOUT +) + +type ROAEvent struct { + EventType ROAEventType + Src string + Data []byte + conn *net.TCPConn +} + +type roaManager struct { + AS uint32 + Roas map[bgp.RouteFamily]*radix.Tree + eventCh chan *ROAEvent + clientMap map[string]*roaClient +} + +func NewROAManager(as uint32) (*roaManager, error) { + m := &roaManager{ + AS: as, + Roas: make(map[bgp.RouteFamily]*radix.Tree), + } + m.Roas[bgp.RF_IPv4_UC] = radix.New() + m.Roas[bgp.RF_IPv6_UC] = radix.New() + m.eventCh = make(chan *ROAEvent) + m.clientMap = make(map[string]*roaClient) + return m, nil +} + +func (c *roaManager) enabled() bool { + return len(c.clientMap) != 0 +} + +func (m *roaManager) SetAS(as uint32) error { + if m.AS != 0 { + return fmt.Errorf("AS was already configured") + } + m.AS = as + return nil +} + +func (m *roaManager) AddServer(host string, lifetime int64) error { + address, port, err := net.SplitHostPort(host) + if err != nil { + return err + } + if lifetime == 0 { + lifetime = 3600 + } + if _, ok := m.clientMap[host]; ok { + return fmt.Errorf("ROA server exists %s", host) + } + m.clientMap[host] = NewRoaClient(address, port, m.eventCh, lifetime) + return nil +} + +func (m *roaManager) DeleteServer(host string) error { + client, ok := m.clientMap[host] + if !ok { + return fmt.Errorf("ROA server doesn't exists %s", host) + } + client.stop() + m.deleteAllROA(host) + delete(m.clientMap, host) + return nil +} + +func (m *roaManager) deleteAllROA(network string) { + for _, tree := range m.Roas { + deleteKeys := make([]string, 0, tree.Len()) + tree.Walk(func(s string, v interface{}) bool { + b, _ := v.(*RoaBucket) + newEntries := make([]*table.ROA, 0, len(b.entries)) + for _, r := range b.entries { + if r.Src != network { + newEntries = append(newEntries, r) + } + } + if len(newEntries) > 0 { + b.entries = newEntries + } else { + deleteKeys = append(deleteKeys, s) + } + return false + }) + for _, key := range deleteKeys { + tree.Delete(key) + } + } +} + +func (m *roaManager) Enable(address string) error { + for network, client := range m.clientMap { + add, _, _ := net.SplitHostPort(network) + if add == address { + client.enable(client.serialNumber) + return nil + } + } + return fmt.Errorf("ROA server not found %s", address) +} + +func (m *roaManager) Disable(address string) error { + for network, client := range m.clientMap { + add, _, _ := net.SplitHostPort(network) + if add == address { + client.reset() + m.deleteAllROA(add) + return nil + } + } + return fmt.Errorf("ROA server not found %s", address) +} + +func (m *roaManager) Reset(address string) error { + return m.Disable(address) +} + +func (m *roaManager) SoftReset(address string) error { + for network, client := range m.clientMap { + add, _, _ := net.SplitHostPort(network) + if add == address { + client.softReset() + m.deleteAllROA(network) + return nil + } + } + return fmt.Errorf("ROA server not found %s", address) +} + +func (c *roaManager) ReceiveROA() chan *ROAEvent { + return c.eventCh +} + +func (c *roaClient) lifetimeout() { + c.eventCh <- &ROAEvent{ + EventType: LIFETIMEOUT, + Src: c.host, + } +} + +func (m *roaManager) HandleROAEvent(ev *ROAEvent) { + client, y := m.clientMap[ev.Src] + if !y { + if ev.EventType == CONNECTED { + ev.conn.Close() + } + log.WithFields(log.Fields{"Topic": "rpki"}).Errorf("Can't find %s ROA server configuration", ev.Src) + return + } + switch ev.EventType { + case DISCONNECTED: + log.WithFields(log.Fields{"Topic": "rpki"}).Infof("ROA server %s is disconnected", ev.Src) + client.state.Downtime = time.Now().Unix() + // clear state + client.endOfData = false + client.pendingROAs = make([]*table.ROA, 0) + client.state.RpkiMessages = config.RpkiMessages{} + client.conn = nil + go client.tryConnect() + client.timer = time.AfterFunc(time.Duration(client.lifetime)*time.Second, client.lifetimeout) + client.oldSessionID = client.sessionID + case CONNECTED: + log.WithFields(log.Fields{"Topic": "rpki"}).Infof("ROA server %s is connected", ev.Src) + client.conn = ev.conn + client.state.Uptime = time.Now().Unix() + go client.established() + case RTR: + m.handleRTRMsg(client, &client.state, ev.Data) + case LIFETIMEOUT: + // a) already reconnected but hasn't received + // EndOfData -> needs to delete stale ROAs + // b) not reconnected -> needs to delete stale ROAs + // + // c) already reconnected and received EndOfData so + // all stale ROAs were deleted -> timer was cancelled + // so should not be here. + if client.oldSessionID != client.sessionID { + log.WithFields(log.Fields{"Topic": "rpki"}).Infof("Reconnected to %s. Ignore timeout", client.host) + } else { + log.WithFields(log.Fields{"Topic": "rpki"}).Infof("Deleting all ROAs due to timeout with:%s", client.host) + m.deleteAllROA(client.host) + } + } +} + +func (m *roaManager) roa2tree(roa *table.ROA) (*radix.Tree, string) { + tree := m.Roas[bgp.RF_IPv4_UC] + if roa.Family == bgp.AFI_IP6 { + tree = m.Roas[bgp.RF_IPv6_UC] + } + return tree, table.IpToRadixkey(roa.Prefix.Prefix, roa.Prefix.Length) +} + +func (m *roaManager) deleteROA(roa *table.ROA) { + tree, key := m.roa2tree(roa) + b, _ := tree.Get(key) + if b != nil { + bucket := b.(*RoaBucket) + newEntries := make([]*table.ROA, 0, len(bucket.entries)) + for _, r := range bucket.entries { + if !r.Equal(roa) { + newEntries = append(newEntries, r) + } + } + if len(newEntries) != len(bucket.entries) { + bucket.entries = newEntries + if len(newEntries) == 0 { + tree.Delete(key) + } + return + } + } + log.WithFields(log.Fields{ + "Topic": "rpki", + "Prefix": roa.Prefix.Prefix.String(), + "Prefix Length": roa.Prefix.Length, + "AS": roa.AS, + "Max Length": roa.MaxLen, + }).Info("Can't withdraw a ROA") +} + +func (m *roaManager) DeleteROA(roa *table.ROA) { + m.deleteROA(roa) +} + +func (m *roaManager) addROA(roa *table.ROA) { + tree, key := m.roa2tree(roa) + b, _ := tree.Get(key) + var bucket *RoaBucket + if b == nil { + bucket = &RoaBucket{ + Prefix: roa.Prefix, + entries: make([]*table.ROA, 0), + } + tree.Insert(key, bucket) + } else { + bucket = b.(*RoaBucket) + for _, r := range bucket.entries { + if r.Equal(roa) { + // we already have the same one + return + } + } + } + bucket.entries = append(bucket.entries, roa) +} + +func (m *roaManager) AddROA(roa *table.ROA) { + m.addROA(roa) +} + +func (c *roaManager) handleRTRMsg(client *roaClient, state *config.RpkiServerState, buf []byte) { + received := &state.RpkiMessages.RpkiReceived + + m, err := rtr.ParseRTR(buf) + if err == nil { + switch msg := m.(type) { + case *rtr.RTRSerialNotify: + if before(client.serialNumber, msg.RTRCommon.SerialNumber) { + client.enable(client.serialNumber) + } else if client.serialNumber == msg.RTRCommon.SerialNumber { + // nothing + } else { + // should not happen. try to get the whole ROAs. + client.softReset() + } + received.SerialNotify++ + case *rtr.RTRSerialQuery: + case *rtr.RTRResetQuery: + case *rtr.RTRCacheResponse: + received.CacheResponse++ + client.endOfData = false + case *rtr.RTRIPPrefix: + family := bgp.AFI_IP + if msg.Type == rtr.RTR_IPV4_PREFIX { + received.Ipv4Prefix++ + } else { + family = bgp.AFI_IP6 + received.Ipv6Prefix++ + } + roa := table.NewROA(family, msg.Prefix, msg.PrefixLen, msg.MaxLen, msg.AS, client.host) + if (msg.Flags & 1) == 1 { + if client.endOfData { + c.addROA(roa) + } else { + client.pendingROAs = append(client.pendingROAs, roa) + } + } else { + c.deleteROA(roa) + } + case *rtr.RTREndOfData: + received.EndOfData++ + if client.sessionID != msg.RTRCommon.SessionID { + // remove all ROAs related with the + // previous session + c.deleteAllROA(client.host) + } + client.sessionID = msg.RTRCommon.SessionID + client.serialNumber = msg.RTRCommon.SerialNumber + client.endOfData = true + if client.timer != nil { + client.timer.Stop() + client.timer = nil + } + for _, roa := range client.pendingROAs { + c.addROA(roa) + } + client.pendingROAs = make([]*table.ROA, 0) + case *rtr.RTRCacheReset: + client.softReset() + received.CacheReset++ + case *rtr.RTRErrorReport: + received.Error++ + } + } else { + log.WithFields(log.Fields{ + "Topic": "rpki", + "Host": client.host, + "Error": err, + }).Info("Failed to parse an RTR message") + } +} + +func (c *roaManager) GetServers() []*config.RpkiServer { + f := func(tree *radix.Tree) (map[string]uint32, map[string]uint32) { + records := make(map[string]uint32) + prefixes := make(map[string]uint32) + + tree.Walk(func(s string, v interface{}) bool { + b, _ := v.(*RoaBucket) + tmpRecords := make(map[string]uint32) + for _, roa := range b.entries { + tmpRecords[roa.Src]++ + } + + for src, r := range tmpRecords { + if r > 0 { + records[src] += r + prefixes[src]++ + } + } + return false + }) + return records, prefixes + } + + recordsV4, prefixesV4 := f(c.Roas[bgp.RF_IPv4_UC]) + recordsV6, prefixesV6 := f(c.Roas[bgp.RF_IPv6_UC]) + + l := make([]*config.RpkiServer, 0, len(c.clientMap)) + for _, client := range c.clientMap { + state := &client.state + + if client.conn == nil { + state.Up = false + } else { + state.Up = true + } + f := func(m map[string]uint32, key string) uint32 { + if r, ok := m[key]; ok { + return r + } + return 0 + } + state.RecordsV4 = f(recordsV4, client.host) + state.RecordsV6 = f(recordsV6, client.host) + state.PrefixesV4 = f(prefixesV4, client.host) + state.PrefixesV6 = f(prefixesV6, client.host) + state.SerialNumber = client.serialNumber + + addr, port, _ := net.SplitHostPort(client.host) + l = append(l, &config.RpkiServer{ + Config: config.RpkiServerConfig{ + Address: addr, + // Note: RpkiServerConfig.Port is uint32 type, but the TCP/UDP + // port is 16-bit length. + Port: func() uint32 { p, _ := strconv.ParseUint(port, 10, 16); return uint32(p) }(), + }, + State: client.state, + }) + } + return l +} + +func (c *roaManager) GetRoa(family bgp.RouteFamily) ([]*table.ROA, error) { + if len(c.clientMap) == 0 { + return []*table.ROA{}, fmt.Errorf("RPKI server isn't configured.") + } + var rfList []bgp.RouteFamily + switch family { + case bgp.RF_IPv4_UC: + rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC} + case bgp.RF_IPv6_UC: + rfList = []bgp.RouteFamily{bgp.RF_IPv6_UC} + default: + rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC, bgp.RF_IPv6_UC} + } + l := make([]*table.ROA, 0) + for _, rf := range rfList { + if tree, ok := c.Roas[rf]; ok { + tree.Walk(func(s string, v interface{}) bool { + b, _ := v.(*RoaBucket) + var roaList roas + for _, r := range b.entries { + roaList = append(roaList, r) + } + sort.Sort(roaList) + for _, roa := range roaList { + l = append(l, roa) + } + return false + }) + } + } + return l, nil +} + +func ValidatePath(ownAs uint32, tree *radix.Tree, cidr string, asPath *bgp.PathAttributeAsPath) *table.Validation { + var as uint32 + + validation := &table.Validation{ + Status: config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND, + Reason: table.RPKI_VALIDATION_REASON_TYPE_NONE, + Matched: make([]*table.ROA, 0), + UnmatchedLength: make([]*table.ROA, 0), + UnmatchedAs: make([]*table.ROA, 0), + } + + if asPath == nil || len(asPath.Value) == 0 { + as = ownAs + } else { + param := asPath.Value[len(asPath.Value)-1] + switch param.GetType() { + case bgp.BGP_ASPATH_ATTR_TYPE_SEQ: + asList := param.GetAS() + if len(asList) == 0 { + as = ownAs + } else { + as = asList[len(asList)-1] + } + case bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET, bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SEQ: + as = ownAs + default: + return validation + } + } + _, n, _ := net.ParseCIDR(cidr) + ones, _ := n.Mask.Size() + prefixLen := uint8(ones) + key := table.IpToRadixkey(n.IP, prefixLen) + _, b, _ := tree.LongestPrefix(key) + if b == nil { + return validation + } + + var bucket *RoaBucket + fn := radix.WalkFn(func(k string, v interface{}) bool { + bucket, _ = v.(*RoaBucket) + for _, r := range bucket.entries { + if prefixLen <= r.MaxLen { + if r.AS != 0 && r.AS == as { + validation.Matched = append(validation.Matched, r) + } else { + validation.UnmatchedAs = append(validation.UnmatchedAs, r) + } + } else { + validation.UnmatchedLength = append(validation.UnmatchedLength, r) + } + } + return false + }) + tree.WalkPath(key, fn) + + if len(validation.Matched) != 0 { + validation.Status = config.RPKI_VALIDATION_RESULT_TYPE_VALID + validation.Reason = table.RPKI_VALIDATION_REASON_TYPE_NONE + } else if len(validation.UnmatchedAs) != 0 { + validation.Status = config.RPKI_VALIDATION_RESULT_TYPE_INVALID + validation.Reason = table.RPKI_VALIDATION_REASON_TYPE_AS + } else if len(validation.UnmatchedLength) != 0 { + validation.Status = config.RPKI_VALIDATION_RESULT_TYPE_INVALID + validation.Reason = table.RPKI_VALIDATION_REASON_TYPE_LENGTH + } else { + validation.Status = config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND + validation.Reason = table.RPKI_VALIDATION_REASON_TYPE_NONE + } + + return validation +} + +func (c *roaManager) validate(path *table.Path) *table.Validation { + if len(c.clientMap) == 0 || path.IsWithdraw || path.IsEOR() { + // RPKI isn't enabled or invalid path + return nil + } + if tree, ok := c.Roas[path.GetRouteFamily()]; ok { + return ValidatePath(c.AS, tree, path.GetNlri().String(), path.GetAsPath()) + } + return nil +} + +type roaClient struct { + host string + conn *net.TCPConn + state config.RpkiServerState + eventCh chan *ROAEvent + sessionID uint16 + oldSessionID uint16 + serialNumber uint32 + timer *time.Timer + lifetime int64 + endOfData bool + pendingROAs []*table.ROA + cancelfnc context.CancelFunc + ctx context.Context +} + +func NewRoaClient(address, port string, ch chan *ROAEvent, lifetime int64) *roaClient { + ctx, cancel := context.WithCancel(context.Background()) + c := &roaClient{ + host: net.JoinHostPort(address, port), + eventCh: ch, + lifetime: lifetime, + pendingROAs: make([]*table.ROA, 0), + ctx: ctx, + cancelfnc: cancel, + } + go c.tryConnect() + return c +} + +func (c *roaClient) enable(serial uint32) error { + if c.conn != nil { + r := rtr.NewRTRSerialQuery(c.sessionID, serial) + data, _ := r.Serialize() + _, err := c.conn.Write(data) + if err != nil { + return err + } + c.state.RpkiMessages.RpkiSent.SerialQuery++ + } + return nil +} + +func (c *roaClient) softReset() error { + if c.conn != nil { + r := rtr.NewRTRResetQuery() + data, _ := r.Serialize() + _, err := c.conn.Write(data) + if err != nil { + return err + } + c.state.RpkiMessages.RpkiSent.ResetQuery++ + c.endOfData = false + c.pendingROAs = make([]*table.ROA, 0) + } + return nil +} + +func (c *roaClient) reset() { + if c.conn != nil { + c.conn.Close() + } +} + +func (c *roaClient) stop() { + c.cancelfnc() + c.reset() +} + +func (c *roaClient) tryConnect() { + for { + select { + case <-c.ctx.Done(): + return + default: + } + if conn, err := net.Dial("tcp", c.host); err != nil { + // better to use context with timeout + time.Sleep(CONNECT_RETRY_INTERVAL * time.Second) + } else { + c.eventCh <- &ROAEvent{ + EventType: CONNECTED, + Src: c.host, + conn: conn.(*net.TCPConn), + } + return + } + } +} + +func (c *roaClient) established() (err error) { + defer func() { + c.conn.Close() + c.eventCh <- &ROAEvent{ + EventType: DISCONNECTED, + Src: c.host, + } + }() + + if err := c.softReset(); err != nil { + return err + } + + for { + header := make([]byte, rtr.RTR_MIN_LEN) + if _, err = io.ReadFull(c.conn, header); err != nil { + return err + } + totalLen := binary.BigEndian.Uint32(header[4:8]) + if totalLen < rtr.RTR_MIN_LEN { + return fmt.Errorf("too short header length %v", totalLen) + } + + body := make([]byte, totalLen-rtr.RTR_MIN_LEN) + if _, err = io.ReadFull(c.conn, body); err != nil { + return + } + + c.eventCh <- &ROAEvent{ + EventType: RTR, + Src: c.host, + Data: append(header, body...), + } + } +} diff --git a/pkg/server/rpki_test.go b/pkg/server/rpki_test.go new file mode 100644 index 00000000..0d77dbb2 --- /dev/null +++ b/pkg/server/rpki_test.go @@ -0,0 +1,257 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "net" + "strconv" + "strings" + "testing" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + + radix "github.com/armon/go-radix" + "github.com/stretchr/testify/assert" +) + +func strToASParam(str string) *bgp.PathAttributeAsPath { + toList := func(asstr, sep string) []uint32 { + as := make([]uint32, 0) + l := strings.Split(asstr, sep) + for _, s := range l { + v, _ := strconv.ParseUint(s, 10, 32) + as = append(as, uint32(v)) + } + return as + } + var atype uint8 + var as []uint32 + if strings.HasPrefix(str, "{") { + atype = bgp.BGP_ASPATH_ATTR_TYPE_SET + as = toList(str[1:len(str)-1], ",") + } else if strings.HasPrefix(str, "(") { + atype = bgp.BGP_ASPATH_ATTR_TYPE_CONFED_SET + as = toList(str[1:len(str)-1], " ") + } else { + atype = bgp.BGP_ASPATH_ATTR_TYPE_SEQ + as = toList(str, " ") + } + + return bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(atype, as)}) +} + +func validateOne(tree *radix.Tree, cidr, aspathStr string) config.RpkiValidationResultType { + r := ValidatePath(65500, tree, cidr, strToASParam(aspathStr)) + return r.Status +} + +func TestValidate0(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("192.168.0.0").To4(), 24, 32, 100, "")) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("192.168.0.0").To4(), 24, 24, 200, "")) + + var r config.RpkiValidationResultType + + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "192.168.0.0/24", "100") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "192.168.0.0/24", "100 200") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "192.168.0.0/24", "300") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) + + r = validateOne(tree, "192.168.0.0/25", "100") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "192.168.0.0/25", "200") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) + + r = validateOne(tree, "192.168.0.0/25", "300") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) +} + +func TestValidate1(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 16, 65000, "")) + + var r config.RpkiValidationResultType + + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/16", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "10.0.0.0/16", "65001") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) +} + +func TestValidate2(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + + var r config.RpkiValidationResultType + + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/16", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) + + r = validateOne(tree, "10.0.0.0/16", "65001") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) +} + +func TestValidate3(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 16, 65000, "")) + + var r config.RpkiValidationResultType + + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/8", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) + + r = validateOne(tree, "10.0.0.0/17", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) + + manager, _ = NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 24, 65000, "")) + + tree = manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/17", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) +} + +func TestValidate4(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 16, 65000, "")) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 16, 65001, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/16", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "10.0.0.0/16", "65001") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) +} + +func TestValidate5(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 17, 17, 65000, "")) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.128.0").To4(), 17, 17, 65000, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/16", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) +} + +func TestValidate6(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 8, 32, 0, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/7", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) + + r = validateOne(tree, "10.0.0.0/8", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) + + r = validateOne(tree, "10.0.0.0/24", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) +} + +func TestValidate7(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 24, 65000, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/24", "{65000}") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) + + r = validateOne(tree, "10.0.0.0/24", "{65001}") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) + + r = validateOne(tree, "10.0.0.0/24", "{65000,65001}") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_NOT_FOUND) +} + +func TestValidate8(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 24, 0, "")) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 24, 65000, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/24", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "10.0.0.0/24", "65001") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) +} + +func TestValidate9(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 24, 24, 65000, "")) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 24, 65001, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/24", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) + + r = validateOne(tree, "10.0.0.0/24", "65001") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) +} + +func TestValidate10(t *testing.T) { + assert := assert.New(t) + + manager, _ := NewROAManager(0) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 24, 24, 0, "")) + manager.addROA(table.NewROA(bgp.AFI_IP, net.ParseIP("10.0.0.0").To4(), 16, 24, 65001, "")) + + var r config.RpkiValidationResultType + tree := manager.Roas[bgp.RF_IPv4_UC] + r = validateOne(tree, "10.0.0.0/24", "65000") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_INVALID) + + r = validateOne(tree, "10.0.0.0/24", "65001") + assert.Equal(r, config.RPKI_VALIDATION_RESULT_TYPE_VALID) +} diff --git a/pkg/server/server.go b/pkg/server/server.go new file mode 100644 index 00000000..b00bb55d --- /dev/null +++ b/pkg/server/server.go @@ -0,0 +1,3048 @@ +// Copyright (C) 2014-2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "bytes" + "fmt" + "net" + "strconv" + "sync" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + + "github.com/eapache/channels" + uuid "github.com/satori/go.uuid" + log "github.com/sirupsen/logrus" +) + +type TCPListener struct { + l *net.TCPListener + ch chan struct{} +} + +func (l *TCPListener) Close() error { + if err := l.l.Close(); err != nil { + return err + } + t := time.NewTicker(time.Second) + select { + case <-l.ch: + case <-t.C: + return fmt.Errorf("close timeout") + } + return nil +} + +// avoid mapped IPv6 address +func NewTCPListener(address string, port uint32, ch chan *net.TCPConn) (*TCPListener, error) { + proto := "tcp4" + if ip := net.ParseIP(address); ip == nil { + return nil, fmt.Errorf("can't listen on %s", address) + } else if ip.To4() == nil { + proto = "tcp6" + } + addr, err := net.ResolveTCPAddr(proto, net.JoinHostPort(address, strconv.Itoa(int(port)))) + if err != nil { + return nil, err + } + + l, err := net.ListenTCP(proto, addr) + if err != nil { + return nil, err + } + // Note: Set TTL=255 for incoming connection listener in order to accept + // connection in case for the neighbor has TTL Security settings. + if err := SetListenTcpTTLSockopt(l, 255); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warnf("cannot set TTL(=%d) for TCPListener: %s", 255, err) + } + + closeCh := make(chan struct{}) + go func() error { + for { + conn, err := l.AcceptTCP() + if err != nil { + close(closeCh) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Error": err, + }).Warn("Failed to AcceptTCP") + return err + } + ch <- conn + } + }() + return &TCPListener{ + l: l, + ch: closeCh, + }, nil +} + +type BgpServer struct { + bgpConfig config.Bgp + fsmincomingCh *channels.InfiniteChannel + fsmStateCh chan *FsmMsg + acceptCh chan *net.TCPConn + + mgmtCh chan *mgmtOp + policy *table.RoutingPolicy + listeners []*TCPListener + neighborMap map[string]*Peer + peerGroupMap map[string]*PeerGroup + globalRib *table.TableManager + rsRib *table.TableManager + roaManager *roaManager + shutdownWG *sync.WaitGroup + watcherMap map[WatchEventType][]*Watcher + zclient *zebraClient + bmpManager *bmpClientManager + mrtManager *mrtManager + uuidMap map[uuid.UUID]string +} + +func NewBgpServer() *BgpServer { + roaManager, _ := NewROAManager(0) + s := &BgpServer{ + neighborMap: make(map[string]*Peer), + peerGroupMap: make(map[string]*PeerGroup), + policy: table.NewRoutingPolicy(), + roaManager: roaManager, + mgmtCh: make(chan *mgmtOp, 1), + watcherMap: make(map[WatchEventType][]*Watcher), + uuidMap: make(map[uuid.UUID]string), + } + s.bmpManager = newBmpClientManager(s) + s.mrtManager = newMrtManager(s) + return s +} + +func (server *BgpServer) Listeners(addr string) []*net.TCPListener { + list := make([]*net.TCPListener, 0, len(server.listeners)) + rhs := net.ParseIP(addr).To4() != nil + for _, l := range server.listeners { + host, _, _ := net.SplitHostPort(l.l.Addr().String()) + lhs := net.ParseIP(host).To4() != nil + if lhs == rhs { + list = append(list, l.l) + } + } + return list +} + +func (s *BgpServer) active() error { + if s.bgpConfig.Global.Config.As == 0 { + return fmt.Errorf("bgp server hasn't started yet") + } + return nil +} + +type mgmtOp struct { + f func() error + errCh chan error + checkActive bool // check BGP global setting is configured before calling f() +} + +func (server *BgpServer) handleMGMTOp(op *mgmtOp) { + if op.checkActive { + if err := server.active(); err != nil { + op.errCh <- err + return + } + } + op.errCh <- op.f() +} + +func (s *BgpServer) mgmtOperation(f func() error, checkActive bool) (err error) { + ch := make(chan error) + defer func() { err = <-ch }() + s.mgmtCh <- &mgmtOp{ + f: f, + errCh: ch, + checkActive: checkActive, + } + return +} + +func (server *BgpServer) Serve() { + server.listeners = make([]*TCPListener, 0, 2) + server.fsmincomingCh = channels.NewInfiniteChannel() + server.fsmStateCh = make(chan *FsmMsg, 4096) + + handleFsmMsg := func(e *FsmMsg) { + peer, found := server.neighborMap[e.MsgSrc] + if !found { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Warnf("Can't find the neighbor %s", e.MsgSrc) + return + } + if e.Version != peer.fsm.version { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Debug("FSM version inconsistent") + return + } + server.handleFSMMessage(peer, e) + } + + for { + passConn := func(conn *net.TCPConn) { + host, _, _ := net.SplitHostPort(conn.RemoteAddr().String()) + ipaddr, _ := net.ResolveIPAddr("ip", host) + remoteAddr := ipaddr.String() + peer, found := server.neighborMap[remoteAddr] + if found { + if peer.fsm.adminState != ADMIN_STATE_UP { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Remote Addr": remoteAddr, + "Admin State": peer.fsm.adminState, + }).Debug("New connection for non admin-state-up peer") + conn.Close() + return + } + localAddrValid := func(laddr string) bool { + if laddr == "0.0.0.0" || laddr == "::" { + return true + } + l := conn.LocalAddr() + if l == nil { + // already closed + return false + } + + host, _, _ := net.SplitHostPort(l.String()) + if host != laddr { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": remoteAddr, + "Configured addr": laddr, + "Addr": host, + }).Info("Mismatched local address") + return false + } + return true + }(peer.fsm.pConf.Transport.Config.LocalAddress) + + if !localAddrValid { + conn.Close() + return + } + + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Debugf("Accepted a new passive connection from:%s", remoteAddr) + peer.PassConn(conn) + } else if pg := server.matchLongestDynamicNeighborPrefix(remoteAddr); pg != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Debugf("Accepted a new dynamic neighbor from:%s", remoteAddr) + rib := server.globalRib + if pg.Conf.RouteServer.Config.RouteServerClient { + rib = server.rsRib + } + peer := newDynamicPeer(&server.bgpConfig.Global, remoteAddr, pg.Conf, rib, server.policy) + if peer == nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": remoteAddr, + }).Infof("Can't create new Dynamic Peer") + conn.Close() + return + } + server.policy.Reset(nil, map[string]config.ApplyPolicy{peer.ID(): peer.fsm.pConf.ApplyPolicy}) + server.neighborMap[remoteAddr] = peer + peer.startFSMHandler(server.fsmincomingCh, server.fsmStateCh) + server.broadcastPeerState(peer, bgp.BGP_FSM_ACTIVE, nil) + peer.PassConn(conn) + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Infof("Can't find configuration for a new passive connection from:%s", remoteAddr) + conn.Close() + } + } + + select { + case op := <-server.mgmtCh: + server.handleMGMTOp(op) + case conn := <-server.acceptCh: + passConn(conn) + default: + } + + for { + select { + case e := <-server.fsmStateCh: + handleFsmMsg(e) + default: + goto CONT + } + } + CONT: + + select { + case op := <-server.mgmtCh: + server.handleMGMTOp(op) + case rmsg := <-server.roaManager.ReceiveROA(): + server.roaManager.HandleROAEvent(rmsg) + case conn := <-server.acceptCh: + passConn(conn) + case e, ok := <-server.fsmincomingCh.Out(): + if !ok { + continue + } + handleFsmMsg(e.(*FsmMsg)) + case e := <-server.fsmStateCh: + handleFsmMsg(e) + } + } +} + +func (server *BgpServer) matchLongestDynamicNeighborPrefix(a string) *PeerGroup { + ipAddr := net.ParseIP(a) + longestMask := net.CIDRMask(0, 32).String() + var longestPG *PeerGroup + for _, pg := range server.peerGroupMap { + for _, d := range pg.dynamicNeighbors { + _, netAddr, _ := net.ParseCIDR(d.Config.Prefix) + if netAddr.Contains(ipAddr) { + if netAddr.Mask.String() > longestMask { + longestMask = netAddr.Mask.String() + longestPG = pg + } + } + } + } + return longestPG +} + +func sendFsmOutgoingMsg(peer *Peer, paths []*table.Path, notification *bgp.BGPMessage, stayIdle bool) { + peer.outgoing.In() <- &FsmOutgoingMsg{ + Paths: paths, + Notification: notification, + StayIdle: stayIdle, + } +} + +func isASLoop(peer *Peer, path *table.Path) bool { + for _, as := range path.GetAsList() { + if as == peer.AS() { + return true + } + } + return false +} + +func filterpath(peer *Peer, path, old *table.Path) *table.Path { + if path == nil { + return nil + } + if _, ok := peer.fsm.rfMap[path.GetRouteFamily()]; !ok { + return nil + } + + //RFC4684 Constrained Route Distribution + if _, y := peer.fsm.rfMap[bgp.RF_RTC_UC]; y && path.GetRouteFamily() != bgp.RF_RTC_UC { + ignore := true + for _, ext := range path.GetExtCommunities() { + for _, p := range peer.adjRibIn.PathList([]bgp.RouteFamily{bgp.RF_RTC_UC}, true) { + rt := p.GetNlri().(*bgp.RouteTargetMembershipNLRI).RouteTarget + // Note: nil RT means the default route target + if rt == nil || ext.String() == rt.String() { + ignore = false + break + } + } + if !ignore { + break + } + } + if ignore { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Data": path, + }).Debug("Filtered by Route Target Constraint, ignore") + return nil + } + } + + //iBGP handling + if peer.isIBGPPeer() { + ignore := false + if !path.IsLocal() { + ignore = true + info := path.GetSource() + //if the path comes from eBGP peer + if info.AS != peer.AS() { + ignore = false + } + // RFC4456 8. Avoiding Routing Information Loops + // A router that recognizes the ORIGINATOR_ID attribute SHOULD + // ignore a route received with its BGP Identifier as the ORIGINATOR_ID. + if id := path.GetOriginatorID(); peer.fsm.gConf.Config.RouterId == id.String() { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "OriginatorID": id, + "Data": path, + }).Debug("Originator ID is mine, ignore") + return nil + } + if info.RouteReflectorClient { + ignore = false + } + if peer.isRouteReflectorClient() { + // RFC4456 8. Avoiding Routing Information Loops + // If the local CLUSTER_ID is found in the CLUSTER_LIST, + // the advertisement received SHOULD be ignored. + for _, clusterID := range path.GetClusterList() { + if clusterID.Equal(peer.fsm.peerInfo.RouteReflectorClusterID) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "ClusterID": clusterID, + "Data": path, + }).Debug("cluster list path attribute has local cluster id, ignore") + return nil + } + } + ignore = false + } + } + + if ignore { + if !path.IsWithdraw && old != nil { + oldSource := old.GetSource() + if old.IsLocal() || oldSource.Address.String() != peer.ID() && oldSource.AS != peer.AS() { + // In this case, we suppose this peer has the same prefix + // received from another iBGP peer. + // So we withdraw the old best which was injected locally + // (from CLI or gRPC for example) in order to avoid the + // old best left on peers. + // Also, we withdraw the eBGP route which is the old best. + // When we got the new best from iBGP, we don't advertise + // the new best and need to withdraw the old best. + return old.Clone(true) + } + } + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Data": path, + }).Debug("From same AS, ignore.") + return nil + } + } + + if path = peer.filterPathFromSourcePeer(path, old); path == nil { + return nil + } + + if !peer.isRouteServerClient() && isASLoop(peer, path) { + return nil + } + return path +} + +func (s *BgpServer) filterpath(peer *Peer, path, old *table.Path) *table.Path { + // Special handling for RTM NLRI. + if path != nil && path.GetRouteFamily() == bgp.RF_RTC_UC && !path.IsWithdraw { + // If the given "path" is locally generated and the same with "old", we + // assumes "path" was already sent before. This assumption avoids the + // infinite UPDATE loop between Route Reflector and its clients. + if path.IsLocal() && path == old { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + "Path": path, + }).Debug("given rtm nlri is already sent, skipping to advertise") + return nil + } + + if old != nil && old.IsLocal() { + // We assumes VRF with the specific RT is deleted. + path = old.Clone(true) + } else if peer.isRouteReflectorClient() { + // We need to send the path even if the peer is originator of the + // path in order to signal that the client should distribute route + // with the given RT. + } else { + // We send a path even if it is not the best path. See comments in + // (*Destination) GetChanges(). + dst := peer.localRib.GetDestination(path) + path = nil + for _, p := range dst.GetKnownPathList(peer.TableID(), peer.AS()) { + srcPeer := p.GetSource() + if peer.ID() != srcPeer.Address.String() { + if srcPeer.RouteReflectorClient { + // The path from a RR client is preferred than others + // for the case that RR and non RR client peering + // (e.g., peering of different RR clusters). + path = p + break + } else if path == nil { + path = p + } + } + } + } + } + + // only allow vpnv4 and vpnv6 paths to be advertised to VRFed neighbors. + // also check we can import this path using table.CanImportToVrf() + // if we can, make it local path by calling (*Path).ToLocal() + if path != nil && peer.fsm.pConf.Config.Vrf != "" { + if f := path.GetRouteFamily(); f != bgp.RF_IPv4_VPN && f != bgp.RF_IPv6_VPN { + return nil + } + vrf := peer.localRib.Vrfs[peer.fsm.pConf.Config.Vrf] + if table.CanImportToVrf(vrf, path) { + path = path.ToLocal() + } else { + return nil + } + } + + // replace-peer-as handling + if path != nil && !path.IsWithdraw && peer.fsm.pConf.AsPathOptions.State.ReplacePeerAs { + path = path.ReplaceAS(peer.fsm.pConf.Config.LocalAs, peer.fsm.pConf.Config.PeerAs) + } + + if path = filterpath(peer, path, old); path == nil { + return nil + } + + options := &table.PolicyOptions{ + Info: peer.fsm.peerInfo, + OldNextHop: path.GetNexthop(), + } + path = table.UpdatePathAttrs(peer.fsm.gConf, peer.fsm.pConf, peer.fsm.peerInfo, path) + + if v := s.roaManager.validate(path); v != nil { + options.ValidationResult = v + } + + path = peer.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_EXPORT, path, options) + // When 'path' is filtered (path == nil), check 'old' has been sent to this peer. + // If it has, send withdrawal to the peer. + if path == nil && old != nil { + o := peer.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_EXPORT, old, options) + if o != nil { + path = old.Clone(true) + } + } + + // draft-uttaro-idr-bgp-persistence-02 + // 4.3. Processing LLGR_STALE Routes + // + // The route SHOULD NOT be advertised to any neighbor from which the + // Long-lived Graceful Restart Capability has not been received. The + // exception is described in the Optional Partial Deployment + // Procedure section (Section 4.7). Note that this requirement + // implies that such routes should be withdrawn from any such neighbor. + if path != nil && !path.IsWithdraw && !peer.isLLGREnabledFamily(path.GetRouteFamily()) && path.IsLLGRStale() { + // we send unnecessary withdrawn even if we didn't + // sent the route. + path = path.Clone(true) + } + + // remove local-pref attribute + // we should do this after applying export policy since policy may + // set local-preference + if path != nil && !peer.isIBGPPeer() && !peer.isRouteServerClient() { + path.RemoveLocalPref() + } + return path +} + +func clonePathList(pathList []*table.Path) []*table.Path { + l := make([]*table.Path, 0, len(pathList)) + for _, p := range pathList { + if p != nil { + l = append(l, p.Clone(p.IsWithdraw)) + } + } + return l +} + +func (server *BgpServer) notifyBestWatcher(best []*table.Path, multipath [][]*table.Path) { + if table.SelectionOptions.DisableBestPathSelection { + // Note: If best path selection disabled, no best path to notify. + return + } + clonedM := make([][]*table.Path, len(multipath)) + for i, pathList := range multipath { + clonedM[i] = clonePathList(pathList) + } + clonedB := clonePathList(best) + m := make(map[string]uint16) + for _, p := range clonedB { + switch p.GetRouteFamily() { + case bgp.RF_IPv4_VPN, bgp.RF_IPv6_VPN: + for _, vrf := range server.globalRib.Vrfs { + if vrf.Id != 0 && table.CanImportToVrf(vrf, p) { + m[p.GetNlri().String()] = uint16(vrf.Id) + } + } + } + } + w := &WatchEventBestPath{PathList: clonedB, MultiPathList: clonedM} + if len(m) > 0 { + w.Vrf = m + } + server.notifyWatcher(WATCH_EVENT_TYPE_BEST_PATH, w) +} + +func (s *BgpServer) ToConfig(peer *Peer, getAdvertised bool) *config.Neighbor { + // create copy which can be access to without mutex + conf := *peer.fsm.pConf + + conf.AfiSafis = make([]config.AfiSafi, len(peer.fsm.pConf.AfiSafis)) + for i, af := range peer.fsm.pConf.AfiSafis { + conf.AfiSafis[i] = af + conf.AfiSafis[i].AddPaths.State.Receive = peer.isAddPathReceiveEnabled(af.State.Family) + if peer.isAddPathSendEnabled(af.State.Family) { + conf.AfiSafis[i].AddPaths.State.SendMax = af.AddPaths.State.SendMax + } else { + conf.AfiSafis[i].AddPaths.State.SendMax = 0 + } + } + + remoteCap := make([]bgp.ParameterCapabilityInterface, 0, len(peer.fsm.capMap)) + for _, caps := range peer.fsm.capMap { + for _, m := range caps { + // need to copy all values here + buf, _ := m.Serialize() + c, _ := bgp.DecodeCapability(buf) + remoteCap = append(remoteCap, c) + } + } + conf.State.RemoteCapabilityList = remoteCap + conf.State.LocalCapabilityList = capabilitiesFromConfig(peer.fsm.pConf) + + conf.State.SessionState = config.IntToSessionStateMap[int(peer.fsm.state)] + conf.State.AdminState = config.IntToAdminStateMap[int(peer.fsm.adminState)] + + if peer.fsm.state == bgp.BGP_FSM_ESTABLISHED { + rfList := peer.configuredRFlist() + if getAdvertised { + pathList, filtered := s.getBestFromLocal(peer, rfList) + conf.State.AdjTable.Advertised = uint32(len(pathList)) + conf.State.AdjTable.Filtered = uint32(len(filtered)) + } else { + conf.State.AdjTable.Advertised = 0 + } + conf.State.AdjTable.Received = uint32(peer.adjRibIn.Count(rfList)) + conf.State.AdjTable.Accepted = uint32(peer.adjRibIn.Accepted(rfList)) + + conf.Transport.State.LocalAddress, conf.Transport.State.LocalPort = peer.fsm.LocalHostPort() + _, conf.Transport.State.RemotePort = peer.fsm.RemoteHostPort() + buf, _ := peer.fsm.recvOpen.Serialize() + // need to copy all values here + conf.State.ReceivedOpenMessage, _ = bgp.ParseBGPMessage(buf) + conf.State.RemoteRouterId = peer.fsm.peerInfo.ID.To4().String() + } + return &conf +} + +func (server *BgpServer) notifyPrePolicyUpdateWatcher(peer *Peer, pathList []*table.Path, msg *bgp.BGPMessage, timestamp time.Time, payload []byte) { + if !server.isWatched(WATCH_EVENT_TYPE_PRE_UPDATE) || peer == nil { + return + } + cloned := clonePathList(pathList) + if len(cloned) == 0 { + return + } + _, y := peer.fsm.capMap[bgp.BGP_CAP_FOUR_OCTET_AS_NUMBER] + l, _ := peer.fsm.LocalHostPort() + ev := &WatchEventUpdate{ + Message: msg, + PeerAS: peer.fsm.peerInfo.AS, + LocalAS: peer.fsm.peerInfo.LocalAS, + PeerAddress: peer.fsm.peerInfo.Address, + LocalAddress: net.ParseIP(l), + PeerID: peer.fsm.peerInfo.ID, + FourBytesAs: y, + Timestamp: timestamp, + Payload: payload, + PostPolicy: false, + PathList: cloned, + Neighbor: server.ToConfig(peer, false), + } + server.notifyWatcher(WATCH_EVENT_TYPE_PRE_UPDATE, ev) +} + +func (server *BgpServer) notifyPostPolicyUpdateWatcher(peer *Peer, pathList []*table.Path) { + if !server.isWatched(WATCH_EVENT_TYPE_POST_UPDATE) || peer == nil { + return + } + cloned := clonePathList(pathList) + if len(cloned) == 0 { + return + } + _, y := peer.fsm.capMap[bgp.BGP_CAP_FOUR_OCTET_AS_NUMBER] + l, _ := peer.fsm.LocalHostPort() + ev := &WatchEventUpdate{ + PeerAS: peer.fsm.peerInfo.AS, + LocalAS: peer.fsm.peerInfo.LocalAS, + PeerAddress: peer.fsm.peerInfo.Address, + LocalAddress: net.ParseIP(l), + PeerID: peer.fsm.peerInfo.ID, + FourBytesAs: y, + Timestamp: cloned[0].GetTimestamp(), + PostPolicy: true, + PathList: cloned, + Neighbor: server.ToConfig(peer, false), + } + server.notifyWatcher(WATCH_EVENT_TYPE_POST_UPDATE, ev) +} + +func newWatchEventPeerState(peer *Peer, m *FsmMsg) *WatchEventPeerState { + _, rport := peer.fsm.RemoteHostPort() + laddr, lport := peer.fsm.LocalHostPort() + sentOpen := buildopen(peer.fsm.gConf, peer.fsm.pConf) + recvOpen := peer.fsm.recvOpen + e := &WatchEventPeerState{ + PeerAS: peer.fsm.peerInfo.AS, + LocalAS: peer.fsm.peerInfo.LocalAS, + PeerAddress: peer.fsm.peerInfo.Address, + LocalAddress: net.ParseIP(laddr), + PeerPort: rport, + LocalPort: lport, + PeerID: peer.fsm.peerInfo.ID, + SentOpen: sentOpen, + RecvOpen: recvOpen, + State: peer.fsm.state, + AdminState: peer.fsm.adminState, + Timestamp: time.Now(), + PeerInterface: peer.fsm.pConf.Config.NeighborInterface, + } + + if m != nil { + e.StateReason = m.StateReason + } + return e +} + +func (server *BgpServer) broadcastPeerState(peer *Peer, oldState bgp.FSMState, e *FsmMsg) { + newState := peer.fsm.state + if oldState == bgp.BGP_FSM_ESTABLISHED || newState == bgp.BGP_FSM_ESTABLISHED { + server.notifyWatcher(WATCH_EVENT_TYPE_PEER_STATE, newWatchEventPeerState(peer, e)) + } +} + +func (server *BgpServer) notifyMessageWatcher(peer *Peer, timestamp time.Time, msg *bgp.BGPMessage, isSent bool) { + // validation should be done in the caller of this function + _, y := peer.fsm.capMap[bgp.BGP_CAP_FOUR_OCTET_AS_NUMBER] + l, _ := peer.fsm.LocalHostPort() + ev := &WatchEventMessage{ + Message: msg, + PeerAS: peer.fsm.peerInfo.AS, + LocalAS: peer.fsm.peerInfo.LocalAS, + PeerAddress: peer.fsm.peerInfo.Address, + LocalAddress: net.ParseIP(l), + PeerID: peer.fsm.peerInfo.ID, + FourBytesAs: y, + Timestamp: timestamp, + IsSent: isSent, + } + if !isSent { + server.notifyWatcher(WATCH_EVENT_TYPE_RECV_MSG, ev) + } +} + +func (server *BgpServer) notifyRecvMessageWatcher(peer *Peer, timestamp time.Time, msg *bgp.BGPMessage) { + if peer == nil || !server.isWatched(WATCH_EVENT_TYPE_RECV_MSG) { + return + } + server.notifyMessageWatcher(peer, timestamp, msg, false) +} + +func (s *BgpServer) getBestFromLocal(peer *Peer, rfList []bgp.RouteFamily) ([]*table.Path, []*table.Path) { + pathList := []*table.Path{} + filtered := []*table.Path{} + for _, family := range peer.toGlobalFamilies(rfList) { + pl := func() []*table.Path { + if peer.isAddPathSendEnabled(family) { + return peer.localRib.GetPathList(peer.TableID(), peer.AS(), []bgp.RouteFamily{family}) + } + return peer.localRib.GetBestPathList(peer.TableID(), peer.AS(), []bgp.RouteFamily{family}) + }() + for _, path := range pl { + if p := s.filterpath(peer, path, nil); p != nil { + pathList = append(pathList, p) + } else { + filtered = append(filtered, path) + } + } + } + if peer.isGracefulRestartEnabled() { + for _, family := range rfList { + pathList = append(pathList, table.NewEOR(family)) + } + } + return pathList, filtered +} + +func (s *BgpServer) processOutgoingPaths(peer *Peer, paths, olds []*table.Path) []*table.Path { + if peer.fsm.state != bgp.BGP_FSM_ESTABLISHED { + return nil + } + if peer.fsm.pConf.GracefulRestart.State.LocalRestarting { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + }).Debug("now syncing, suppress sending updates") + return nil + } + + outgoing := make([]*table.Path, 0, len(paths)) + + for idx, path := range paths { + var old *table.Path + if olds != nil { + old = olds[idx] + } + if p := s.filterpath(peer, path, old); p != nil { + outgoing = append(outgoing, p) + } + } + return outgoing +} + +func (s *BgpServer) handleRouteRefresh(peer *Peer, e *FsmMsg) []*table.Path { + m := e.MsgData.(*bgp.BGPMessage) + rr := m.Body.(*bgp.BGPRouteRefresh) + rf := bgp.AfiSafiToRouteFamily(rr.AFI, rr.SAFI) + if _, ok := peer.fsm.rfMap[rf]; !ok { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Data": rf, + }).Warn("Route family isn't supported") + return nil + } + if _, ok := peer.fsm.capMap[bgp.BGP_CAP_ROUTE_REFRESH]; !ok { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Warn("ROUTE_REFRESH received but the capability wasn't advertised") + return nil + } + rfList := []bgp.RouteFamily{rf} + accepted, filtered := s.getBestFromLocal(peer, rfList) + for _, path := range filtered { + path.IsWithdraw = true + accepted = append(accepted, path) + } + return accepted +} + +func (server *BgpServer) propagateUpdate(peer *Peer, pathList []*table.Path) { + rs := peer != nil && peer.isRouteServerClient() + vrf := !rs && peer != nil && peer.fsm.pConf.Config.Vrf != "" + + tableId := table.GLOBAL_RIB_NAME + rib := server.globalRib + if rs { + tableId = peer.TableID() + rib = server.rsRib + } + + for _, path := range pathList { + if vrf { + path = path.ToGlobal(rib.Vrfs[peer.fsm.pConf.Config.Vrf]) + } + + policyOptions := &table.PolicyOptions{} + + if !rs && peer != nil { + policyOptions.Info = peer.fsm.peerInfo + } + if v := server.roaManager.validate(path); v != nil { + policyOptions.ValidationResult = v + } + + if p := server.policy.ApplyPolicy(tableId, table.POLICY_DIRECTION_IMPORT, path, policyOptions); p != nil { + path = p + } else { + path = path.Clone(true) + } + + if !rs { + server.notifyPostPolicyUpdateWatcher(peer, []*table.Path{path}) + + // RFC4684 Constrained Route Distribution 6. Operation + // + // When a BGP speaker receives a BGP UPDATE that advertises or withdraws + // a given Route Target membership NLRI, it should examine the RIB-OUTs + // of VPN NLRIs and re-evaluate the advertisement status of routes that + // match the Route Target in question. + // + // A BGP speaker should generate the minimum set of BGP VPN route + // updates (advertisements and/or withdraws) necessary to transition + // between the previous and current state of the route distribution + // graph that is derived from Route Target membership information. + if peer != nil && path != nil && path.GetRouteFamily() == bgp.RF_RTC_UC { + rt := path.GetNlri().(*bgp.RouteTargetMembershipNLRI).RouteTarget + fs := make([]bgp.RouteFamily, 0, len(peer.negotiatedRFList())) + for _, f := range peer.negotiatedRFList() { + if f != bgp.RF_RTC_UC { + fs = append(fs, f) + } + } + var candidates []*table.Path + if path.IsWithdraw { + // Note: The paths to be withdrawn are filtered because the + // given RT on RTM NLRI is already removed from adj-RIB-in. + _, candidates = server.getBestFromLocal(peer, fs) + } else { + candidates = server.globalRib.GetBestPathList(peer.TableID(), 0, fs) + } + paths := make([]*table.Path, 0, len(candidates)) + for _, p := range candidates { + for _, ext := range p.GetExtCommunities() { + if rt == nil || ext.String() == rt.String() { + if path.IsWithdraw { + p = p.Clone(true) + } + paths = append(paths, p) + break + } + } + } + if path.IsWithdraw { + // Skips filtering because the paths are already filtered + // and the withdrawal does not need the path attributes. + } else { + paths = server.processOutgoingPaths(peer, paths, nil) + } + sendFsmOutgoingMsg(peer, paths, nil, false) + } + } + + if dsts := rib.Update(path); len(dsts) > 0 { + server.propagateUpdateToNeighbors(peer, path, dsts, true) + } + } +} + +func (server *BgpServer) dropPeerAllRoutes(peer *Peer, families []bgp.RouteFamily) { + rib := server.globalRib + if peer.isRouteServerClient() { + rib = server.rsRib + } + for _, family := range peer.toGlobalFamilies(families) { + for _, path := range rib.GetPathListByPeer(peer.fsm.peerInfo, family) { + p := path.Clone(true) + if dsts := rib.Update(p); len(dsts) > 0 { + server.propagateUpdateToNeighbors(peer, p, dsts, false) + } + } + } +} + +func dstsToPaths(id string, as uint32, dsts []*table.Update) ([]*table.Path, []*table.Path, [][]*table.Path) { + bestList := make([]*table.Path, 0, len(dsts)) + oldList := make([]*table.Path, 0, len(dsts)) + mpathList := make([][]*table.Path, 0, len(dsts)) + + for _, dst := range dsts { + best, old, mpath := dst.GetChanges(id, as, false) + bestList = append(bestList, best) + oldList = append(oldList, old) + if mpath != nil { + mpathList = append(mpathList, mpath) + } + } + return bestList, oldList, mpathList +} + +func (server *BgpServer) propagateUpdateToNeighbors(source *Peer, newPath *table.Path, dsts []*table.Update, needOld bool) { + if table.SelectionOptions.DisableBestPathSelection { + return + } + var gBestList, gOldList, bestList, oldList []*table.Path + var mpathList [][]*table.Path + if source == nil || !source.isRouteServerClient() { + gBestList, gOldList, mpathList = dstsToPaths(table.GLOBAL_RIB_NAME, 0, dsts) + server.notifyBestWatcher(gBestList, mpathList) + } + family := newPath.GetRouteFamily() + for _, targetPeer := range server.neighborMap { + if (source == nil && targetPeer.isRouteServerClient()) || (source != nil && source.isRouteServerClient() != targetPeer.isRouteServerClient()) { + continue + } + f := func() bgp.RouteFamily { + if targetPeer.fsm.pConf.Config.Vrf != "" { + switch family { + case bgp.RF_IPv4_VPN: + return bgp.RF_IPv4_UC + case bgp.RF_IPv6_VPN: + return bgp.RF_IPv6_UC + } + } + return family + }() + if targetPeer.isAddPathSendEnabled(f) { + if newPath.IsWithdraw { + bestList = func() []*table.Path { + l := make([]*table.Path, 0, len(dsts)) + for _, d := range dsts { + l = append(l, d.GetWithdrawnPath()...) + } + return l + }() + } else { + bestList = []*table.Path{newPath} + if newPath.GetRouteFamily() == bgp.RF_RTC_UC { + // we assumes that new "path" nlri was already sent before. This assumption avoids the + // infinite UPDATE loop between Route Reflector and its clients. + for _, old := range dsts[0].OldKnownPathList { + if old.IsLocal() { + bestList = []*table.Path{} + break + } + } + } + } + oldList = nil + } else if targetPeer.isRouteServerClient() { + bestList, oldList, _ = dstsToPaths(targetPeer.TableID(), targetPeer.AS(), dsts) + } else { + bestList = gBestList + oldList = gOldList + } + if !needOld { + oldList = nil + } + if paths := server.processOutgoingPaths(targetPeer, bestList, oldList); len(paths) > 0 { + sendFsmOutgoingMsg(targetPeer, paths, nil, false) + } + } +} + +func (server *BgpServer) handleFSMMessage(peer *Peer, e *FsmMsg) { + switch e.MsgType { + case FSM_MSG_STATE_CHANGE: + nextState := e.MsgData.(bgp.FSMState) + oldState := bgp.FSMState(peer.fsm.pConf.State.SessionState.ToInt()) + peer.fsm.pConf.State.SessionState = config.IntToSessionStateMap[int(nextState)] + peer.fsm.StateChange(nextState) + + // PeerDown + if oldState == bgp.BGP_FSM_ESTABLISHED { + t := time.Now() + if t.Sub(time.Unix(peer.fsm.pConf.Timers.State.Uptime, 0)) < FLOP_THRESHOLD { + peer.fsm.pConf.State.Flops++ + } + var drop []bgp.RouteFamily + if peer.fsm.reason.Type == FSM_GRACEFUL_RESTART { + peer.fsm.pConf.GracefulRestart.State.PeerRestarting = true + var p []bgp.RouteFamily + p, drop = peer.forwardingPreservedFamilies() + server.propagateUpdate(peer, peer.StaleAll(p)) + } else { + drop = peer.configuredRFlist() + } + peer.prefixLimitWarned = make(map[bgp.RouteFamily]bool) + peer.DropAll(drop) + server.dropPeerAllRoutes(peer, drop) + if peer.fsm.pConf.Config.PeerAs == 0 { + peer.fsm.pConf.State.PeerAs = 0 + peer.fsm.peerInfo.AS = 0 + } + if peer.isDynamicNeighbor() { + peer.stopPeerRestarting() + go peer.stopFSM() + delete(server.neighborMap, peer.fsm.pConf.State.NeighborAddress) + server.broadcastPeerState(peer, oldState, e) + return + } + } else if peer.fsm.pConf.GracefulRestart.State.PeerRestarting && nextState == bgp.BGP_FSM_IDLE { + if peer.fsm.pConf.GracefulRestart.State.LongLivedEnabled { + llgr, no_llgr := peer.llgrFamilies() + + peer.DropAll(no_llgr) + server.dropPeerAllRoutes(peer, no_llgr) + + // attach LLGR_STALE community to paths in peer's adj-rib-in + // paths with NO_LLGR are deleted + pathList := peer.markLLGRStale(llgr) + + // calculate again + // wheh path with LLGR_STALE chosen as best, + // peer which doesn't support LLGR will drop the path + // if it is in adj-rib-out, do withdrawal + server.propagateUpdate(peer, pathList) + + for _, f := range llgr { + endCh := make(chan struct{}) + peer.llgrEndChs = append(peer.llgrEndChs, endCh) + go func(family bgp.RouteFamily, endCh chan struct{}) { + t := peer.llgrRestartTime(family) + timer := time.NewTimer(time.Second * time.Duration(t)) + + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Family": family, + }).Debugf("start LLGR restart timer (%d sec) for %s", t, family) + + select { + case <-timer.C: + server.mgmtOperation(func() error { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Family": family, + }).Debugf("LLGR restart timer (%d sec) for %s expired", t, family) + peer.DropAll([]bgp.RouteFamily{family}) + server.dropPeerAllRoutes(peer, []bgp.RouteFamily{family}) + + // when all llgr restart timer expired, stop PeerRestarting + if peer.llgrRestartTimerExpired(family) { + peer.stopPeerRestarting() + } + return nil + }, false) + case <-endCh: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Family": family, + }).Debugf("stop LLGR restart timer (%d sec) for %s", t, family) + } + }(f, endCh) + } + } else { + // RFC 4724 4.2 + // If the session does not get re-established within the "Restart Time" + // that the peer advertised previously, the Receiving Speaker MUST + // delete all the stale routes from the peer that it is retaining. + peer.fsm.pConf.GracefulRestart.State.PeerRestarting = false + peer.DropAll(peer.configuredRFlist()) + server.dropPeerAllRoutes(peer, peer.configuredRFlist()) + } + } + + cleanInfiniteChannel(peer.outgoing) + peer.outgoing = channels.NewInfiniteChannel() + if nextState == bgp.BGP_FSM_ESTABLISHED { + // update for export policy + laddr, _ := peer.fsm.LocalHostPort() + // may include zone info + peer.fsm.pConf.Transport.State.LocalAddress = laddr + // exclude zone info + ipaddr, _ := net.ResolveIPAddr("ip", laddr) + peer.fsm.peerInfo.LocalAddress = ipaddr.IP + deferralExpiredFunc := func(family bgp.RouteFamily) func() { + return func() { + server.mgmtOperation(func() error { + server.softResetOut(peer.fsm.pConf.State.NeighborAddress, family, true) + return nil + }, false) + } + } + if !peer.fsm.pConf.GracefulRestart.State.LocalRestarting { + // When graceful-restart cap (which means intention + // of sending EOR) and route-target address family are negotiated, + // send route-target NLRIs first, and wait to send others + // till receiving EOR of route-target address family. + // This prevents sending uninterested routes to peers. + // + // However, when the peer is graceful restarting, give up + // waiting sending non-route-target NLRIs since the peer won't send + // any routes (and EORs) before we send ours (or deferral-timer expires). + var pathList []*table.Path + _, y := peer.fsm.rfMap[bgp.RF_RTC_UC] + if c := peer.fsm.pConf.GetAfiSafi(bgp.RF_RTC_UC); y && !peer.fsm.pConf.GracefulRestart.State.PeerRestarting && c.RouteTargetMembership.Config.DeferralTime > 0 { + pathList, _ = server.getBestFromLocal(peer, []bgp.RouteFamily{bgp.RF_RTC_UC}) + t := c.RouteTargetMembership.Config.DeferralTime + for _, f := range peer.negotiatedRFList() { + if f != bgp.RF_RTC_UC { + time.AfterFunc(time.Second*time.Duration(t), deferralExpiredFunc(f)) + } + } + } else { + pathList, _ = server.getBestFromLocal(peer, peer.negotiatedRFList()) + } + + if len(pathList) > 0 { + sendFsmOutgoingMsg(peer, pathList, nil, false) + } + } else { + // RFC 4724 4.1 + // Once the session between the Restarting Speaker and the Receiving + // Speaker is re-established, ...snip... it MUST defer route + // selection for an address family until it either (a) receives the + // End-of-RIB marker from all its peers (excluding the ones with the + // "Restart State" bit set in the received capability and excluding the + // ones that do not advertise the graceful restart capability) or (b) + // the Selection_Deferral_Timer referred to below has expired. + allEnd := func() bool { + for _, p := range server.neighborMap { + if !p.recvedAllEOR() { + return false + } + } + return true + }() + if allEnd { + for _, p := range server.neighborMap { + p.fsm.pConf.GracefulRestart.State.LocalRestarting = false + if !p.isGracefulRestartEnabled() { + continue + } + paths, _ := server.getBestFromLocal(p, p.configuredRFlist()) + if len(paths) > 0 { + sendFsmOutgoingMsg(p, paths, nil, false) + } + } + log.WithFields(log.Fields{ + "Topic": "Server", + }).Info("sync finished") + } else { + deferral := peer.fsm.pConf.GracefulRestart.Config.DeferralTime + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Debugf("Now syncing, suppress sending updates. start deferral timer(%d)", deferral) + time.AfterFunc(time.Second*time.Duration(deferral), deferralExpiredFunc(bgp.RouteFamily(0))) + } + } + } else { + if server.shutdownWG != nil && nextState == bgp.BGP_FSM_IDLE { + die := true + for _, p := range server.neighborMap { + if p.fsm.state != bgp.BGP_FSM_IDLE { + die = false + break + } + } + if die { + server.shutdownWG.Done() + } + } + peer.fsm.pConf.Timers.State.Downtime = time.Now().Unix() + } + // clear counter + if peer.fsm.adminState == ADMIN_STATE_DOWN { + peer.fsm.pConf.State = config.NeighborState{} + peer.fsm.pConf.State.NeighborAddress = peer.fsm.pConf.Config.NeighborAddress + peer.fsm.pConf.State.PeerAs = peer.fsm.pConf.Config.PeerAs + peer.fsm.pConf.Timers.State = config.TimersState{} + } + peer.startFSMHandler(server.fsmincomingCh, server.fsmStateCh) + server.broadcastPeerState(peer, oldState, e) + case FSM_MSG_ROUTE_REFRESH: + if peer.fsm.state != bgp.BGP_FSM_ESTABLISHED || e.timestamp.Unix() < peer.fsm.pConf.Timers.State.Uptime { + return + } + if paths := server.handleRouteRefresh(peer, e); len(paths) > 0 { + sendFsmOutgoingMsg(peer, paths, nil, false) + return + } + case FSM_MSG_BGP_MESSAGE: + switch m := e.MsgData.(type) { + case *bgp.MessageError: + sendFsmOutgoingMsg(peer, nil, bgp.NewBGPNotificationMessage(m.TypeCode, m.SubTypeCode, m.Data), false) + return + case *bgp.BGPMessage: + server.notifyRecvMessageWatcher(peer, e.timestamp, m) + if peer.fsm.state != bgp.BGP_FSM_ESTABLISHED || e.timestamp.Unix() < peer.fsm.pConf.Timers.State.Uptime { + return + } + pathList, eor, notification := peer.handleUpdate(e) + if notification != nil { + sendFsmOutgoingMsg(peer, nil, notification, true) + return + } + if m.Header.Type == bgp.BGP_MSG_UPDATE { + server.notifyPrePolicyUpdateWatcher(peer, pathList, m, e.timestamp, e.payload) + } + + if len(pathList) > 0 { + server.propagateUpdate(peer, pathList) + } + + if len(eor) > 0 { + rtc := false + for _, f := range eor { + if f == bgp.RF_RTC_UC { + rtc = true + } + for i, a := range peer.fsm.pConf.AfiSafis { + if a.State.Family == f { + peer.fsm.pConf.AfiSafis[i].MpGracefulRestart.State.EndOfRibReceived = true + } + } + } + + // RFC 4724 4.1 + // Once the session between the Restarting Speaker and the Receiving + // Speaker is re-established, ...snip... it MUST defer route + // selection for an address family until it either (a) receives the + // End-of-RIB marker from all its peers (excluding the ones with the + // "Restart State" bit set in the received capability and excluding the + // ones that do not advertise the graceful restart capability) or ...snip... + if peer.fsm.pConf.GracefulRestart.State.LocalRestarting { + allEnd := func() bool { + for _, p := range server.neighborMap { + if !p.recvedAllEOR() { + return false + } + } + return true + }() + if allEnd { + for _, p := range server.neighborMap { + p.fsm.pConf.GracefulRestart.State.LocalRestarting = false + if !p.isGracefulRestartEnabled() { + continue + } + paths, _ := server.getBestFromLocal(p, p.negotiatedRFList()) + if len(paths) > 0 { + sendFsmOutgoingMsg(p, paths, nil, false) + } + } + log.WithFields(log.Fields{ + "Topic": "Server", + }).Info("sync finished") + + } + + // we don't delay non-route-target NLRIs when local-restarting + rtc = false + } + if peer.fsm.pConf.GracefulRestart.State.PeerRestarting { + if peer.recvedAllEOR() { + peer.stopPeerRestarting() + pathList := peer.adjRibIn.DropStale(peer.configuredRFlist()) + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + }).Debugf("withdraw %d stale routes", len(pathList)) + server.propagateUpdate(peer, pathList) + } + + // we don't delay non-route-target NLRIs when peer is restarting + rtc = false + } + + // received EOR of route-target address family + // outbound filter is now ready, let's flash non-route-target NLRIs + if c := peer.fsm.pConf.GetAfiSafi(bgp.RF_RTC_UC); rtc && c != nil && c.RouteTargetMembership.Config.DeferralTime > 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Debug("received route-target eor. flash non-route-target NLRIs") + families := make([]bgp.RouteFamily, 0, len(peer.negotiatedRFList())) + for _, f := range peer.negotiatedRFList() { + if f != bgp.RF_RTC_UC { + families = append(families, f) + } + } + if paths, _ := server.getBestFromLocal(peer, families); len(paths) > 0 { + sendFsmOutgoingMsg(peer, paths, nil, false) + } + } + } + default: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + "Data": e.MsgData, + }).Panic("unknown msg type") + } + } +} + +func (s *BgpServer) AddCollector(c *config.CollectorConfig) error { + return s.mgmtOperation(func() error { + _, err := NewCollector(s, c.Url, c.DbName, c.TableDumpInterval) + return err + }, false) +} + +func (s *BgpServer) StartZebraClient(c *config.ZebraConfig) error { + return s.mgmtOperation(func() error { + if s.zclient != nil { + return fmt.Errorf("already connected to Zebra") + } + protos := make([]string, 0, len(c.RedistributeRouteTypeList)) + for _, p := range c.RedistributeRouteTypeList { + protos = append(protos, string(p)) + } + var err error + s.zclient, err = newZebraClient(s, c.Url, protos, c.Version, c.NexthopTriggerEnable, c.NexthopTriggerDelay) + return err + }, false) +} + +func (s *BgpServer) AddBmp(c *config.BmpServerConfig) error { + return s.mgmtOperation(func() error { + return s.bmpManager.addServer(c) + }, true) +} + +func (s *BgpServer) DeleteBmp(c *config.BmpServerConfig) error { + return s.mgmtOperation(func() error { + return s.bmpManager.deleteServer(c) + }, true) +} + +func (s *BgpServer) Shutdown() { + s.mgmtOperation(func() error { + s.shutdownWG = new(sync.WaitGroup) + s.shutdownWG.Add(1) + stateOp := AdminStateOperation{ + State: ADMIN_STATE_DOWN, + Communication: nil, + } + for _, p := range s.neighborMap { + p.fsm.adminStateCh <- stateOp + } + // TODO: call fsmincomingCh.Close() + return nil + }, false) + + // Waits for all goroutines per peer to stop. + // Note: This should not be wrapped with s.mgmtOperation() in order to + // avoid the deadlock in the main goroutine of BgpServer. + if s.shutdownWG != nil { + s.shutdownWG.Wait() + s.shutdownWG = nil + } +} + +func (s *BgpServer) UpdatePolicy(policy config.RoutingPolicy) error { + return s.mgmtOperation(func() error { + ap := make(map[string]config.ApplyPolicy, len(s.neighborMap)+1) + ap[table.GLOBAL_RIB_NAME] = s.bgpConfig.Global.ApplyPolicy + for _, peer := range s.neighborMap { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + }).Info("call set policy") + ap[peer.ID()] = peer.fsm.pConf.ApplyPolicy + } + return s.policy.Reset(&policy, ap) + }, false) +} + +// EVPN MAC MOBILITY HANDLING +// +// We don't have multihoming function now, so ignore +// ESI comparison. +// +// RFC7432 15. MAC Mobility +// +// A PE detecting a locally attached MAC address for which it had +// previously received a MAC/IP Advertisement route with the same zero +// Ethernet segment identifier (single-homed scenarios) advertises it +// with a MAC Mobility extended community attribute with the sequence +// number set properly. In the case of single-homed scenarios, there +// is no need for ESI comparison. + +func getMacMobilityExtendedCommunity(etag uint32, mac net.HardwareAddr, evpnPaths []*table.Path) *bgp.MacMobilityExtended { + seqs := make([]struct { + seq int + isLocal bool + }, 0) + + for _, path := range evpnPaths { + nlri := path.GetNlri().(*bgp.EVPNNLRI) + target, ok := nlri.RouteTypeData.(*bgp.EVPNMacIPAdvertisementRoute) + if !ok { + continue + } + if target.ETag == etag && bytes.Equal(target.MacAddress, mac) { + found := false + for _, ec := range path.GetExtCommunities() { + if t, st := ec.GetTypes(); t == bgp.EC_TYPE_EVPN && st == bgp.EC_SUBTYPE_MAC_MOBILITY { + seqs = append(seqs, struct { + seq int + isLocal bool + }{int(ec.(*bgp.MacMobilityExtended).Sequence), path.IsLocal()}) + found = true + break + } + } + + if !found { + seqs = append(seqs, struct { + seq int + isLocal bool + }{-1, path.IsLocal()}) + } + } + } + + if len(seqs) > 0 { + newSeq := -2 + var isLocal bool + for _, seq := range seqs { + if seq.seq > newSeq { + newSeq = seq.seq + isLocal = seq.isLocal + } + } + + if !isLocal { + newSeq += 1 + } + + if newSeq != -1 { + return &bgp.MacMobilityExtended{ + Sequence: uint32(newSeq), + } + } + } + return nil +} + +func (server *BgpServer) fixupApiPath(vrfId string, pathList []*table.Path) error { + pi := &table.PeerInfo{ + AS: server.bgpConfig.Global.Config.As, + LocalID: net.ParseIP(server.bgpConfig.Global.Config.RouterId).To4(), + } + + for _, path := range pathList { + if !path.IsWithdraw { + if _, err := path.GetOrigin(); err != nil { + return err + } + } + + if path.GetSource() == nil { + path.SetSource(pi) + } + + if vrfId != "" { + vrf := server.globalRib.Vrfs[vrfId] + if vrf == nil { + return fmt.Errorf("vrf %s not found", vrfId) + } + if err := vrf.ToGlobalPath(path); err != nil { + return err + } + } + + // Address Family specific Handling + switch nlri := path.GetNlri().(type) { + case *bgp.EVPNNLRI: + switch r := nlri.RouteTypeData.(type) { + case *bgp.EVPNMacIPAdvertisementRoute: + // MAC Mobility Extended Community + paths := server.globalRib.GetBestPathList(table.GLOBAL_RIB_NAME, 0, []bgp.RouteFamily{bgp.RF_EVPN}) + if m := getMacMobilityExtendedCommunity(r.ETag, r.MacAddress, paths); m != nil { + path.SetExtCommunities([]bgp.ExtendedCommunityInterface{m}, false) + } + case *bgp.EVPNEthernetSegmentRoute: + // RFC7432: BGP MPLS-Based Ethernet VPN + // 7.6. ES-Import Route Target + // The value is derived automatically for the ESI Types 1, 2, + // and 3, by encoding the high-order 6-octet portion of the 9-octet ESI + // Value, which corresponds to a MAC address, in the ES-Import Route + // Target. + // Note: If the given path already has the ES-Import Route Target, + // skips deriving a new one. + found := false + for _, extComm := range path.GetExtCommunities() { + if _, found = extComm.(*bgp.ESImportRouteTarget); found { + break + } + } + if !found { + switch r.ESI.Type { + case bgp.ESI_LACP, bgp.ESI_MSTP, bgp.ESI_MAC: + mac := net.HardwareAddr(r.ESI.Value[0:6]) + rt := &bgp.ESImportRouteTarget{ESImport: mac} + path.SetExtCommunities([]bgp.ExtendedCommunityInterface{rt}, false) + } + } + } + } + } + return nil +} + +func pathTokey(path *table.Path) string { + return fmt.Sprintf("%d:%s", path.GetNlri().PathIdentifier(), path.GetNlri().String()) +} + +func (s *BgpServer) AddPath(vrfId string, pathList []*table.Path) (uuidBytes []byte, err error) { + err = s.mgmtOperation(func() error { + if err := s.fixupApiPath(vrfId, pathList); err != nil { + return err + } + if len(pathList) == 1 { + path := pathList[0] + id, _ := uuid.NewV4() + s.uuidMap[id] = pathTokey(path) + uuidBytes = id.Bytes() + } + s.propagateUpdate(nil, pathList) + return nil + }, true) + return +} + +func (s *BgpServer) DeletePath(uuidBytes []byte, f bgp.RouteFamily, vrfId string, pathList []*table.Path) error { + return s.mgmtOperation(func() error { + deletePathList := make([]*table.Path, 0) + if len(uuidBytes) > 0 { + // Delete locally generated path which has the given UUID + path := func() *table.Path { + id, _ := uuid.FromBytes(uuidBytes) + if key, ok := s.uuidMap[id]; !ok { + return nil + } else { + for _, path := range s.globalRib.GetPathList(table.GLOBAL_RIB_NAME, 0, s.globalRib.GetRFlist()) { + if path.IsLocal() && key == pathTokey(path) { + delete(s.uuidMap, id) + return path + } + } + } + return nil + }() + if path == nil { + return fmt.Errorf("Can't find a specified path") + } + deletePathList = append(deletePathList, path.Clone(true)) + } else if len(pathList) == 0 { + // Delete all locally generated paths + families := s.globalRib.GetRFlist() + if f != 0 { + families = []bgp.RouteFamily{f} + } + for _, path := range s.globalRib.GetPathList(table.GLOBAL_RIB_NAME, 0, families) { + if path.IsLocal() { + deletePathList = append(deletePathList, path.Clone(true)) + } + } + s.uuidMap = make(map[uuid.UUID]string) + } else { + if err := s.fixupApiPath(vrfId, pathList); err != nil { + return err + } + deletePathList = pathList + } + s.propagateUpdate(nil, deletePathList) + return nil + }, true) +} + +func (s *BgpServer) UpdatePath(vrfId string, pathList []*table.Path) error { + err := s.mgmtOperation(func() error { + if err := s.fixupApiPath(vrfId, pathList); err != nil { + return err + } + s.propagateUpdate(nil, pathList) + return nil + }, true) + return err +} + +func (s *BgpServer) Start(c *config.Global) error { + return s.mgmtOperation(func() error { + if err := config.SetDefaultGlobalConfigValues(c); err != nil { + return err + } + + if c.Config.Port > 0 { + acceptCh := make(chan *net.TCPConn, 4096) + for _, addr := range c.Config.LocalAddressList { + l, err := NewTCPListener(addr, uint32(c.Config.Port), acceptCh) + if err != nil { + return err + } + s.listeners = append(s.listeners, l) + } + s.acceptCh = acceptCh + } + + rfs, _ := config.AfiSafis(c.AfiSafis).ToRfList() + s.globalRib = table.NewTableManager(rfs) + s.rsRib = table.NewTableManager(rfs) + + if err := s.policy.Reset(&config.RoutingPolicy{}, map[string]config.ApplyPolicy{}); err != nil { + return err + } + s.bgpConfig.Global = *c + // update route selection options + table.SelectionOptions = c.RouteSelectionOptions.Config + table.UseMultiplePaths = c.UseMultiplePaths.Config + + s.roaManager.SetAS(s.bgpConfig.Global.Config.As) + return nil + }, false) +} + +func (s *BgpServer) GetVrf() (l []*table.Vrf) { + s.mgmtOperation(func() error { + l = make([]*table.Vrf, 0, len(s.globalRib.Vrfs)) + for _, vrf := range s.globalRib.Vrfs { + l = append(l, vrf.Clone()) + } + return nil + }, true) + return l +} + +func (s *BgpServer) AddVrf(name string, id uint32, rd bgp.RouteDistinguisherInterface, im, ex []bgp.ExtendedCommunityInterface) error { + return s.mgmtOperation(func() error { + pi := &table.PeerInfo{ + AS: s.bgpConfig.Global.Config.As, + LocalID: net.ParseIP(s.bgpConfig.Global.Config.RouterId).To4(), + } + if pathList, err := s.globalRib.AddVrf(name, id, rd, im, ex, pi); err != nil { + return err + } else if len(pathList) > 0 { + s.propagateUpdate(nil, pathList) + } + return nil + }, true) +} + +func (s *BgpServer) DeleteVrf(name string) error { + return s.mgmtOperation(func() error { + for _, n := range s.neighborMap { + if n.fsm.pConf.Config.Vrf == name { + return fmt.Errorf("failed to delete VRF %s: neighbor %s is in use", name, n.ID()) + } + } + pathList, err := s.globalRib.DeleteVrf(name) + if err != nil { + return err + } + if len(pathList) > 0 { + s.propagateUpdate(nil, pathList) + } + return nil + }, true) +} + +func (s *BgpServer) Stop() error { + return s.mgmtOperation(func() error { + for k, _ := range s.neighborMap { + if err := s.deleteNeighbor(&config.Neighbor{Config: config.NeighborConfig{ + NeighborAddress: k}}, bgp.BGP_ERROR_CEASE, bgp.BGP_ERROR_SUB_PEER_DECONFIGURED); err != nil { + return err + } + } + for _, l := range s.listeners { + l.Close() + } + s.bgpConfig.Global = config.Global{} + return nil + }, true) +} + +func familiesForSoftreset(peer *Peer, family bgp.RouteFamily) []bgp.RouteFamily { + if family == bgp.RouteFamily(0) { + configured := peer.configuredRFlist() + families := make([]bgp.RouteFamily, 0, len(configured)) + for _, f := range configured { + if f != bgp.RF_RTC_UC { + families = append(families, f) + } + } + return families + } + return []bgp.RouteFamily{family} +} + +func (s *BgpServer) softResetIn(addr string, family bgp.RouteFamily) error { + peers, err := s.addrToPeers(addr) + if err != nil { + return err + } + for _, peer := range peers { + families := familiesForSoftreset(peer, family) + + pathList := make([]*table.Path, 0, peer.adjRibIn.Count(families)) + for _, path := range peer.adjRibIn.PathList(families, false) { + // RFC4271 9.1.2 Phase 2: Route Selection + // + // If the AS_PATH attribute of a BGP route contains an AS loop, the BGP + // route should be excluded from the Phase 2 decision function. + isLooped := false + if aspath := path.GetAsPath(); aspath != nil { + isLooped = hasOwnASLoop(peer.fsm.peerInfo.LocalAS, int(peer.fsm.pConf.AsPathOptions.Config.AllowOwnAs), aspath) + } + if path.IsAsLooped() != isLooped { + // can't modify the existing one. needs to create one + path = path.Clone(false) + path.SetAsLooped(isLooped) + // update accepted counter + peer.adjRibIn.Update([]*table.Path{path}) + } + if !path.IsAsLooped() { + pathList = append(pathList, path) + } + } + s.propagateUpdate(peer, pathList) + } + return err +} + +func (s *BgpServer) softResetOut(addr string, family bgp.RouteFamily, deferral bool) error { + peers, err := s.addrToPeers(addr) + if err != nil { + return err + } + for _, peer := range peers { + if peer.fsm.state != bgp.BGP_FSM_ESTABLISHED { + continue + } + families := familiesForSoftreset(peer, family) + + if deferral { + _, y := peer.fsm.rfMap[bgp.RF_RTC_UC] + if peer.fsm.pConf.GracefulRestart.State.LocalRestarting { + peer.fsm.pConf.GracefulRestart.State.LocalRestarting = false + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Families": families, + }).Debug("deferral timer expired") + } else if c := peer.fsm.pConf.GetAfiSafi(bgp.RF_RTC_UC); y && !c.MpGracefulRestart.State.EndOfRibReceived { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "Families": families, + }).Debug("route-target deferral timer expired") + } else { + continue + } + } + + pathList, filtered := s.getBestFromLocal(peer, families) + if len(pathList) > 0 { + sendFsmOutgoingMsg(peer, pathList, nil, false) + } + if !deferral && len(filtered) > 0 { + withdrawnList := make([]*table.Path, 0, len(filtered)) + for _, p := range filtered { + withdrawnList = append(withdrawnList, p.Clone(true)) + } + sendFsmOutgoingMsg(peer, withdrawnList, nil, false) + } + } + return nil +} + +func (s *BgpServer) SoftResetIn(addr string, family bgp.RouteFamily) error { + return s.mgmtOperation(func() error { + log.WithFields(log.Fields{ + "Topic": "Operation", + "Key": addr, + }).Info("Neighbor soft reset in") + return s.softResetIn(addr, family) + }, true) +} + +func (s *BgpServer) SoftResetOut(addr string, family bgp.RouteFamily) error { + return s.mgmtOperation(func() error { + log.WithFields(log.Fields{ + "Topic": "Operation", + "Key": addr, + }).Info("Neighbor soft reset out") + return s.softResetOut(addr, family, false) + }, true) +} + +func (s *BgpServer) SoftReset(addr string, family bgp.RouteFamily) error { + return s.mgmtOperation(func() error { + log.WithFields(log.Fields{ + "Topic": "Operation", + "Key": addr, + }).Info("Neighbor soft reset") + err := s.softResetIn(addr, family) + if err != nil { + return err + } + return s.softResetOut(addr, family, false) + }, true) +} + +func (s *BgpServer) validateTable(r *table.Table) (v []*table.Validation) { + if s.roaManager.enabled() { + v = make([]*table.Validation, 0, len(r.GetDestinations())) + for _, d := range r.GetDestinations() { + for _, p := range d.GetAllKnownPathList() { + v = append(v, s.roaManager.validate(p)) + } + } + } + return +} + +func (s *BgpServer) GetRib(addr string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (rib *table.Table, v []*table.Validation, err error) { + err = s.mgmtOperation(func() error { + m := s.globalRib + id := table.GLOBAL_RIB_NAME + as := uint32(0) + if len(addr) > 0 { + peer, ok := s.neighborMap[addr] + if !ok { + return fmt.Errorf("Neighbor that has %v doesn't exist.", addr) + } + if !peer.isRouteServerClient() { + return fmt.Errorf("Neighbor %v doesn't have local rib", addr) + } + id = peer.ID() + as = peer.AS() + m = s.rsRib + } + af := bgp.RouteFamily(family) + tbl, ok := m.Tables[af] + if !ok { + return fmt.Errorf("address family: %s not supported", af) + } + rib, err = tbl.Select(table.TableSelectOption{ID: id, AS: as, LookupPrefixes: prefixes}) + v = s.validateTable(rib) + return err + }, true) + return +} + +func (s *BgpServer) GetVrfRib(name string, family bgp.RouteFamily, prefixes []*table.LookupPrefix) (rib *table.Table, err error) { + err = s.mgmtOperation(func() error { + m := s.globalRib + vrfs := m.Vrfs + if _, ok := vrfs[name]; !ok { + return fmt.Errorf("vrf %s not found", name) + } + var af bgp.RouteFamily + switch family { + case bgp.RF_IPv4_UC: + af = bgp.RF_IPv4_VPN + case bgp.RF_IPv6_UC: + af = bgp.RF_IPv6_VPN + case bgp.RF_EVPN: + af = bgp.RF_EVPN + } + tbl, ok := m.Tables[af] + if !ok { + return fmt.Errorf("address family: %s not supported", af) + } + rib, err = tbl.Select(table.TableSelectOption{VRF: vrfs[name], LookupPrefixes: prefixes}) + return err + }, true) + return +} + +func (s *BgpServer) GetAdjRib(addr string, family bgp.RouteFamily, in bool, prefixes []*table.LookupPrefix) (rib *table.Table, v []*table.Validation, err error) { + err = s.mgmtOperation(func() error { + peer, ok := s.neighborMap[addr] + if !ok { + return fmt.Errorf("Neighbor that has %v doesn't exist.", addr) + } + id := peer.ID() + as := peer.AS() + + var adjRib *table.AdjRib + if in { + adjRib = peer.adjRibIn + } else { + adjRib = table.NewAdjRib(peer.configuredRFlist()) + accepted, _ := s.getBestFromLocal(peer, peer.configuredRFlist()) + adjRib.Update(accepted) + } + rib, err = adjRib.Select(family, false, table.TableSelectOption{ID: id, AS: as, LookupPrefixes: prefixes}) + v = s.validateTable(rib) + return err + }, true) + return +} + +func (s *BgpServer) GetRibInfo(addr string, family bgp.RouteFamily) (info *table.TableInfo, err error) { + err = s.mgmtOperation(func() error { + m := s.globalRib + id := table.GLOBAL_RIB_NAME + as := uint32(0) + if len(addr) > 0 { + peer, ok := s.neighborMap[addr] + if !ok { + return fmt.Errorf("Neighbor that has %v doesn't exist.", addr) + } + if !peer.isRouteServerClient() { + return fmt.Errorf("Neighbor %v doesn't have local rib", addr) + } + id = peer.ID() + as = peer.AS() + m = s.rsRib + } + info, err = m.TableInfo(id, as, family) + return err + }, true) + return +} + +func (s *BgpServer) GetAdjRibInfo(addr string, family bgp.RouteFamily, in bool) (info *table.TableInfo, err error) { + err = s.mgmtOperation(func() error { + peer, ok := s.neighborMap[addr] + if !ok { + return fmt.Errorf("Neighbor that has %v doesn't exist.", addr) + } + + var adjRib *table.AdjRib + if in { + adjRib = peer.adjRibIn + } else { + adjRib = table.NewAdjRib(peer.configuredRFlist()) + accepted, _ := s.getBestFromLocal(peer, peer.configuredRFlist()) + adjRib.Update(accepted) + } + info, err = adjRib.TableInfo(family) + return err + }, true) + return +} + +func (s *BgpServer) GetServer() (c *config.Global) { + s.mgmtOperation(func() error { + g := s.bgpConfig.Global + c = &g + return nil + }, false) + return c +} + +func (s *BgpServer) GetNeighbor(address string, getAdvertised bool) (l []*config.Neighbor) { + s.mgmtOperation(func() error { + l = make([]*config.Neighbor, 0, len(s.neighborMap)) + for k, peer := range s.neighborMap { + if address != "" && address != k && address != peer.fsm.pConf.Config.NeighborInterface { + continue + } + l = append(l, s.ToConfig(peer, getAdvertised)) + } + return nil + }, false) + return l +} + +func (server *BgpServer) addPeerGroup(c *config.PeerGroup) error { + name := c.Config.PeerGroupName + if _, y := server.peerGroupMap[name]; y { + return fmt.Errorf("Can't overwrite the existing peer-group: %s", name) + } + + log.WithFields(log.Fields{ + "Topic": "Peer", + "Name": name, + }).Info("Add a peer group configuration") + + server.peerGroupMap[c.Config.PeerGroupName] = NewPeerGroup(c) + + return nil +} + +func (server *BgpServer) addNeighbor(c *config.Neighbor) error { + addr, err := c.ExtractNeighborAddress() + if err != nil { + return err + } + + if _, y := server.neighborMap[addr]; y { + return fmt.Errorf("Can't overwrite the existing peer: %s", addr) + } + + var pgConf *config.PeerGroup + if c.Config.PeerGroup != "" { + pg, ok := server.peerGroupMap[c.Config.PeerGroup] + if !ok { + return fmt.Errorf("no such peer-group: %s", c.Config.PeerGroup) + } + pgConf = pg.Conf + } + + if err := config.SetDefaultNeighborConfigValues(c, pgConf, &server.bgpConfig.Global); err != nil { + return err + } + + if vrf := c.Config.Vrf; vrf != "" { + if c.RouteServer.Config.RouteServerClient { + return fmt.Errorf("route server client can't be enslaved to VRF") + } + families, _ := config.AfiSafis(c.AfiSafis).ToRfList() + for _, f := range families { + if f != bgp.RF_IPv4_UC && f != bgp.RF_IPv6_UC { + return fmt.Errorf("%s is not supported for VRF enslaved neighbor", f) + } + } + _, y := server.globalRib.Vrfs[vrf] + if !y { + return fmt.Errorf("VRF not found: %s", vrf) + } + } + + if c.RouteServer.Config.RouteServerClient && c.RouteReflector.Config.RouteReflectorClient { + return fmt.Errorf("can't be both route-server-client and route-reflector-client") + } + + if server.bgpConfig.Global.Config.Port > 0 { + for _, l := range server.Listeners(addr) { + if c.Config.AuthPassword != "" { + if err := SetTcpMD5SigSockopt(l, addr, c.Config.AuthPassword); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warnf("failed to set md5: %s", err) + } + } + } + } + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Infof("Add a peer configuration for:%s", addr) + + rib := server.globalRib + if c.RouteServer.Config.RouteServerClient { + rib = server.rsRib + } + peer := NewPeer(&server.bgpConfig.Global, c, rib, server.policy) + server.policy.Reset(nil, map[string]config.ApplyPolicy{peer.ID(): c.ApplyPolicy}) + server.neighborMap[addr] = peer + if name := c.Config.PeerGroup; name != "" { + server.peerGroupMap[name].AddMember(*c) + } + peer.startFSMHandler(server.fsmincomingCh, server.fsmStateCh) + server.broadcastPeerState(peer, bgp.BGP_FSM_IDLE, nil) + return nil +} + +func (s *BgpServer) AddPeerGroup(c *config.PeerGroup) error { + return s.mgmtOperation(func() error { + return s.addPeerGroup(c) + }, true) +} + +func (s *BgpServer) AddNeighbor(c *config.Neighbor) error { + return s.mgmtOperation(func() error { + return s.addNeighbor(c) + }, true) +} + +func (s *BgpServer) AddDynamicNeighbor(c *config.DynamicNeighbor) error { + return s.mgmtOperation(func() error { + s.peerGroupMap[c.Config.PeerGroup].AddDynamicNeighbor(c) + return nil + }, true) +} + +func (server *BgpServer) deletePeerGroup(pg *config.PeerGroup) error { + name := pg.Config.PeerGroupName + + if _, y := server.peerGroupMap[name]; !y { + return fmt.Errorf("Can't delete a peer-group %s which does not exist", name) + } + + log.WithFields(log.Fields{ + "Topic": "Peer", + "Name": name, + }).Info("Delete a peer group configuration") + + delete(server.peerGroupMap, name) + return nil +} + +func (server *BgpServer) deleteNeighbor(c *config.Neighbor, code, subcode uint8) error { + if c.Config.PeerGroup != "" { + _, y := server.peerGroupMap[c.Config.PeerGroup] + if y { + server.peerGroupMap[c.Config.PeerGroup].DeleteMember(*c) + } + } + + addr, err := c.ExtractNeighborAddress() + if err != nil { + return err + } + + if intf := c.Config.NeighborInterface; intf != "" { + var err error + addr, err = config.GetIPv6LinkLocalNeighborAddress(intf) + if err != nil { + return err + } + } + n, y := server.neighborMap[addr] + if !y { + return fmt.Errorf("Can't delete a peer configuration for %s", addr) + } + for _, l := range server.Listeners(addr) { + if err := SetTcpMD5SigSockopt(l, addr, ""); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warnf("failed to unset md5: %s", err) + } + } + log.WithFields(log.Fields{ + "Topic": "Peer", + }).Infof("Delete a peer configuration for:%s", addr) + + n.fsm.sendNotification(code, subcode, nil, "") + n.stopPeerRestarting() + + go n.stopFSM() + delete(server.neighborMap, addr) + server.dropPeerAllRoutes(n, n.configuredRFlist()) + return nil +} + +func (s *BgpServer) DeletePeerGroup(c *config.PeerGroup) error { + return s.mgmtOperation(func() error { + name := c.Config.PeerGroupName + for _, n := range s.neighborMap { + if n.fsm.pConf.Config.PeerGroup == name { + return fmt.Errorf("failed to delete peer-group %s: neighbor %s is in use", name, n.ID()) + } + } + return s.deletePeerGroup(c) + }, true) +} + +func (s *BgpServer) DeleteNeighbor(c *config.Neighbor) error { + return s.mgmtOperation(func() error { + return s.deleteNeighbor(c, bgp.BGP_ERROR_CEASE, bgp.BGP_ERROR_SUB_PEER_DECONFIGURED) + }, true) +} + +func (s *BgpServer) updatePeerGroup(pg *config.PeerGroup) (needsSoftResetIn bool, err error) { + name := pg.Config.PeerGroupName + + _, ok := s.peerGroupMap[name] + if !ok { + return false, fmt.Errorf("Peer-group %s doesn't exist.", name) + } + s.peerGroupMap[name].Conf = pg + + for _, n := range s.peerGroupMap[name].members { + c := n + u, err := s.updateNeighbor(&c) + if err != nil { + return needsSoftResetIn, err + } + needsSoftResetIn = needsSoftResetIn || u + } + return needsSoftResetIn, nil +} + +func (s *BgpServer) UpdatePeerGroup(pg *config.PeerGroup) (needsSoftResetIn bool, err error) { + err = s.mgmtOperation(func() error { + needsSoftResetIn, err = s.updatePeerGroup(pg) + return err + }, true) + return needsSoftResetIn, err +} + +func (s *BgpServer) updateNeighbor(c *config.Neighbor) (needsSoftResetIn bool, err error) { + if c.Config.PeerGroup != "" { + if pg, ok := s.peerGroupMap[c.Config.PeerGroup]; ok { + if err := config.SetDefaultNeighborConfigValues(c, pg.Conf, &s.bgpConfig.Global); err != nil { + return needsSoftResetIn, err + } + } else { + return needsSoftResetIn, fmt.Errorf("no such peer-group: %s", c.Config.PeerGroup) + } + } + + addr, err := c.ExtractNeighborAddress() + if err != nil { + return needsSoftResetIn, err + } + + peer, ok := s.neighborMap[addr] + if !ok { + return needsSoftResetIn, fmt.Errorf("Neighbor that has %v doesn't exist.", addr) + } + + if !peer.fsm.pConf.ApplyPolicy.Equal(&c.ApplyPolicy) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Info("Update ApplyPolicy") + s.policy.Reset(nil, map[string]config.ApplyPolicy{peer.ID(): c.ApplyPolicy}) + peer.fsm.pConf.ApplyPolicy = c.ApplyPolicy + needsSoftResetIn = true + } + original := peer.fsm.pConf + + if !original.AsPathOptions.Config.Equal(&c.AsPathOptions.Config) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Info("Update aspath options") + peer.fsm.pConf.AsPathOptions = c.AsPathOptions + needsSoftResetIn = true + } + + if original.NeedsResendOpenMessage(c) { + sub := uint8(bgp.BGP_ERROR_SUB_OTHER_CONFIGURATION_CHANGE) + if original.Config.AdminDown != c.Config.AdminDown { + sub = bgp.BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN + state := "Admin Down" + + if !c.Config.AdminDown { + state = "Admin Up" + } + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + "State": state, + }).Info("Update admin-state configuration") + } else if original.Config.PeerAs != c.Config.PeerAs { + sub = bgp.BGP_ERROR_SUB_PEER_DECONFIGURED + } + if err = s.deleteNeighbor(peer.fsm.pConf, bgp.BGP_ERROR_CEASE, sub); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Error(err) + return needsSoftResetIn, err + } + err = s.addNeighbor(c) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Error(err) + } + return needsSoftResetIn, err + } + + if !original.Timers.Config.Equal(&c.Timers.Config) { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.ID(), + }).Info("Update timer configuration") + peer.fsm.pConf.Timers.Config = c.Timers.Config + } + + err = peer.updatePrefixLimitConfig(c.AfiSafis) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Error(err) + // rollback to original state + peer.fsm.pConf = original + } + return needsSoftResetIn, err +} + +func (s *BgpServer) UpdateNeighbor(c *config.Neighbor) (needsSoftResetIn bool, err error) { + err = s.mgmtOperation(func() error { + needsSoftResetIn, err = s.updateNeighbor(c) + return err + }, true) + return needsSoftResetIn, err +} + +func (s *BgpServer) addrToPeers(addr string) (l []*Peer, err error) { + if len(addr) == 0 { + for _, p := range s.neighborMap { + l = append(l, p) + } + return l, nil + } + peer, found := s.neighborMap[addr] + if !found { + return l, fmt.Errorf("Neighbor that has %v doesn't exist.", addr) + } + return []*Peer{peer}, nil +} + +func (s *BgpServer) resetNeighbor(op, addr string, subcode uint8, data []byte) error { + log.WithFields(log.Fields{ + "Topic": "Operation", + "Key": addr, + }).Info(op) + + peers, err := s.addrToPeers(addr) + if err == nil { + m := bgp.NewBGPNotificationMessage(bgp.BGP_ERROR_CEASE, subcode, data) + for _, peer := range peers { + sendFsmOutgoingMsg(peer, nil, m, false) + } + } + return err +} + +func (s *BgpServer) ShutdownNeighbor(addr, communication string) error { + return s.mgmtOperation(func() error { + return s.resetNeighbor("Neighbor shutdown", addr, bgp.BGP_ERROR_SUB_ADMINISTRATIVE_SHUTDOWN, newAdministrativeCommunication(communication)) + }, true) +} + +func (s *BgpServer) ResetNeighbor(addr, communication string) error { + return s.mgmtOperation(func() error { + err := s.resetNeighbor("Neighbor reset", addr, bgp.BGP_ERROR_SUB_ADMINISTRATIVE_RESET, newAdministrativeCommunication(communication)) + if err != nil { + return err + } + peers, _ := s.addrToPeers(addr) + for _, peer := range peers { + peer.fsm.idleHoldTime = peer.fsm.pConf.Timers.Config.IdleHoldTimeAfterReset + } + return nil + }, true) +} + +func (s *BgpServer) setAdminState(addr, communication string, enable bool) error { + peers, err := s.addrToPeers(addr) + if err != nil { + return err + } + for _, peer := range peers { + f := func(stateOp *AdminStateOperation, message string) { + select { + case peer.fsm.adminStateCh <- *stateOp: + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": peer.fsm.pConf.State.NeighborAddress, + }).Debug(message) + default: + log.Warning("previous request is still remaining. : ", peer.fsm.pConf.State.NeighborAddress) + } + } + if enable { + f(&AdminStateOperation{ADMIN_STATE_UP, nil}, "ADMIN_STATE_UP requested") + } else { + f(&AdminStateOperation{ADMIN_STATE_DOWN, newAdministrativeCommunication(communication)}, "ADMIN_STATE_DOWN requested") + } + } + return nil +} + +func (s *BgpServer) EnableNeighbor(addr string) error { + return s.mgmtOperation(func() error { + return s.setAdminState(addr, "", true) + }, true) +} + +func (s *BgpServer) DisableNeighbor(addr, communication string) error { + return s.mgmtOperation(func() error { + return s.setAdminState(addr, communication, false) + }, true) +} + +func (s *BgpServer) GetDefinedSet(typ table.DefinedType, name string) (sets *config.DefinedSets, err error) { + err = s.mgmtOperation(func() error { + sets, err = s.policy.GetDefinedSet(typ, name) + return nil + }, false) + return sets, err +} + +func (s *BgpServer) AddDefinedSet(a table.DefinedSet) error { + return s.mgmtOperation(func() error { + return s.policy.AddDefinedSet(a) + }, false) +} + +func (s *BgpServer) DeleteDefinedSet(a table.DefinedSet, all bool) error { + return s.mgmtOperation(func() error { + return s.policy.DeleteDefinedSet(a, all) + }, false) +} + +func (s *BgpServer) ReplaceDefinedSet(a table.DefinedSet) error { + return s.mgmtOperation(func() error { + return s.policy.ReplaceDefinedSet(a) + }, false) +} + +func (s *BgpServer) GetStatement() (l []*config.Statement) { + s.mgmtOperation(func() error { + l = s.policy.GetStatement() + return nil + }, false) + return l +} + +func (s *BgpServer) AddStatement(st *table.Statement) error { + return s.mgmtOperation(func() error { + return s.policy.AddStatement(st) + }, false) +} + +func (s *BgpServer) DeleteStatement(st *table.Statement, all bool) error { + return s.mgmtOperation(func() error { + return s.policy.DeleteStatement(st, all) + }, false) +} + +func (s *BgpServer) ReplaceStatement(st *table.Statement) error { + return s.mgmtOperation(func() error { + return s.policy.ReplaceStatement(st) + }, false) +} + +func (s *BgpServer) GetPolicy() (l []*config.PolicyDefinition) { + s.mgmtOperation(func() error { + l = s.policy.GetAllPolicy() + return nil + }, false) + return l +} + +func (s *BgpServer) AddPolicy(x *table.Policy, refer bool) error { + return s.mgmtOperation(func() error { + return s.policy.AddPolicy(x, refer) + }, false) +} + +func (s *BgpServer) DeletePolicy(x *table.Policy, all, preserve bool) error { + return s.mgmtOperation(func() error { + l := make([]string, 0, len(s.neighborMap)+1) + for _, peer := range s.neighborMap { + l = append(l, peer.ID()) + } + l = append(l, table.GLOBAL_RIB_NAME) + + return s.policy.DeletePolicy(x, all, preserve, l) + }, false) +} + +func (s *BgpServer) ReplacePolicy(x *table.Policy, refer, preserve bool) error { + return s.mgmtOperation(func() error { + return s.policy.ReplacePolicy(x, refer, preserve) + }, false) +} + +func (server *BgpServer) toPolicyInfo(name string, dir table.PolicyDirection) (string, error) { + if name == "" { + switch dir { + case table.POLICY_DIRECTION_IMPORT, table.POLICY_DIRECTION_EXPORT: + return table.GLOBAL_RIB_NAME, nil + } + return "", fmt.Errorf("invalid policy type") + } else { + peer, ok := server.neighborMap[name] + if !ok { + return "", fmt.Errorf("not found peer %s", name) + } + if !peer.isRouteServerClient() { + return "", fmt.Errorf("non-rs-client peer %s doesn't have per peer policy", name) + } + return peer.ID(), nil + } +} + +func (s *BgpServer) GetPolicyAssignment(name string, dir table.PolicyDirection) (rt table.RouteType, l []*config.PolicyDefinition, err error) { + err = s.mgmtOperation(func() error { + var id string + id, err = s.toPolicyInfo(name, dir) + if err != nil { + rt = table.ROUTE_TYPE_NONE + return err + } + rt, l, err = s.policy.GetPolicyAssignment(id, dir) + return nil + }, false) + return rt, l, err +} + +func (s *BgpServer) AddPolicyAssignment(name string, dir table.PolicyDirection, policies []*config.PolicyDefinition, def table.RouteType) error { + return s.mgmtOperation(func() error { + id, err := s.toPolicyInfo(name, dir) + if err != nil { + return err + } + return s.policy.AddPolicyAssignment(id, dir, policies, def) + }, false) +} + +func (s *BgpServer) DeletePolicyAssignment(name string, dir table.PolicyDirection, policies []*config.PolicyDefinition, all bool) error { + return s.mgmtOperation(func() error { + id, err := s.toPolicyInfo(name, dir) + if err != nil { + return err + } + return s.policy.DeletePolicyAssignment(id, dir, policies, all) + }, false) +} + +func (s *BgpServer) ReplacePolicyAssignment(name string, dir table.PolicyDirection, policies []*config.PolicyDefinition, def table.RouteType) error { + return s.mgmtOperation(func() error { + id, err := s.toPolicyInfo(name, dir) + if err != nil { + return err + } + return s.policy.ReplacePolicyAssignment(id, dir, policies, def) + }, false) +} + +func (s *BgpServer) EnableMrt(c *config.MrtConfig) error { + return s.mgmtOperation(func() error { + return s.mrtManager.enable(c) + }, false) +} + +func (s *BgpServer) DisableMrt(c *config.MrtConfig) error { + return s.mgmtOperation(func() error { + return s.mrtManager.disable(c) + }, false) +} + +func (s *BgpServer) GetRpki() (l []*config.RpkiServer, err error) { + err = s.mgmtOperation(func() error { + l = s.roaManager.GetServers() + return nil + }, false) + return l, err +} + +func (s *BgpServer) GetRoa(family bgp.RouteFamily) (l []*table.ROA, err error) { + s.mgmtOperation(func() error { + l, err = s.roaManager.GetRoa(family) + return nil + }, false) + return l, err +} + +func (s *BgpServer) AddRpki(c *config.RpkiServerConfig) error { + return s.mgmtOperation(func() error { + return s.roaManager.AddServer(net.JoinHostPort(c.Address, strconv.Itoa(int(c.Port))), c.RecordLifetime) + }, false) +} + +func (s *BgpServer) DeleteRpki(c *config.RpkiServerConfig) error { + return s.mgmtOperation(func() error { + return s.roaManager.DeleteServer(c.Address) + }, false) +} + +func (s *BgpServer) EnableRpki(c *config.RpkiServerConfig) error { + return s.mgmtOperation(func() error { + return s.roaManager.Enable(c.Address) + }, false) +} + +func (s *BgpServer) DisableRpki(c *config.RpkiServerConfig) error { + return s.mgmtOperation(func() error { + return s.roaManager.Disable(c.Address) + }, false) +} + +func (s *BgpServer) ResetRpki(c *config.RpkiServerConfig) error { + return s.mgmtOperation(func() error { + return s.roaManager.Reset(c.Address) + }, false) +} + +func (s *BgpServer) SoftResetRpki(c *config.RpkiServerConfig) error { + return s.mgmtOperation(func() error { + return s.roaManager.SoftReset(c.Address) + }, false) +} + +type WatchEventType string + +const ( + WATCH_EVENT_TYPE_BEST_PATH WatchEventType = "bestpath" + WATCH_EVENT_TYPE_PRE_UPDATE WatchEventType = "preupdate" + WATCH_EVENT_TYPE_POST_UPDATE WatchEventType = "postupdate" + WATCH_EVENT_TYPE_PEER_STATE WatchEventType = "peerstate" + WATCH_EVENT_TYPE_TABLE WatchEventType = "table" + WATCH_EVENT_TYPE_RECV_MSG WatchEventType = "receivedmessage" +) + +type WatchEvent interface { +} + +type WatchEventUpdate struct { + Message *bgp.BGPMessage + PeerAS uint32 + LocalAS uint32 + PeerAddress net.IP + LocalAddress net.IP + PeerID net.IP + FourBytesAs bool + Timestamp time.Time + Payload []byte + PostPolicy bool + Init bool + PathList []*table.Path + Neighbor *config.Neighbor +} + +type WatchEventPeerState struct { + PeerAS uint32 + LocalAS uint32 + PeerAddress net.IP + LocalAddress net.IP + PeerPort uint16 + LocalPort uint16 + PeerID net.IP + SentOpen *bgp.BGPMessage + RecvOpen *bgp.BGPMessage + State bgp.FSMState + StateReason *FsmStateReason + AdminState AdminState + Timestamp time.Time + PeerInterface string +} + +type WatchEventAdjIn struct { + PathList []*table.Path +} + +type WatchEventTable struct { + RouterId string + PathList map[string][]*table.Path + Neighbor []*config.Neighbor +} + +type WatchEventBestPath struct { + PathList []*table.Path + MultiPathList [][]*table.Path + Vrf map[string]uint16 +} + +type WatchEventMessage struct { + Message *bgp.BGPMessage + PeerAS uint32 + LocalAS uint32 + PeerAddress net.IP + LocalAddress net.IP + PeerID net.IP + FourBytesAs bool + Timestamp time.Time + IsSent bool +} + +type watchOptions struct { + bestpath bool + preUpdate bool + postUpdate bool + peerState bool + initBest bool + initUpdate bool + initPostUpdate bool + initPeerState bool + tableName string + recvMessage bool +} + +type WatchOption func(*watchOptions) + +func WatchBestPath(current bool) WatchOption { + return func(o *watchOptions) { + o.bestpath = true + if current { + o.initBest = true + } + } +} + +func WatchUpdate(current bool) WatchOption { + return func(o *watchOptions) { + o.preUpdate = true + if current { + o.initUpdate = true + } + } +} + +func WatchPostUpdate(current bool) WatchOption { + return func(o *watchOptions) { + o.postUpdate = true + if current { + o.initPostUpdate = true + } + } +} + +func WatchPeerState(current bool) WatchOption { + return func(o *watchOptions) { + o.peerState = true + if current { + o.initPeerState = true + } + } +} + +func WatchTableName(name string) WatchOption { + return func(o *watchOptions) { + o.tableName = name + } +} + +func WatchMessage(isSent bool) WatchOption { + return func(o *watchOptions) { + if isSent { + log.WithFields(log.Fields{ + "Topic": "Server", + }).Warn("watch event for sent messages is not implemented yet") + // o.sentMessage = true + } else { + o.recvMessage = true + } + } +} + +type Watcher struct { + opts watchOptions + realCh chan WatchEvent + ch *channels.InfiniteChannel + s *BgpServer +} + +func (w *Watcher) Event() <-chan WatchEvent { + return w.realCh +} + +func (w *Watcher) Generate(t WatchEventType) error { + return w.s.mgmtOperation(func() error { + switch t { + case WATCH_EVENT_TYPE_PRE_UPDATE: + pathList := make([]*table.Path, 0) + for _, peer := range w.s.neighborMap { + pathList = append(pathList, peer.adjRibIn.PathList(peer.configuredRFlist(), false)...) + } + w.notify(&WatchEventAdjIn{PathList: clonePathList(pathList)}) + case WATCH_EVENT_TYPE_TABLE: + rib := w.s.globalRib + as := uint32(0) + id := table.GLOBAL_RIB_NAME + if len(w.opts.tableName) > 0 { + peer, ok := w.s.neighborMap[w.opts.tableName] + if !ok { + return fmt.Errorf("Neighbor that has %v doesn't exist.", w.opts.tableName) + } + if !peer.isRouteServerClient() { + return fmt.Errorf("Neighbor %v doesn't have local rib", w.opts.tableName) + } + id = peer.ID() + as = peer.AS() + rib = w.s.rsRib + } + + pathList := func() map[string][]*table.Path { + pathList := make(map[string][]*table.Path) + for _, t := range rib.Tables { + for _, dst := range t.GetDestinations() { + if paths := dst.GetKnownPathList(id, as); len(paths) > 0 { + pathList[dst.GetNlri().String()] = clonePathList(paths) + } + } + } + return pathList + }() + l := make([]*config.Neighbor, 0, len(w.s.neighborMap)) + for _, peer := range w.s.neighborMap { + l = append(l, w.s.ToConfig(peer, false)) + } + w.notify(&WatchEventTable{PathList: pathList, Neighbor: l}) + default: + return fmt.Errorf("unsupported type %v", t) + } + return nil + }, false) +} + +func (w *Watcher) notify(v WatchEvent) { + w.ch.In() <- v +} + +func (w *Watcher) loop() { + for ev := range w.ch.Out() { + w.realCh <- ev.(WatchEvent) + } + close(w.realCh) +} + +func (w *Watcher) Stop() { + w.s.mgmtOperation(func() error { + for k, l := range w.s.watcherMap { + for i, v := range l { + if w == v { + w.s.watcherMap[k] = append(l[:i], l[i+1:]...) + break + } + } + } + + cleanInfiniteChannel(w.ch) + // the loop function goroutine might be blocked for + // writing to realCh. make sure it finishes. + for range w.realCh { + } + return nil + }, false) +} + +func (s *BgpServer) isWatched(typ WatchEventType) bool { + return len(s.watcherMap[typ]) != 0 +} + +func (s *BgpServer) notifyWatcher(typ WatchEventType, ev WatchEvent) { + for _, w := range s.watcherMap[typ] { + w.notify(ev) + } +} + +func (s *BgpServer) Watch(opts ...WatchOption) (w *Watcher) { + s.mgmtOperation(func() error { + w = &Watcher{ + s: s, + realCh: make(chan WatchEvent, 8), + ch: channels.NewInfiniteChannel(), + } + + for _, opt := range opts { + opt(&w.opts) + } + + register := func(t WatchEventType, w *Watcher) { + s.watcherMap[t] = append(s.watcherMap[t], w) + } + + if w.opts.bestpath { + register(WATCH_EVENT_TYPE_BEST_PATH, w) + } + if w.opts.preUpdate { + register(WATCH_EVENT_TYPE_PRE_UPDATE, w) + } + if w.opts.postUpdate { + register(WATCH_EVENT_TYPE_POST_UPDATE, w) + } + if w.opts.peerState { + register(WATCH_EVENT_TYPE_PEER_STATE, w) + } + if w.opts.initPeerState { + for _, peer := range s.neighborMap { + if peer.fsm.state != bgp.BGP_FSM_ESTABLISHED { + continue + } + w.notify(newWatchEventPeerState(peer, nil)) + } + } + if w.opts.initBest && s.active() == nil { + w.notify(&WatchEventBestPath{ + PathList: s.globalRib.GetBestPathList(table.GLOBAL_RIB_NAME, 0, nil), + MultiPathList: s.globalRib.GetBestMultiPathList(table.GLOBAL_RIB_NAME, nil), + }) + } + if w.opts.initUpdate { + for _, peer := range s.neighborMap { + if peer.fsm.state != bgp.BGP_FSM_ESTABLISHED { + continue + } + configNeighbor := w.s.ToConfig(peer, false) + for _, rf := range peer.configuredRFlist() { + _, y := peer.fsm.capMap[bgp.BGP_CAP_FOUR_OCTET_AS_NUMBER] + l, _ := peer.fsm.LocalHostPort() + w.notify(&WatchEventUpdate{ + PeerAS: peer.fsm.peerInfo.AS, + LocalAS: peer.fsm.peerInfo.LocalAS, + PeerAddress: peer.fsm.peerInfo.Address, + LocalAddress: net.ParseIP(l), + PeerID: peer.fsm.peerInfo.ID, + FourBytesAs: y, + Init: true, + PostPolicy: false, + Neighbor: configNeighbor, + PathList: peer.adjRibIn.PathList([]bgp.RouteFamily{rf}, false), + }) + + eor := bgp.NewEndOfRib(rf) + eorBuf, _ := eor.Serialize() + w.notify(&WatchEventUpdate{ + Message: eor, + PeerAS: peer.fsm.peerInfo.AS, + LocalAS: peer.fsm.peerInfo.LocalAS, + PeerAddress: peer.fsm.peerInfo.Address, + LocalAddress: net.ParseIP(l), + PeerID: peer.fsm.peerInfo.ID, + FourBytesAs: y, + Timestamp: time.Now(), + Init: true, + Payload: eorBuf, + PostPolicy: false, + Neighbor: configNeighbor, + }) + } + } + } + if w.opts.initPostUpdate && s.active() == nil { + for _, rf := range s.globalRib.GetRFlist() { + if len(s.globalRib.Tables[rf].GetDestinations()) == 0 { + continue + } + pathsByPeer := make(map[*table.PeerInfo][]*table.Path) + for _, path := range s.globalRib.GetPathList(table.GLOBAL_RIB_NAME, 0, []bgp.RouteFamily{rf}) { + pathsByPeer[path.GetSource()] = append(pathsByPeer[path.GetSource()], path) + } + for peerInfo, paths := range pathsByPeer { + // create copy which can be access to without mutex + var configNeighbor *config.Neighbor + if peer, ok := s.neighborMap[peerInfo.Address.String()]; ok { + configNeighbor = w.s.ToConfig(peer, false) + } + + w.notify(&WatchEventUpdate{ + PeerAS: peerInfo.AS, + PeerAddress: peerInfo.Address, + PeerID: peerInfo.ID, + PostPolicy: true, + Neighbor: configNeighbor, + PathList: paths, + Init: true, + }) + + eor := bgp.NewEndOfRib(rf) + eorBuf, _ := eor.Serialize() + w.notify(&WatchEventUpdate{ + Message: eor, + PeerAS: peerInfo.AS, + PeerAddress: peerInfo.Address, + PeerID: peerInfo.ID, + Timestamp: time.Now(), + Payload: eorBuf, + PostPolicy: true, + Neighbor: configNeighbor, + Init: true, + }) + } + } + } + if w.opts.recvMessage { + register(WATCH_EVENT_TYPE_RECV_MSG, w) + } + + go w.loop() + return nil + }, false) + return w +} diff --git a/pkg/server/server_test.go b/pkg/server/server_test.go new file mode 100644 index 00000000..e4a5e677 --- /dev/null +++ b/pkg/server/server_test.go @@ -0,0 +1,706 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "context" + "net" + "runtime" + "testing" + "time" + + "github.com/osrg/gobgp/internal/pkg/config" + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestModPolicyAssign(t *testing.T) { + assert := assert.New(t) + s := NewBgpServer() + go s.Serve() + err := s.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: -1, + }, + }) + assert.Nil(err) + defer s.Stop() + + err = s.AddPolicy(&table.Policy{Name: "p1"}, false) + assert.Nil(err) + + err = s.AddPolicy(&table.Policy{Name: "p2"}, false) + assert.Nil(err) + + err = s.AddPolicy(&table.Policy{Name: "p3"}, false) + assert.Nil(err) + + err = s.AddPolicyAssignment("", table.POLICY_DIRECTION_IMPORT, + []*config.PolicyDefinition{&config.PolicyDefinition{Name: "p1"}, &config.PolicyDefinition{Name: "p2"}, &config.PolicyDefinition{Name: "p3"}}, table.ROUTE_TYPE_ACCEPT) + assert.Nil(err) + + err = s.DeletePolicyAssignment("", table.POLICY_DIRECTION_IMPORT, + []*config.PolicyDefinition{&config.PolicyDefinition{Name: "p1"}}, false) + assert.Nil(err) + + _, ps, _ := s.GetPolicyAssignment("", table.POLICY_DIRECTION_IMPORT) + assert.Equal(len(ps), 2) +} + +func TestMonitor(test *testing.T) { + assert := assert.New(test) + s := NewBgpServer() + go s.Serve() + err := s.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: 10179, + }, + }) + assert.Nil(err) + defer s.Stop() + + n := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 2, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + PassiveMode: true, + }, + }, + } + err = s.AddNeighbor(n) + assert.Nil(err) + + t := NewBgpServer() + go t.Serve() + err = t.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 2, + RouterId: "2.2.2.2", + Port: -1, + }, + }) + assert.Nil(err) + defer t.Stop() + + m := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 1, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + RemotePort: 10179, + }, + }, + } + err = t.AddNeighbor(m) + assert.Nil(err) + + for { + time.Sleep(time.Second) + if t.GetNeighbor("", false)[0].State.SessionState == config.SESSION_STATE_ESTABLISHED { + break + } + } + + // Test WatchBestPath. + w := s.Watch(WatchBestPath(false)) + + // Advertises a route. + attrs := []bgp.PathAttributeInterface{ + bgp.NewPathAttributeOrigin(0), + bgp.NewPathAttributeNextHop("10.0.0.1"), + } + if _, err := t.AddPath("", []*table.Path{table.NewPath(nil, bgp.NewIPAddrPrefix(24, "10.0.0.0"), false, attrs, time.Now(), false)}); err != nil { + log.Fatal(err) + } + ev := <-w.Event() + b := ev.(*WatchEventBestPath) + assert.Equal(1, len(b.PathList)) + assert.Equal("10.0.0.0/24", b.PathList[0].GetNlri().String()) + assert.False(b.PathList[0].IsWithdraw) + + // Withdraws the previous route. + // NOTE: Withdow should not require any path attribute. + if _, err := t.AddPath("", []*table.Path{table.NewPath(nil, bgp.NewIPAddrPrefix(24, "10.0.0.0"), true, nil, time.Now(), false)}); err != nil { + log.Fatal(err) + } + ev = <-w.Event() + b = ev.(*WatchEventBestPath) + assert.Equal(1, len(b.PathList)) + assert.Equal("10.0.0.0/24", b.PathList[0].GetNlri().String()) + assert.True(b.PathList[0].IsWithdraw) + + // Stops the watcher still having an item. + w.Stop() + + // Prepares an initial route to test WatchUpdate with "current" flag. + if _, err := t.AddPath("", []*table.Path{table.NewPath(nil, bgp.NewIPAddrPrefix(24, "10.1.0.0"), false, attrs, time.Now(), false)}); err != nil { + log.Fatal(err) + } + for { + // Waits for the initial route will be advertised. + rib, _, err := s.GetRib("", bgp.RF_IPv4_UC, nil) + if err != nil { + log.Fatal(err) + } + if len(rib.GetKnownPathList("", 0)) > 0 { + break + } + time.Sleep(100 * time.Millisecond) + } + + // Test WatchUpdate with "current" flag. + w = s.Watch(WatchUpdate(true)) + + // Test the initial route. + ev = <-w.Event() + u := ev.(*WatchEventUpdate) + assert.Equal(1, len(u.PathList)) + assert.Equal("10.1.0.0/24", u.PathList[0].GetNlri().String()) + assert.False(u.PathList[0].IsWithdraw) + ev = <-w.Event() + u = ev.(*WatchEventUpdate) + assert.Equal(len(u.PathList), 0) // End of RIB + + // Advertises an additional route. + if _, err := t.AddPath("", []*table.Path{table.NewPath(nil, bgp.NewIPAddrPrefix(24, "10.2.0.0"), false, attrs, time.Now(), false)}); err != nil { + log.Fatal(err) + } + ev = <-w.Event() + u = ev.(*WatchEventUpdate) + assert.Equal(1, len(u.PathList)) + assert.Equal("10.2.0.0/24", u.PathList[0].GetNlri().String()) + assert.False(u.PathList[0].IsWithdraw) + + // Withdraws the previous route. + // NOTE: Withdow should not require any path attribute. + if _, err := t.AddPath("", []*table.Path{table.NewPath(nil, bgp.NewIPAddrPrefix(24, "10.2.0.0"), true, nil, time.Now(), false)}); err != nil { + log.Fatal(err) + } + ev = <-w.Event() + u = ev.(*WatchEventUpdate) + assert.Equal(1, len(u.PathList)) + assert.Equal("10.2.0.0/24", u.PathList[0].GetNlri().String()) + assert.True(u.PathList[0].IsWithdraw) + + // Stops the watcher still having an item. + w.Stop() +} + +func TestNumGoroutineWithAddDeleteNeighbor(t *testing.T) { + assert := assert.New(t) + s := NewBgpServer() + go s.Serve() + err := s.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: -1, + }, + }) + assert.Nil(err) + defer s.Stop() + + // wait a few seconds to avoid taking effect from other test cases. + time.Sleep(time.Second * 5) + + num := runtime.NumGoroutine() + + n := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 2, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + PassiveMode: true, + }, + }, + } + err = s.AddNeighbor(n) + assert.Nil(err) + + err = s.DeleteNeighbor(n) + assert.Nil(err) + // wait goroutines to finish (e.g. internal goroutine for + // InfiniteChannel) + time.Sleep(time.Second * 5) + assert.Equal(num, runtime.NumGoroutine()) +} + +func newPeerandInfo(myAs, as uint32, address string, rib *table.TableManager) (*Peer, *table.PeerInfo) { + nConf := &config.Neighbor{Config: config.NeighborConfig{PeerAs: as, NeighborAddress: address}} + gConf := &config.Global{Config: config.GlobalConfig{As: myAs}} + config.SetDefaultNeighborConfigValues(nConf, nil, gConf) + policy := table.NewRoutingPolicy() + policy.Reset(&config.RoutingPolicy{}, nil) + p := NewPeer( + &config.Global{Config: config.GlobalConfig{As: myAs}}, + nConf, + rib, + policy) + for _, f := range rib.GetRFlist() { + p.fsm.rfMap[f] = bgp.BGP_ADD_PATH_NONE + } + return p, &table.PeerInfo{AS: as, Address: net.ParseIP(address)} +} + +func process(rib *table.TableManager, l []*table.Path) (*table.Path, *table.Path) { + dsts := make([]*table.Update, 0) + for _, path := range l { + dsts = append(dsts, rib.Update(path)...) + } + news, olds, _ := dstsToPaths(table.GLOBAL_RIB_NAME, 0, dsts) + if len(news) != 1 { + panic("can't handle multiple paths") + } + + return news[0], olds[0] +} + +func TestFilterpathWitheBGP(t *testing.T) { + as := uint32(65000) + p1As := uint32(65001) + p2As := uint32(65002) + rib := table.NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + p1, pi1 := newPeerandInfo(as, p1As, "192.168.0.1", rib) + p2, pi2 := newPeerandInfo(as, p2As, "192.168.0.2", rib) + + nlri := bgp.NewIPAddrPrefix(24, "10.10.10.0") + pa1 := []bgp.PathAttributeInterface{bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{p1As})}), bgp.NewPathAttributeLocalPref(200)} + pa2 := []bgp.PathAttributeInterface{bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{p2As})})} + + path1 := table.NewPath(pi1, nlri, false, pa1, time.Now(), false) + path2 := table.NewPath(pi2, nlri, false, pa2, time.Now(), false) + rib.Update(path2) + d := rib.Update(path1) + new, old, _ := d[0].GetChanges(table.GLOBAL_RIB_NAME, 0, false) + assert.Equal(t, new, path1) + filterpath(p1, new, old) + filterpath(p2, new, old) + + new, old = process(rib, []*table.Path{path1.Clone(true)}) + assert.Equal(t, new, path2) + // p1 and p2 advertized the same prefix and p1's was best. Then p1 withdraw it, so p2 must get withdawal. + path := filterpath(p2, new, old) + assert.NotNil(t, path) + assert.True(t, path.IsWithdraw) + + // p1 should get the new best (from p2) + assert.Equal(t, filterpath(p1, new, old), path2) + + new, old = process(rib, []*table.Path{path2.Clone(true)}) + assert.True(t, new.IsWithdraw) + // p2 withdraw so p1 should get withdrawal. + path = filterpath(p1, new, old) + assert.True(t, path.IsWithdraw) + + // p2 withdraw so p2 should get nothing. + path = filterpath(p2, new, old) + assert.Nil(t, path) +} + +func TestFilterpathWithiBGP(t *testing.T) { + as := uint32(65000) + + rib := table.NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + p1, pi1 := newPeerandInfo(as, as, "192.168.0.1", rib) + //p2, pi2 := newPeerandInfo(as, as, "192.168.0.2", rib) + p2, _ := newPeerandInfo(as, as, "192.168.0.2", rib) + + nlri := bgp.NewIPAddrPrefix(24, "10.10.10.0") + pa1 := []bgp.PathAttributeInterface{bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{as})}), bgp.NewPathAttributeLocalPref(200)} + //pa2 := []bgp.PathAttributeInterface{bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{as})})} + + path1 := table.NewPath(pi1, nlri, false, pa1, time.Now(), false) + //path2 := table.NewPath(pi2, nlri, false, pa2, time.Now(), false) + + new, old := process(rib, []*table.Path{path1}) + assert.Equal(t, new, path1) + path := filterpath(p1, new, old) + assert.Nil(t, path) + path = filterpath(p2, new, old) + assert.Nil(t, path) + + new, old = process(rib, []*table.Path{path1.Clone(true)}) + path = filterpath(p1, new, old) + assert.Nil(t, path) + path = filterpath(p2, new, old) + assert.Nil(t, path) + +} + +func TestFilterpathWithRejectPolicy(t *testing.T) { + rib1 := table.NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + _, pi1 := newPeerandInfo(1, 2, "192.168.0.1", rib1) + rib2 := table.NewTableManager([]bgp.RouteFamily{bgp.RF_IPv4_UC}) + p2, _ := newPeerandInfo(1, 3, "192.168.0.2", rib2) + + comSet1 := config.CommunitySet{ + CommunitySetName: "comset1", + CommunityList: []string{"100:100"}, + } + s, _ := table.NewCommunitySet(comSet1) + p2.policy.AddDefinedSet(s) + + statement := config.Statement{ + Name: "stmt1", + Conditions: config.Conditions{ + BgpConditions: config.BgpConditions{ + MatchCommunitySet: config.MatchCommunitySet{ + CommunitySet: "comset1", + }, + }, + }, + Actions: config.Actions{ + RouteDisposition: config.ROUTE_DISPOSITION_REJECT_ROUTE, + }, + } + policy := config.PolicyDefinition{ + Name: "policy1", + Statements: []config.Statement{statement}, + } + p, _ := table.NewPolicy(policy) + p2.policy.AddPolicy(p, false) + policies := []*config.PolicyDefinition{ + &config.PolicyDefinition{ + Name: "policy1", + }, + } + p2.policy.AddPolicyAssignment(p2.TableID(), table.POLICY_DIRECTION_EXPORT, policies, table.ROUTE_TYPE_ACCEPT) + + for _, addCommunity := range []bool{false, true, false, true} { + nlri := bgp.NewIPAddrPrefix(24, "10.10.10.0") + pa1 := []bgp.PathAttributeInterface{bgp.NewPathAttributeAsPath([]bgp.AsPathParamInterface{bgp.NewAs4PathParam(2, []uint32{1})}), bgp.NewPathAttributeLocalPref(200)} + if addCommunity { + pa1 = append(pa1, bgp.NewPathAttributeCommunities([]uint32{100<<16 | 100})) + } + path1 := table.NewPath(pi1, nlri, false, pa1, time.Now(), false) + new, old := process(rib2, []*table.Path{path1}) + assert.Equal(t, new, path1) + s := NewBgpServer() + path2 := s.filterpath(p2, new, old) + if addCommunity { + assert.True(t, path2.IsWithdraw) + } else { + assert.False(t, path2.IsWithdraw) + } + } + +} + +func TestPeerGroup(test *testing.T) { + assert := assert.New(test) + log.SetLevel(log.DebugLevel) + s := NewBgpServer() + go s.Serve() + err := s.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: 10179, + }, + }) + assert.Nil(err) + defer s.Stop() + + g := &config.PeerGroup{ + Config: config.PeerGroupConfig{ + PeerAs: 2, + PeerGroupName: "g", + }, + } + err = s.AddPeerGroup(g) + assert.Nil(err) + + n := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerGroup: "g", + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + PassiveMode: true, + }, + }, + } + configured := map[string]interface{}{ + "config": map[string]interface{}{ + "neigbor-address": "127.0.0.1", + "peer-group": "g", + }, + "transport": map[string]interface{}{ + "config": map[string]interface{}{ + "passive-mode": true, + }, + }, + } + config.RegisterConfiguredFields("127.0.0.1", configured) + err = s.AddNeighbor(n) + assert.Nil(err) + + t := NewBgpServer() + go t.Serve() + err = t.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 2, + RouterId: "2.2.2.2", + Port: -1, + }, + }) + assert.Nil(err) + defer t.Stop() + + m := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 1, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + RemotePort: 10179, + }, + }, + } + err = t.AddNeighbor(m) + assert.Nil(err) + + for { + time.Sleep(time.Second) + if t.GetNeighbor("", false)[0].State.SessionState == config.SESSION_STATE_ESTABLISHED { + break + } + } +} + +func TestDynamicNeighbor(t *testing.T) { + assert := assert.New(t) + log.SetLevel(log.DebugLevel) + s1 := NewBgpServer() + go s1.Serve() + err := s1.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: 10179, + }, + }) + assert.Nil(err) + defer s1.Stop() + + g := &config.PeerGroup{ + Config: config.PeerGroupConfig{ + PeerAs: 2, + PeerGroupName: "g", + }, + } + err = s1.AddPeerGroup(g) + assert.Nil(err) + + d := &config.DynamicNeighbor{ + Config: config.DynamicNeighborConfig{ + Prefix: "127.0.0.0/24", + PeerGroup: "g", + }, + } + err = s1.AddDynamicNeighbor(d) + assert.Nil(err) + + s2 := NewBgpServer() + go s2.Serve() + err = s2.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 2, + RouterId: "2.2.2.2", + Port: -1, + }, + }) + assert.Nil(err) + defer s2.Stop() + + m := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 1, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + RemotePort: 10179, + }, + }, + } + err = s2.AddNeighbor(m) + + assert.Nil(err) + + for { + time.Sleep(time.Second) + if s2.GetNeighbor("", false)[0].State.SessionState == config.SESSION_STATE_ESTABLISHED { + break + } + } +} + +func TestGracefulRestartTimerExpired(t *testing.T) { + assert := assert.New(t) + s1 := NewBgpServer() + go s1.Serve() + err := s1.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 1, + RouterId: "1.1.1.1", + Port: 10179, + }, + }) + assert.Nil(err) + defer s1.Stop() + + n := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 2, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + PassiveMode: true, + }, + }, + GracefulRestart: config.GracefulRestart{ + Config: config.GracefulRestartConfig{ + Enabled: true, + RestartTime: 1, + }, + }, + } + err = s1.AddNeighbor(n) + assert.Nil(err) + + s2 := NewBgpServer() + go s2.Serve() + err = s2.Start(&config.Global{ + Config: config.GlobalConfig{ + As: 2, + RouterId: "2.2.2.2", + Port: -1, + }, + }) + require.NoError(t, err) + defer s2.Stop() + + m := &config.Neighbor{ + Config: config.NeighborConfig{ + NeighborAddress: "127.0.0.1", + PeerAs: 1, + }, + Transport: config.Transport{ + Config: config.TransportConfig{ + RemotePort: 10179, + }, + }, + GracefulRestart: config.GracefulRestart{ + Config: config.GracefulRestartConfig{ + Enabled: true, + RestartTime: 1, + }, + }, + } + err = s2.AddNeighbor(m) + assert.Nil(err) + + // Waiting for BGP session established. + for { + time.Sleep(time.Second) + if s2.GetNeighbor("", false)[0].State.SessionState == config.SESSION_STATE_ESTABLISHED { + break + } + } + + // Force TCP session disconnected in order to cause Graceful Restart at s1 + // side. + for _, n := range s2.neighborMap { + n.fsm.conn.Close() + } + s2.Stop() + + time.Sleep(5 * time.Second) + + // Create dummy session which does NOT send BGP OPEN message in order to + // cause Graceful Restart timer expired. + var conn net.Conn + + conn, err = net.Dial("tcp", "127.0.0.1:10179") + require.NoError(t, err) + defer conn.Close() + + // this seems to take around 22 seconds... need to address this whole thing + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // Waiting for Graceful Restart timer expired and moving on to IDLE state. + for { + if s1.GetNeighbor("", false)[0].State.SessionState == config.SESSION_STATE_IDLE { + break + } + + select { + case <-time.After(time.Second): + case <-ctx.Done(): + t.Fatalf("failed to enter IDLE state in the deadline") + return + } + } +} + +func TestFamiliesForSoftreset(t *testing.T) { + f := func(f bgp.RouteFamily) config.AfiSafi { + return config.AfiSafi{ + State: config.AfiSafiState{ + Family: f, + }, + } + } + peer := &Peer{ + fsm: &FSM{ + pConf: &config.Neighbor{ + AfiSafis: []config.AfiSafi{f(bgp.RF_RTC_UC), f(bgp.RF_IPv4_UC), f(bgp.RF_IPv6_UC)}, + }, + }, + } + + families := familiesForSoftreset(peer, bgp.RF_IPv4_UC) + assert.Equal(t, len(families), 1) + assert.Equal(t, families[0], bgp.RF_IPv4_UC) + + families = familiesForSoftreset(peer, bgp.RF_RTC_UC) + assert.Equal(t, len(families), 1) + assert.Equal(t, families[0], bgp.RF_RTC_UC) + + families = familiesForSoftreset(peer, bgp.RouteFamily(0)) + assert.Equal(t, len(families), 2) + assert.NotContains(t, families, bgp.RF_RTC_UC) +} diff --git a/pkg/server/sockopt.go b/pkg/server/sockopt.go new file mode 100644 index 00000000..e1c9c467 --- /dev/null +++ b/pkg/server/sockopt.go @@ -0,0 +1,90 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build !linux,!openbsd + +package server + +import ( + "fmt" + "net" + + log "github.com/sirupsen/logrus" +) + +func SetTcpMD5SigSockopt(l *net.TCPListener, address string, key string) error { + return setTcpMD5SigSockopt(l, address, key) +} + +func SetListenTcpTTLSockopt(l *net.TCPListener, ttl int) error { + return setListenTcpTTLSockopt(l, ttl) +} + +func SetTcpTTLSockopt(conn *net.TCPConn, ttl int) error { + return setTcpTTLSockopt(conn, ttl) +} + +func SetTcpMinTTLSockopt(conn *net.TCPConn, ttl int) error { + return setTcpMinTTLSockopt(conn, ttl) +} + +type TCPDialer struct { + net.Dialer + + // MD5 authentication password. + AuthPassword string + + // The TTL value to set outgoing connection. + Ttl uint8 + + // The minimum TTL value for incoming packets. + TtlMin uint8 +} + +func (d *TCPDialer) DialTCP(addr string, port int) (*net.TCPConn, error) { + if d.AuthPassword != "" { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warn("setting md5 for active connection is not supported") + } + if d.Ttl != 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warn("setting ttl for active connection is not supported") + } + if d.TtlMin != 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warn("setting min ttl for active connection is not supported") + } + + raddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(addr, fmt.Sprintf("%d", port))) + if err != nil { + return nil, fmt.Errorf("invalid remote address: %s", err) + } + laddr, err := net.ResolveTCPAddr("tcp", d.LocalAddr.String()) + if err != nil { + return nil, fmt.Errorf("invalid local address: %s", err) + } + + dialer := net.Dialer{LocalAddr: laddr, Timeout: d.Timeout} + conn, err := dialer.Dial("tcp", raddr.String()) + if err != nil { + return nil, err + } + return conn.(*net.TCPConn), nil +} diff --git a/pkg/server/sockopt_bsd.go b/pkg/server/sockopt_bsd.go new file mode 100644 index 00000000..651e4e58 --- /dev/null +++ b/pkg/server/sockopt_bsd.go @@ -0,0 +1,89 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build dragonfly freebsd netbsd + +package server + +import ( + "net" + "os" + "syscall" +) + +const ( + TCP_MD5SIG = 0x10 // TCP MD5 Signature (RFC2385) + IPV6_MINHOPCOUNT = 73 // Generalized TTL Security Mechanism (RFC5082) +) + +func setsockoptTcpMD5Sig(fd int, address string, key string) error { + // always enable and assumes that the configuration is done by setkey() + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_MD5SIG, 1)) +} + +func setTcpMD5SigSockopt(l *net.TCPListener, address string, key string) error { + fi, _, err := extractFileAndFamilyFromTCPListener(l) + defer fi.Close() + if err != nil { + return err + } + return setsockoptTcpMD5Sig(int(fi.Fd()), address, key) +} + +func setsockoptIpTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_TTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = syscall.IPV6_UNICAST_HOPS + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func setListenTcpTTLSockopt(l *net.TCPListener, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPListener(l) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func setTcpTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func setsockoptIpMinTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_MINTTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = IPV6_MINHOPCOUNT + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func setTcpMinTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIpMinTtl(int(fi.Fd()), family, ttl) +} diff --git a/pkg/server/sockopt_darwin.go b/pkg/server/sockopt_darwin.go new file mode 100644 index 00000000..4bad54ff --- /dev/null +++ b/pkg/server/sockopt_darwin.go @@ -0,0 +1,64 @@ +// Copyright (C) 2016-2017 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build darwin + +package server + +import ( + "fmt" + "net" + "os" + "syscall" +) + +func setTcpMD5SigSockopt(l *net.TCPListener, address string, key string) error { + return fmt.Errorf("setting md5 is not supported") +} + +func setsockoptIpTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_TTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = syscall.IPV6_UNICAST_HOPS + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func setListenTcpTTLSockopt(l *net.TCPListener, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPListener(l) + if err != nil { + return err + } + + defer fi.Close() + + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func setTcpTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + if err != nil { + return err + } + + defer fi.Close() + + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func setTcpMinTTLSockopt(conn *net.TCPConn, ttl int) error { + return fmt.Errorf("setting min ttl is not supported") +} diff --git a/pkg/server/sockopt_linux.go b/pkg/server/sockopt_linux.go new file mode 100644 index 00000000..9fe02ba5 --- /dev/null +++ b/pkg/server/sockopt_linux.go @@ -0,0 +1,282 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build linux + +package server + +import ( + "fmt" + "net" + "os" + "syscall" + "unsafe" +) + +const ( + TCP_MD5SIG = 14 // TCP MD5 Signature (RFC2385) + IPV6_MINHOPCOUNT = 73 // Generalized TTL Security Mechanism (RFC5082) +) + +type tcpmd5sig struct { + ss_family uint16 + ss [126]byte + // padding the struct + _ uint16 + keylen uint16 + // padding the struct + _ uint32 + key [80]byte +} + +func buildTcpMD5Sig(address string, key string) (tcpmd5sig, error) { + t := tcpmd5sig{} + addr := net.ParseIP(address) + if addr.To4() != nil { + t.ss_family = syscall.AF_INET + copy(t.ss[2:], addr.To4()) + } else { + t.ss_family = syscall.AF_INET6 + copy(t.ss[6:], addr.To16()) + } + + t.keylen = uint16(len(key)) + copy(t.key[0:], []byte(key)) + + return t, nil +} + +func setsockoptTcpMD5Sig(fd int, address string, key string) error { + t, err := buildTcpMD5Sig(address, key) + if err != nil { + return err + } + b := *(*[unsafe.Sizeof(t)]byte)(unsafe.Pointer(&t)) + return os.NewSyscallError("setsockopt", syscall.SetsockoptString(fd, syscall.IPPROTO_TCP, TCP_MD5SIG, string(b[:]))) +} + +func SetTcpMD5SigSockopt(l *net.TCPListener, address string, key string) error { + fi, _, err := extractFileAndFamilyFromTCPListener(l) + if err != nil { + return err + } + defer fi.Close() + + return setsockoptTcpMD5Sig(int(fi.Fd()), address, key) +} + +func setsockoptIpTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_TTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = syscall.IPV6_UNICAST_HOPS + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func SetListenTcpTTLSockopt(l *net.TCPListener, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPListener(l) + if err != nil { + return err + } + defer fi.Close() + + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func SetTcpTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + if err != nil { + return err + } + defer fi.Close() + + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func setsockoptIpMinTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_MINTTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = IPV6_MINHOPCOUNT + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func SetTcpMinTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + if err != nil { + return err + } + defer fi.Close() + + return setsockoptIpMinTtl(int(fi.Fd()), family, ttl) +} + +type TCPDialer struct { + net.Dialer + + // MD5 authentication password. + AuthPassword string + + // The TTL value to set outgoing connection. + Ttl uint8 + + // The minimum TTL value for incoming packets. + TtlMin uint8 +} + +func (d *TCPDialer) DialTCP(addr string, port int) (*net.TCPConn, error) { + var family int + var ra, la syscall.Sockaddr + + raddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(addr, fmt.Sprintf("%d", port))) + if err != nil { + return nil, fmt.Errorf("invalid remote address: %s", err) + } + laddr, err := net.ResolveTCPAddr("tcp", d.LocalAddr.String()) + if err != nil { + return nil, fmt.Errorf("invalid local address: %s", err) + } + if raddr.IP.To4() != nil { + family = syscall.AF_INET + rsockaddr := &syscall.SockaddrInet4{Port: port} + copy(rsockaddr.Addr[:], raddr.IP.To4()) + ra = rsockaddr + lsockaddr := &syscall.SockaddrInet4{} + copy(lsockaddr.Addr[:], laddr.IP.To4()) + la = lsockaddr + } else { + family = syscall.AF_INET6 + rsockaddr := &syscall.SockaddrInet6{Port: port} + copy(rsockaddr.Addr[:], raddr.IP.To16()) + ra = rsockaddr + var zone uint32 + if laddr.Zone != "" { + if intf, err := net.InterfaceByName(laddr.Zone); err != nil { + return nil, err + } else { + zone = uint32(intf.Index) + } + } + lsockaddr := &syscall.SockaddrInet6{ZoneId: zone} + copy(lsockaddr.Addr[:], laddr.IP.To16()) + la = lsockaddr + } + + sockType := syscall.SOCK_STREAM | syscall.SOCK_CLOEXEC | syscall.SOCK_NONBLOCK + proto := 0 + fd, err := syscall.Socket(family, sockType, proto) + if err != nil { + return nil, err + } + fi := os.NewFile(uintptr(fd), "") + defer fi.Close() + // A new socket was created so we must close it before this + // function returns either on failure or success. On success, + // net.FileConn() in newTCPConn() increases the refcount of + // the socket so this fi.Close() doesn't destroy the socket. + // The caller must call Close() with the file later. + // Note that the above os.NewFile() doesn't play with the + // refcount. + + if err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_BROADCAST, 1); err != nil { + return nil, os.NewSyscallError("setsockopt", err) + } + + if err = syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, syscall.TCP_NODELAY, 1); err != nil { + return nil, os.NewSyscallError("setsockopt", err) + } + + if d.AuthPassword != "" { + if err = setsockoptTcpMD5Sig(fd, addr, d.AuthPassword); err != nil { + return nil, err + } + } + + if d.Ttl != 0 { + if err = setsockoptIpTtl(fd, family, int(d.Ttl)); err != nil { + return nil, err + } + } + + if d.TtlMin != 0 { + if err = setsockoptIpMinTtl(fd, family, int(d.Ttl)); err != nil { + return nil, err + } + } + + if err = syscall.Bind(fd, la); err != nil { + return nil, os.NewSyscallError("bind", err) + } + + newTCPConn := func(fi *os.File) (*net.TCPConn, error) { + if conn, err := net.FileConn(fi); err != nil { + return nil, err + } else { + return conn.(*net.TCPConn), err + } + } + + err = syscall.Connect(fd, ra) + switch err { + case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: + // do timeout handling + case nil, syscall.EISCONN: + return newTCPConn(fi) + default: + return nil, os.NewSyscallError("connect", err) + } + + epfd, e := syscall.EpollCreate1(syscall.EPOLL_CLOEXEC) + if e != nil { + return nil, e + } + defer syscall.Close(epfd) + + var event syscall.EpollEvent + events := make([]syscall.EpollEvent, 1) + + event.Events = syscall.EPOLLIN | syscall.EPOLLOUT | syscall.EPOLLPRI + event.Fd = int32(fd) + if e = syscall.EpollCtl(epfd, syscall.EPOLL_CTL_ADD, fd, &event); e != nil { + return nil, e + } + + for { + nevents, e := syscall.EpollWait(epfd, events, int(d.Timeout/1000000) /*msec*/) + if e != nil { + return nil, e + } + if nevents == 0 { + return nil, fmt.Errorf("timeout") + } else if nevents == 1 && events[0].Fd == int32(fd) { + nerr, err := syscall.GetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_ERROR) + if err != nil { + return nil, os.NewSyscallError("getsockopt", err) + } + switch err := syscall.Errno(nerr); err { + case syscall.EINPROGRESS, syscall.EALREADY, syscall.EINTR: + case syscall.Errno(0), syscall.EISCONN: + return newTCPConn(fi) + default: + return nil, os.NewSyscallError("getsockopt", err) + } + } else { + return nil, fmt.Errorf("unexpected epoll behavior") + } + } +} diff --git a/pkg/server/sockopt_linux_test.go b/pkg/server/sockopt_linux_test.go new file mode 100644 index 00000000..a08e7fc7 --- /dev/null +++ b/pkg/server/sockopt_linux_test.go @@ -0,0 +1,106 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build linux + +package server + +import ( + "bytes" + "fmt" + "net" + "os" + "syscall" + "testing" + "time" + "unsafe" +) + +func Test_buildTcpMD5Sig(t *testing.T) { + s, _ := buildTcpMD5Sig("1.2.3.4", "hello") + + if unsafe.Sizeof(s) != 216 { + t.Error("TCPM5Sig struct size is wrong", unsafe.Sizeof(s)) + } + + buf1 := make([]uint8, 216) + p := unsafe.Pointer(&s) + for i := uintptr(0); i < 216; i++ { + buf1[i] = *(*byte)(unsafe.Pointer(uintptr(p) + i)) + } + + buf2 := []uint8{2, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 104, 101, 108, 108, 111, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + + if bytes.Equal(buf1, buf2) { + t.Log("OK") + } else { + t.Error("Something wrong v4") + } +} + +func Test_buildTcpMD5Sigv6(t *testing.T) { + s, _ := buildTcpMD5Sig("fe80::4850:31ff:fe01:fc55", "helloworld") + + buf1 := make([]uint8, 216) + p := unsafe.Pointer(&s) + for i := uintptr(0); i < 216; i++ { + buf1[i] = *(*byte)(unsafe.Pointer(uintptr(p) + i)) + } + + buf2 := []uint8{10, 0, 0, 0, 0, 0, 0, 0, 254, 128, 0, 0, 0, 0, 0, 0, 72, 80, 49, 255, 254, 1, 252, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 0, 104, 101, 108, 108, 111, 119, 111, 114, 108, 100, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0} + + buf2[0] = syscall.AF_INET6 + + if bytes.Equal(buf1, buf2) { + t.Log("OK") + } else { + t.Error("Something wrong v6") + } +} + +func Test_DialTCP_FDleak(t *testing.T) { + openFds := func() int { + pid := os.Getpid() + f, err := os.OpenFile(fmt.Sprintf("/proc/%d/fdinfo", pid), os.O_RDONLY, 0) + if err != nil { + t.Fatal(err) + } + defer f.Close() + names, err := f.Readdirnames(0) + if err != nil { + t.Fatal(err) + } + return len(names) + } + + before := openFds() + + for i := 0; i < 10; i++ { + laddr, _ := net.ResolveTCPAddr("tcp", net.JoinHostPort("127.0.0.1", "0")) + d := TCPDialer{ + Dialer: net.Dialer{ + LocalAddr: laddr, + Timeout: 1 * time.Second, + }, + } + if _, err := d.DialTCP("127.0.0.1", 1); err == nil { + t.Fatalf("should not succeed") + } + + } + + if after := openFds(); before != after { + t.Fatalf("could be fd leak, %d %d", before, after) + } +} diff --git a/pkg/server/sockopt_openbsd.go b/pkg/server/sockopt_openbsd.go new file mode 100644 index 00000000..f52c1447 --- /dev/null +++ b/pkg/server/sockopt_openbsd.go @@ -0,0 +1,469 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build openbsd + +package server + +import ( + "encoding/binary" + "fmt" + "net" + "os" + "syscall" + "unsafe" + + log "github.com/sirupsen/logrus" +) + +const ( + PF_KEY_V2 = 2 + + SADB_X_SATYPE_TCPSIGNATURE = 8 + + SADB_EXT_SA = 1 + SADB_EXT_ADDRESS_SRC = 5 + SADB_EXT_ADDRESS_DST = 6 + SADB_EXT_KEY_AUTH = 8 + SADB_EXT_SPIRANGE = 16 + + SADB_GETSPI = 1 + SADB_UPDATE = 2 + SADB_DELETE = 4 + + SADB_X_EALG_AES = 12 + + SADB_SASTATE_MATURE = 1 +) + +type sadbMsg struct { + sadbMsgVersion uint8 + sadbMsgType uint8 + sadbMsgErrno uint8 + sadbMsgSatype uint8 + sadbMsgLen uint16 + sadbMsgReserved uint16 + sadbMsgSeq uint32 + sadbMsgPid uint32 +} + +func (s *sadbMsg) DecodeFromBytes(data []byte) error { + if len(data) < SADB_MSG_SIZE { + fmt.Errorf("too short for sadbMsg %d", len(data)) + } + s.sadbMsgVersion = data[0] + s.sadbMsgType = data[1] + s.sadbMsgErrno = data[2] + s.sadbMsgSatype = data[3] + s.sadbMsgLen = binary.LittleEndian.Uint16(data[4:6]) + s.sadbMsgSeq = binary.LittleEndian.Uint32(data[8:12]) + s.sadbMsgPid = binary.LittleEndian.Uint32(data[12:16]) + return nil +} + +type sadbSpirange struct { + sadbSpirangeLen uint16 + sadbSpirangeExttype uint16 + sadbSpirangeMin uint32 + sadbSpirangeMax uint32 + sadbSpirangeReserved uint32 +} + +type sadbAddress struct { + sadbAddressLen uint16 + sadbAddressExttype uint16 + sadbAddressReserved uint32 +} + +type sadbExt struct { + sadbExtLen uint16 + sadbExtType uint16 +} + +type sadbSa struct { + sadbSaLen uint16 + sadbSaExttype uint16 + sadbSaSpi uint32 + sadbSaReplay uint8 + sadbSaState uint8 + sadbSaAuth uint8 + sadbSaEncrypt uint8 + sadbSaFlags uint32 +} + +type sadbKey struct { + sadbKeyLen uint16 + sadbKeyExttype uint16 + sadbKeyBits uint16 + sadbKeyReserved uint16 +} + +const ( + SADB_MSG_SIZE = int(unsafe.Sizeof(sadbMsg{})) + SADB_SPIRANGE_SIZE = int(unsafe.Sizeof(sadbSpirange{})) + SADB_ADDRESS_SIZE = int(unsafe.Sizeof(sadbAddress{})) + SADB_SA_SIZE = int(unsafe.Sizeof(sadbSa{})) + SADB_KEY_SIZE = int(unsafe.Sizeof(sadbKey{})) +) + +type sockaddrIn struct { + ssLen uint8 + ssFamily uint8 + ssPort uint16 + ssAddr uint32 + pad [8]byte +} + +func newSockaddrIn(addr string) sockaddrIn { + if len(addr) == 0 { + return sockaddrIn{ + ssLen: 16, + } + } + v := net.ParseIP(addr).To4() + return sockaddrIn{ + ssAddr: uint32(v[3])<<24 | uint32(v[2])<<16 | uint32(v[1])<<8 | uint32(v[0]), + ssLen: 16, + ssFamily: syscall.AF_INET, + } +} + +func roundUp(v int) int { + if v%8 != 0 { + v += 8 - v%8 + } + return v +} + +func b(p unsafe.Pointer, length int) []byte { + buf := make([]byte, length) + for i := 0; i < length; i++ { + buf[i] = *(*byte)(p) + p = unsafe.Pointer(uintptr(p) + 1) + } + return buf +} + +var seq uint32 +var fd int + +var spiInMap map[string]uint32 = map[string]uint32{} +var spiOutMap map[string]uint32 = map[string]uint32{} + +func pfkeyReply() (spi uint32, err error) { + buf := make([]byte, SADB_MSG_SIZE) + if count, _, _, _, _ := syscall.Recvmsg(fd, buf, nil, syscall.MSG_PEEK); count != len(buf) { + return spi, fmt.Errorf("incomplete sadb msg %d %d", len(buf), count) + } + h := sadbMsg{} + h.DecodeFromBytes(buf) + if h.sadbMsgErrno != 0 { + return spi, fmt.Errorf("sadb msg reply error %d", h.sadbMsgErrno) + } + + if h.sadbMsgSeq != seq { + return spi, fmt.Errorf("sadb msg sequence doesn't match %d %d", h.sadbMsgSeq, seq) + } + + if h.sadbMsgPid != uint32(os.Getpid()) { + return spi, fmt.Errorf("sadb msg pid doesn't match %d %d", h.sadbMsgPid, os.Getpid()) + } + + buf = make([]byte, int(8*h.sadbMsgLen)) + if count, _, _, _, _ := syscall.Recvmsg(fd, buf, nil, 0); count != len(buf) { + return spi, fmt.Errorf("incomplete sadb msg body %d %d", len(buf), count) + } + + buf = buf[SADB_MSG_SIZE:] + + for len(buf) >= 4 { + l := binary.LittleEndian.Uint16(buf[0:2]) * 8 + t := binary.LittleEndian.Uint16(buf[2:4]) + if t == SADB_EXT_SA { + return binary.LittleEndian.Uint32(buf[4:8]), nil + } + + if len(buf) <= int(l) { + break + } + buf = buf[l:] + } + return spi, err +} + +func sendSadbMsg(msg *sadbMsg, body []byte) (err error) { + if fd == 0 { + fd, err = syscall.Socket(syscall.AF_KEY, syscall.SOCK_RAW, PF_KEY_V2) + if err != nil { + return err + } + } + + seq++ + msg.sadbMsgSeq = seq + msg.sadbMsgLen = uint16((len(body) + SADB_MSG_SIZE) / 8) + + buf := append(b(unsafe.Pointer(msg), SADB_MSG_SIZE), body...) + + r, err := syscall.Write(fd, buf) + if r != len(buf) { + return fmt.Errorf("short write %d %d", r, len(buf)) + } + return err +} + +func rfkeyRequest(msgType uint8, src, dst string, spi uint32, key string) error { + h := sadbMsg{ + sadbMsgVersion: PF_KEY_V2, + sadbMsgType: msgType, + sadbMsgSatype: SADB_X_SATYPE_TCPSIGNATURE, + sadbMsgPid: uint32(os.Getpid()), + } + + ssrc := newSockaddrIn(src) + sa_src := sadbAddress{ + sadbAddressExttype: SADB_EXT_ADDRESS_SRC, + sadbAddressLen: uint16(SADB_ADDRESS_SIZE+roundUp(int(ssrc.ssLen))) / 8, + } + + sdst := newSockaddrIn(dst) + sa_dst := sadbAddress{ + sadbAddressExttype: SADB_EXT_ADDRESS_DST, + sadbAddressLen: uint16(SADB_ADDRESS_SIZE+roundUp(int(sdst.ssLen))) / 8, + } + + buf := make([]byte, 0) + switch msgType { + case SADB_UPDATE, SADB_DELETE: + sa := sadbSa{ + sadbSaLen: uint16(SADB_SA_SIZE / 8), + sadbSaExttype: SADB_EXT_SA, + sadbSaSpi: spi, + sadbSaState: SADB_SASTATE_MATURE, + sadbSaEncrypt: SADB_X_EALG_AES, + } + buf = append(buf, b(unsafe.Pointer(&sa), SADB_SA_SIZE)...) + case SADB_GETSPI: + spirange := sadbSpirange{ + sadbSpirangeLen: uint16(SADB_SPIRANGE_SIZE) / 8, + sadbSpirangeExttype: SADB_EXT_SPIRANGE, + sadbSpirangeMin: 0x100, + sadbSpirangeMax: 0xffffffff, + } + buf = append(buf, b(unsafe.Pointer(&spirange), SADB_SPIRANGE_SIZE)...) + } + + buf = append(buf, b(unsafe.Pointer(&sa_dst), SADB_ADDRESS_SIZE)...) + buf = append(buf, b(unsafe.Pointer(&sdst), roundUp(int(sdst.ssLen)))...) + buf = append(buf, b(unsafe.Pointer(&sa_src), SADB_ADDRESS_SIZE)...) + buf = append(buf, b(unsafe.Pointer(&ssrc), roundUp(int(ssrc.ssLen)))...) + + switch msgType { + case SADB_UPDATE: + keylen := roundUp(len(key)) + sa_akey := sadbKey{ + sadbKeyLen: uint16((SADB_KEY_SIZE + keylen) / 8), + sadbKeyExttype: SADB_EXT_KEY_AUTH, + sadbKeyBits: uint16(len(key) * 8), + } + k := []byte(key) + if pad := keylen - len(k); pad != 0 { + k = append(k, make([]byte, pad)...) + } + buf = append(buf, b(unsafe.Pointer(&sa_akey), SADB_KEY_SIZE)...) + buf = append(buf, k...) + } + + return sendSadbMsg(&h, buf) +} + +func saAdd(address, key string) error { + f := func(src, dst string) error { + if err := rfkeyRequest(SADB_GETSPI, src, dst, 0, ""); err != nil { + return err + } + spi, err := pfkeyReply() + if err != nil { + return err + } + if src == "" { + spiOutMap[address] = spi + } else { + spiInMap[address] = spi + } + + if err := rfkeyRequest(SADB_UPDATE, src, dst, spi, key); err != nil { + return err + } + _, err = pfkeyReply() + return err + } + + if err := f(address, ""); err != nil { + return err + } + + return f("", address) +} + +func saDelete(address string) error { + if spi, y := spiInMap[address]; y { + if err := rfkeyRequest(SADB_DELETE, address, "", spi, ""); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": address, + }).Info("failed to delete md5 for incoming") + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": address, + }).Info("can't find spi for md5 for incoming") + } + } + if spi, y := spiOutMap[address]; y { + if err := rfkeyRequest(SADB_DELETE, "", address, spi, ""); err != nil { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": address, + }).Info("failed to delete md5 for outgoing") + } else { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": address, + }).Info("can't find spi for md5 for outgoing") + } + } + return nil +} + +const ( + TCP_MD5SIG = 0x4 // TCP MD5 Signature (RFC2385) + IPV6_MINHOPCOUNT = 73 // Generalized TTL Security Mechanism (RFC5082) +) + +func setsockoptTcpMD5Sig(fd int, address string, key string) error { + if err := syscall.SetsockoptInt(fd, syscall.IPPROTO_TCP, TCP_MD5SIG, 1); err != nil { + return os.NewSyscallError("setsockopt", err) + } + if len(key) > 0 { + return saAdd(address, key) + } + return saDelete(address) +} + +func SetTcpMD5SigSockopt(l *net.TCPListener, address string, key string) error { + fi, _, err := extractFileAndFamilyFromTCPListener(l) + defer fi.Close() + if err != nil { + return err + } + return setsockoptTcpMD5Sig(int(fi.Fd()), address, key) +} + +func setsockoptIpTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_TTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = syscall.IPV6_UNICAST_HOPS + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func SetListenTcpTTLSockopt(l *net.TCPListener, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPListener(l) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func SetTcpTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIpTtl(int(fi.Fd()), family, ttl) +} + +func setsockoptIpMinTtl(fd int, family int, value int) error { + level := syscall.IPPROTO_IP + name := syscall.IP_MINTTL + if family == syscall.AF_INET6 { + level = syscall.IPPROTO_IPV6 + name = IPV6_MINHOPCOUNT + } + return os.NewSyscallError("setsockopt", syscall.SetsockoptInt(fd, level, name, value)) +} + +func SetTcpMinTTLSockopt(conn *net.TCPConn, ttl int) error { + fi, family, err := extractFileAndFamilyFromTCPConn(conn) + defer fi.Close() + if err != nil { + return err + } + return setsockoptIpMinTtl(int(fi.Fd()), family, ttl) +} + +type TCPDialer struct { + net.Dialer + + // MD5 authentication password. + AuthPassword string + + // The TTL value to set outgoing connection. + Ttl uint8 + + // The minimum TTL value for incoming packets. + TtlMin uint8 +} + +func (d *TCPDialer) DialTCP(addr string, port int) (*net.TCPConn, error) { + if d.AuthPassword != "" { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warn("setting md5 for active connection is not supported") + } + if d.Ttl != 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warn("setting ttl for active connection is not supported") + } + if d.TtlMin != 0 { + log.WithFields(log.Fields{ + "Topic": "Peer", + "Key": addr, + }).Warn("setting min ttl for active connection is not supported") + } + + raddr, err := net.ResolveTCPAddr("tcp", net.JoinHostPort(addr, fmt.Sprintf("%d", port))) + if err != nil { + return nil, fmt.Errorf("invalid remote address: %s", err) + } + laddr, err := net.ResolveTCPAddr("tcp", d.LocalAddr.String()) + if err != nil { + return nil, fmt.Errorf("invalid local address: %s", err) + } + + dialer := net.Dialer{LocalAddr: laddr, Timeout: d.Timeout} + conn, err := dialer.Dial("tcp", raddr.String()) + if err != nil { + return nil, err + } + return conn.(*net.TCPConn), nil +} diff --git a/pkg/server/sockopt_stub.go b/pkg/server/sockopt_stub.go new file mode 100644 index 00000000..9e888dc5 --- /dev/null +++ b/pkg/server/sockopt_stub.go @@ -0,0 +1,38 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +build !linux,!dragonfly,!freebsd,!netbsd,!openbsd,!darwin + +package server + +import ( + "fmt" + "net" +) + +func setTcpMD5SigSockopt(l *net.TCPListener, address string, key string) error { + return fmt.Errorf("setting md5 is not supported") +} + +func setListenTcpTTLSockopt(l *net.TCPListener, ttl int) error { + return fmt.Errorf("setting ttl is not supported") +} + +func setTcpTTLSockopt(conn *net.TCPConn, ttl int) error { + return fmt.Errorf("setting ttl is not supported") +} + +func setTcpMinTTLSockopt(conn *net.TCPConn, ttl int) error { + return fmt.Errorf("setting min ttl is not supported") +} diff --git a/pkg/server/util.go b/pkg/server/util.go new file mode 100644 index 00000000..ba6fe70a --- /dev/null +++ b/pkg/server/util.go @@ -0,0 +1,117 @@ +// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "net" + "os" + "strings" + "syscall" + + "github.com/eapache/channels" + + "github.com/osrg/gobgp/pkg/packet/bgp" +) + +func cleanInfiniteChannel(ch *channels.InfiniteChannel) { + ch.Close() + // drain all remaining items + for range ch.Out() { + } +} + +// Returns the binary formatted Administrative Shutdown Communication from the +// given string value. +func newAdministrativeCommunication(communication string) (data []byte) { + if communication == "" { + return nil + } + com := []byte(communication) + if len(com) > bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX { + data = []byte{bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX} + data = append(data, com[:bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX]...) + } else { + data = []byte{byte(len(com))} + data = append(data, com...) + } + return data +} + +// Parses the given NOTIFICATION message data as a binary value and returns +// the Administrative Shutdown Communication in string and the rest binary. +func decodeAdministrativeCommunication(data []byte) (string, []byte) { + if len(data) == 0 { + return "", data + } + communicationLen := int(data[0]) + if communicationLen > bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX { + communicationLen = bgp.BGP_ERROR_ADMINISTRATIVE_COMMUNICATION_MAX + } + if communicationLen > len(data)+1 { + communicationLen = len(data) + 1 + } + return string(data[1 : communicationLen+1]), data[communicationLen+1:] +} + +func extractFileAndFamilyFromTCPListener(l *net.TCPListener) (*os.File, int, error) { + // Note #1: TCPListener.File() has the unexpected side-effect of putting + // the original socket into blocking mode. See Note #2. + fi, err := l.File() + if err != nil { + return nil, 0, err + } + + // Note #2: Call net.FileListener() to put the original socket back into + // non-blocking mode. + fl, err := net.FileListener(fi) + if err != nil { + fi.Close() + return nil, 0, err + } + fl.Close() + + family := syscall.AF_INET + if strings.Contains(l.Addr().String(), "[") { + family = syscall.AF_INET6 + } + + return fi, family, nil +} + +func extractFileAndFamilyFromTCPConn(conn *net.TCPConn) (*os.File, int, error) { + // Note #1: TCPConn.File() has the unexpected side-effect of putting + // the original socket into blocking mode. See Note #2. + fi, err := conn.File() + if err != nil { + return nil, 0, err + } + + // Note #2: Call net.FileConn() to put the original socket back into + // non-blocking mode. + fc, err := net.FileConn(fi) + if err != nil { + fi.Close() + return nil, 0, err + } + fc.Close() + + family := syscall.AF_INET + if strings.Contains(conn.RemoteAddr().String(), "[") { + family = syscall.AF_INET6 + } + + return fi, family, nil +} diff --git a/pkg/server/zclient.go b/pkg/server/zclient.go new file mode 100644 index 00000000..3ef057ce --- /dev/null +++ b/pkg/server/zclient.go @@ -0,0 +1,450 @@ +// Copyright (C) 2015 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "fmt" + "math" + "net" + "strconv" + "strings" + "syscall" + "time" + + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/internal/pkg/zebra" + "github.com/osrg/gobgp/pkg/packet/bgp" + + log "github.com/sirupsen/logrus" +) + +// nexthopStateCache stores a map of nexthop IP to metric value. Especially, +// the metric value of math.MaxUint32 means the nexthop is unreachable. +type nexthopStateCache map[string]uint32 + +func (m nexthopStateCache) applyToPathList(paths []*table.Path) []*table.Path { + updated := make([]*table.Path, 0, len(paths)) + for _, path := range paths { + if path == nil || path.IsWithdraw { + continue + } + metric, ok := m[path.GetNexthop().String()] + if !ok { + continue + } + isNexthopInvalid := metric == math.MaxUint32 + med, err := path.GetMed() + if err == nil && med == metric && path.IsNexthopInvalid == isNexthopInvalid { + // If the nexthop state of the given path is already up to date, + // skips this path. + continue + } + newPath := path.Clone(false) + if isNexthopInvalid { + newPath.IsNexthopInvalid = true + } else { + newPath.IsNexthopInvalid = false + newPath.SetMed(int64(metric), true) + } + updated = append(updated, newPath) + } + return updated +} + +func (m nexthopStateCache) updateByNexthopUpdate(body *zebra.NexthopUpdateBody) (updated bool) { + if len(body.Nexthops) == 0 { + // If NEXTHOP_UPDATE message does not contain any nexthop, the given + // nexthop is unreachable. + if _, ok := m[body.Prefix.String()]; !ok { + // Zebra will send an empty NEXTHOP_UPDATE message as the fist + // response for the NEXTHOP_REGISTER message. Here ignores it. + return false + } + m[body.Prefix.String()] = math.MaxUint32 // means unreachable + } else { + m[body.Prefix.String()] = body.Metric + } + return true +} + +func (m nexthopStateCache) filterPathToRegister(paths []*table.Path) []*table.Path { + filteredPaths := make([]*table.Path, 0, len(paths)) + for _, path := range paths { + // Here filters out: + // - Nil path + // - Withdrawn path + // - External path (advertised from Zebra) in order avoid sending back + // - Unspecified nexthop address + // - Already registered nexthop + if path == nil || path.IsWithdraw || path.IsFromExternal() { + continue + } else if nexthop := path.GetNexthop(); nexthop.IsUnspecified() { + continue + } else if _, ok := m[nexthop.String()]; ok { + continue + } + filteredPaths = append(filteredPaths, path) + } + return filteredPaths +} + +func filterOutExternalPath(paths []*table.Path) []*table.Path { + filteredPaths := make([]*table.Path, 0, len(paths)) + for _, path := range paths { + // Here filters out: + // - Nil path + // - External path (advertised from Zebra) in order avoid sending back + // - Unreachable path because invalidated by Zebra + if path == nil || path.IsFromExternal() || path.IsNexthopInvalid { + continue + } + filteredPaths = append(filteredPaths, path) + } + return filteredPaths +} + +func newIPRouteBody(dst []*table.Path) (body *zebra.IPRouteBody, isWithdraw bool) { + paths := filterOutExternalPath(dst) + if len(paths) == 0 { + return nil, false + } + path := paths[0] + + l := strings.SplitN(path.GetNlri().String(), "/", 2) + var prefix net.IP + nexthops := make([]net.IP, 0, len(paths)) + switch path.GetRouteFamily() { + case bgp.RF_IPv4_UC, bgp.RF_IPv4_VPN: + if path.GetRouteFamily() == bgp.RF_IPv4_UC { + prefix = path.GetNlri().(*bgp.IPAddrPrefix).IPAddrPrefixDefault.Prefix.To4() + } else { + prefix = path.GetNlri().(*bgp.LabeledVPNIPAddrPrefix).IPAddrPrefixDefault.Prefix.To4() + } + for _, p := range paths { + nexthops = append(nexthops, p.GetNexthop().To4()) + } + case bgp.RF_IPv6_UC, bgp.RF_IPv6_VPN: + if path.GetRouteFamily() == bgp.RF_IPv6_UC { + prefix = path.GetNlri().(*bgp.IPv6AddrPrefix).IPAddrPrefixDefault.Prefix.To16() + } else { + prefix = path.GetNlri().(*bgp.LabeledVPNIPv6AddrPrefix).IPAddrPrefixDefault.Prefix.To16() + } + for _, p := range paths { + nexthops = append(nexthops, p.GetNexthop().To16()) + } + default: + return nil, false + } + msgFlags := zebra.MESSAGE_NEXTHOP + plen, _ := strconv.ParseUint(l[1], 10, 8) + med, err := path.GetMed() + if err == nil { + msgFlags |= zebra.MESSAGE_METRIC + } + var flags zebra.FLAG + info := path.GetSource() + if info.AS == info.LocalAS { + flags = zebra.FLAG_IBGP | zebra.FLAG_INTERNAL + } else if info.MultihopTtl > 0 { + flags = zebra.FLAG_INTERNAL + } + return &zebra.IPRouteBody{ + Type: zebra.ROUTE_BGP, + Flags: flags, + SAFI: zebra.SAFI_UNICAST, + Message: msgFlags, + Prefix: prefix, + PrefixLength: uint8(plen), + Nexthops: nexthops, + Metric: med, + }, path.IsWithdraw +} + +func newNexthopRegisterBody(paths []*table.Path, nexthopCache nexthopStateCache) *zebra.NexthopRegisterBody { + paths = nexthopCache.filterPathToRegister(paths) + if len(paths) == 0 { + return nil + } + path := paths[0] + + family := path.GetRouteFamily() + nexthops := make([]*zebra.RegisteredNexthop, 0, len(paths)) + for _, p := range paths { + nexthop := p.GetNexthop() + var nh *zebra.RegisteredNexthop + switch family { + case bgp.RF_IPv4_UC, bgp.RF_IPv4_VPN: + nh = &zebra.RegisteredNexthop{ + Family: syscall.AF_INET, + Prefix: nexthop.To4(), + } + case bgp.RF_IPv6_UC, bgp.RF_IPv6_VPN: + nh = &zebra.RegisteredNexthop{ + Family: syscall.AF_INET6, + Prefix: nexthop.To16(), + } + default: + continue + } + nexthops = append(nexthops, nh) + } + + // If no nexthop needs to be registered or unregistered, skips to send + // message. + if len(nexthops) == 0 { + return nil + } + + return &zebra.NexthopRegisterBody{ + Nexthops: nexthops, + } +} + +func newNexthopUnregisterBody(family uint16, prefix net.IP) *zebra.NexthopRegisterBody { + return &zebra.NexthopRegisterBody{ + Nexthops: []*zebra.RegisteredNexthop{{ + Family: family, + Prefix: prefix, + }}, + } +} + +func newPathFromIPRouteMessage(m *zebra.Message) *table.Path { + header := m.Header + body := m.Body.(*zebra.IPRouteBody) + family := body.RouteFamily() + isWithdraw := body.IsWithdraw() + + var nlri bgp.AddrPrefixInterface + pattr := make([]bgp.PathAttributeInterface, 0) + origin := bgp.NewPathAttributeOrigin(bgp.BGP_ORIGIN_ATTR_TYPE_IGP) + pattr = append(pattr, origin) + + log.WithFields(log.Fields{ + "Topic": "Zebra", + "RouteType": body.Type.String(), + "Flag": body.Flags.String(), + "Message": body.Message, + "Prefix": body.Prefix, + "PrefixLength": body.PrefixLength, + "Nexthop": body.Nexthops, + "IfIndex": body.Ifindexs, + "Metric": body.Metric, + "Distance": body.Distance, + "Mtu": body.Mtu, + "api": header.Command.String(), + }).Debugf("create path from ip route message.") + + switch family { + case bgp.RF_IPv4_UC: + nlri = bgp.NewIPAddrPrefix(body.PrefixLength, body.Prefix.String()) + if len(body.Nexthops) > 0 { + pattr = append(pattr, bgp.NewPathAttributeNextHop(body.Nexthops[0].String())) + } + case bgp.RF_IPv6_UC: + nlri = bgp.NewIPv6AddrPrefix(body.PrefixLength, body.Prefix.String()) + nexthop := "" + if len(body.Nexthops) > 0 { + nexthop = body.Nexthops[0].String() + } + pattr = append(pattr, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri})) + default: + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Errorf("unsupport address family: %s", family) + return nil + } + + med := bgp.NewPathAttributeMultiExitDisc(body.Metric) + pattr = append(pattr, med) + + path := table.NewPath(nil, nlri, isWithdraw, pattr, time.Now(), false) + path.SetIsFromExternal(true) + return path +} + +type zebraClient struct { + client *zebra.Client + server *BgpServer + nexthopCache nexthopStateCache + dead chan struct{} +} + +func (z *zebraClient) getPathListWithNexthopUpdate(body *zebra.NexthopUpdateBody) []*table.Path { + rib := &table.TableManager{ + Tables: make(map[bgp.RouteFamily]*table.Table), + } + + var rfList []bgp.RouteFamily + switch body.Family { + case uint16(syscall.AF_INET): + rfList = []bgp.RouteFamily{bgp.RF_IPv4_UC, bgp.RF_IPv4_VPN} + case uint16(syscall.AF_INET6): + rfList = []bgp.RouteFamily{bgp.RF_IPv6_UC, bgp.RF_IPv6_VPN} + } + + for _, rf := range rfList { + tbl, _, err := z.server.GetRib("", rf, nil) + if err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Family": rf.String(), + "Error": err, + }).Error("failed to get global rib") + continue + } + rib.Tables[rf] = tbl + } + + return rib.GetPathListWithNexthop(table.GLOBAL_RIB_NAME, rfList, body.Prefix) +} + +func (z *zebraClient) updatePathByNexthopCache(paths []*table.Path) { + paths = z.nexthopCache.applyToPathList(paths) + if len(paths) > 0 { + if err := z.server.UpdatePath("", paths); err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "PathList": paths, + }).Error("failed to update nexthop reachability") + } + } +} + +func (z *zebraClient) loop() { + w := z.server.Watch([]WatchOption{ + WatchBestPath(true), + WatchPostUpdate(true), + }...) + defer w.Stop() + + for { + select { + case <-z.dead: + return + case msg := <-z.client.Receive(): + switch body := msg.Body.(type) { + case *zebra.IPRouteBody: + if path := newPathFromIPRouteMessage(msg); path != nil { + if _, err := z.server.AddPath("", []*table.Path{path}); err != nil { + log.WithFields(log.Fields{ + "Topic": "Zebra", + "Path": path, + "Error": err, + }).Error("failed to add path from zebra") + } + } + case *zebra.NexthopUpdateBody: + if updated := z.nexthopCache.updateByNexthopUpdate(body); !updated { + continue + } + paths := z.getPathListWithNexthopUpdate(body) + if len(paths) == 0 { + // If there is no path bound for the given nexthop, send + // NEXTHOP_UNREGISTER message. + z.client.SendNexthopRegister(msg.Header.VrfId, newNexthopUnregisterBody(body.Family, body.Prefix), true) + delete(z.nexthopCache, body.Prefix.String()) + } + z.updatePathByNexthopCache(paths) + } + case ev := <-w.Event(): + switch msg := ev.(type) { + case *WatchEventBestPath: + if table.UseMultiplePaths.Enabled { + for _, paths := range msg.MultiPathList { + z.updatePathByNexthopCache(paths) + if body, isWithdraw := newIPRouteBody(paths); body != nil { + z.client.SendIPRoute(0, body, isWithdraw) + } + if body := newNexthopRegisterBody(paths, z.nexthopCache); body != nil { + z.client.SendNexthopRegister(0, body, false) + } + } + } else { + z.updatePathByNexthopCache(msg.PathList) + for _, path := range msg.PathList { + vrfs := []uint16{0} + if msg.Vrf != nil { + if v, ok := msg.Vrf[path.GetNlri().String()]; ok { + vrfs = append(vrfs, v) + } + } + for _, i := range vrfs { + if body, isWithdraw := newIPRouteBody([]*table.Path{path}); body != nil { + z.client.SendIPRoute(i, body, isWithdraw) + } + if body := newNexthopRegisterBody([]*table.Path{path}, z.nexthopCache); body != nil { + z.client.SendNexthopRegister(i, body, false) + } + } + } + } + case *WatchEventUpdate: + if body := newNexthopRegisterBody(msg.PathList, z.nexthopCache); body != nil { + vrfID := uint16(0) + for _, vrf := range z.server.GetVrf() { + if vrf.Name == msg.Neighbor.Config.Vrf { + vrfID = uint16(vrf.Id) + } + } + z.client.SendNexthopRegister(vrfID, body, false) + } + } + } + } +} + +func newZebraClient(s *BgpServer, url string, protos []string, version uint8, nhtEnable bool, nhtDelay uint8) (*zebraClient, error) { + l := strings.SplitN(url, ":", 2) + if len(l) != 2 { + return nil, fmt.Errorf("unsupported url: %s", url) + } + var cli *zebra.Client + var err error + for _, ver := range []uint8{version, 2, 3, 4} { + cli, err = zebra.NewClient(l[0], l[1], zebra.ROUTE_BGP, ver) + if err == nil { + break + } + // Retry with another Zebra message version + log.WithFields(log.Fields{ + "Topic": "Zebra", + }).Warnf("cannot connect to Zebra with message version %d. going to retry another version...", ver) + } + if cli == nil { + return nil, err + } + // Note: HELLO/ROUTER_ID_ADD messages are automatically sent to negotiate + // the Zebra message version in zebra.NewClient(). + // cli.SendHello() + // cli.SendRouterIDAdd() + cli.SendInterfaceAdd() + for _, typ := range protos { + t, err := zebra.RouteTypeFromString(typ) + if err != nil { + return nil, err + } + cli.SendRedistribute(t, zebra.VRF_DEFAULT) + } + w := &zebraClient{ + client: cli, + server: s, + nexthopCache: make(nexthopStateCache), + dead: make(chan struct{}), + } + go w.loop() + return w, nil +} diff --git a/pkg/server/zclient_test.go b/pkg/server/zclient_test.go new file mode 100644 index 00000000..c7868c0b --- /dev/null +++ b/pkg/server/zclient_test.go @@ -0,0 +1,113 @@ +// Copyright (C) 2014 Nippon Telegraph and Telephone Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package server + +import ( + "net" + "testing" + "time" + + "github.com/stretchr/testify/assert" + + "github.com/osrg/gobgp/internal/pkg/table" + "github.com/osrg/gobgp/internal/pkg/zebra" +) + +func Test_newPathFromIPRouteMessage(t *testing.T) { + assert := assert.New(t) + + // IPv4 Route Add + m := &zebra.Message{} + h := &zebra.Header{ + Len: zebra.HeaderSize(2), + Marker: zebra.HEADER_MARKER, + Version: 2, + Command: zebra.IPV4_ROUTE_ADD, + } + b := &zebra.IPRouteBody{ + Type: zebra.ROUTE_TYPE(zebra.ROUTE_STATIC), + Flags: zebra.FLAG(zebra.FLAG_SELECTED), + Message: zebra.MESSAGE_NEXTHOP | zebra.MESSAGE_DISTANCE | zebra.MESSAGE_METRIC | zebra.MESSAGE_MTU, + SAFI: zebra.SAFI(zebra.SAFI_UNICAST), + Prefix: net.ParseIP("192.168.100.0"), + PrefixLength: uint8(24), + Nexthops: []net.IP{net.ParseIP("0.0.0.0")}, + Ifindexs: []uint32{1}, + Distance: uint8(0), + Metric: uint32(100), + Mtu: uint32(0), + Api: zebra.API_TYPE(zebra.IPV4_ROUTE_ADD), + } + m.Header = *h + m.Body = b + + path := newPathFromIPRouteMessage(m) + pp := table.NewPath(nil, path.GetNlri(), path.IsWithdraw, path.GetPathAttrs(), time.Now(), false) + pp.SetIsFromExternal(path.IsFromExternal()) + assert.Equal("0.0.0.0", pp.GetNexthop().String()) + assert.Equal("192.168.100.0/24", pp.GetNlri().String()) + assert.True(pp.IsFromExternal()) + assert.False(pp.IsWithdraw) + + // IPv4 Route Delete + h.Command = zebra.IPV4_ROUTE_DELETE + b.Api = zebra.IPV4_ROUTE_DELETE + m.Header = *h + m.Body = b + + path = newPathFromIPRouteMessage(m) + pp = table.NewPath(nil, path.GetNlri(), path.IsWithdraw, path.GetPathAttrs(), time.Now(), false) + pp.SetIsFromExternal(path.IsFromExternal()) + assert.Equal("0.0.0.0", pp.GetNexthop().String()) + assert.Equal("192.168.100.0/24", pp.GetNlri().String()) + med, _ := pp.GetMed() + assert.Equal(uint32(100), med) + assert.True(pp.IsFromExternal()) + assert.True(pp.IsWithdraw) + + // IPv6 Route Add + h.Command = zebra.IPV6_ROUTE_ADD + b.Api = zebra.IPV6_ROUTE_ADD + b.Prefix = net.ParseIP("2001:db8:0:f101::") + b.PrefixLength = uint8(64) + b.Nexthops = []net.IP{net.ParseIP("::")} + m.Header = *h + m.Body = b + + path = newPathFromIPRouteMessage(m) + pp = table.NewPath(nil, path.GetNlri(), path.IsWithdraw, path.GetPathAttrs(), time.Now(), false) + pp.SetIsFromExternal(path.IsFromExternal()) + assert.Equal("::", pp.GetNexthop().String()) + assert.Equal("2001:db8:0:f101::/64", pp.GetNlri().String()) + med, _ = pp.GetMed() + assert.Equal(uint32(100), med) + assert.True(pp.IsFromExternal()) + assert.False(pp.IsWithdraw) + + // IPv6 Route Delete + h.Command = zebra.IPV6_ROUTE_DELETE + b.Api = zebra.IPV6_ROUTE_DELETE + m.Header = *h + m.Body = b + + path = newPathFromIPRouteMessage(m) + pp = table.NewPath(nil, path.GetNlri(), path.IsWithdraw, path.GetPathAttrs(), time.Now(), false) + pp.SetIsFromExternal(path.IsFromExternal()) + assert.Equal("::", pp.GetNexthop().String()) + assert.Equal("2001:db8:0:f101::/64", pp.GetNlri().String()) + assert.True(pp.IsFromExternal()) + assert.True(pp.IsWithdraw) +} |