summaryrefslogtreecommitdiffhomepage
path: root/pkg
diff options
context:
space:
mode:
authorFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2018-07-07 13:48:38 +0900
committerFUJITA Tomonori <fujita.tomonori@lab.ntt.co.jp>2018-07-07 20:44:25 +0900
commitc4775c42510d1f1ddd55036dc19e982712fa6a0b (patch)
tree6ec8b61d4338c809e239e3003a2d32d480898e22 /pkg
parentb3079759aa13172fcb548a83da9a9653d8d5fed4 (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')
-rw-r--r--pkg/packet/bgp/bgp.go9674
-rw-r--r--pkg/packet/bgp/bgp_race_test.go46
-rw-r--r--pkg/packet/bgp/bgp_test.go1194
-rw-r--r--pkg/packet/bgp/bgpattrtype_string.go28
-rw-r--r--pkg/packet/bgp/constant.go327
-rw-r--r--pkg/packet/bgp/esitype_string.go16
-rw-r--r--pkg/packet/bgp/fsmstate_string.go16
-rw-r--r--pkg/packet/bgp/helper.go126
-rw-r--r--pkg/packet/bgp/validate.go337
-rw-r--r--pkg/packet/bgp/validate_test.go423
-rw-r--r--pkg/packet/bmp/bmp.go1072
-rw-r--r--pkg/packet/bmp/bmp_test.go105
-rw-r--r--pkg/packet/mrt/mrt.go1006
-rw-r--r--pkg/packet/mrt/mrt_test.go302
-rw-r--r--pkg/packet/rtr/rtr.go392
-rw-r--r--pkg/packet/rtr/rtr_test.go118
-rw-r--r--pkg/server/bmp.go358
-rw-r--r--pkg/server/collector.go222
-rw-r--r--pkg/server/fsm.go1752
-rw-r--r--pkg/server/fsm_test.go345
-rw-r--r--pkg/server/grpc_server.go3016
-rw-r--r--pkg/server/mrt.go409
-rw-r--r--pkg/server/peer.go522
-rw-r--r--pkg/server/rpki.go712
-rw-r--r--pkg/server/rpki_test.go257
-rw-r--r--pkg/server/server.go3048
-rw-r--r--pkg/server/server_test.go706
-rw-r--r--pkg/server/sockopt.go90
-rw-r--r--pkg/server/sockopt_bsd.go89
-rw-r--r--pkg/server/sockopt_darwin.go64
-rw-r--r--pkg/server/sockopt_linux.go282
-rw-r--r--pkg/server/sockopt_linux_test.go106
-rw-r--r--pkg/server/sockopt_openbsd.go469
-rw-r--r--pkg/server/sockopt_stub.go38
-rw-r--r--pkg/server/util.go117
-rw-r--r--pkg/server/zclient.go450
-rw-r--r--pkg/server/zclient_test.go113
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)
+}