예제 #1
0
파일: policy.go 프로젝트: ttaanngg/gobgp
func (a *AsPathPrependAction) apply(path *table.Path) *table.Path {

	var asn uint32
	if a.useLeftMost {
		asns := path.GetAsSeqList()
		if len(asns) == 0 {
			log.WithFields(log.Fields{
				"Topic": "Policy",
				"Type":  "AsPathPrepend Action",
			}).Error("aspath length is zero.")
			return path
		}
		asn = asns[0]
		log.WithFields(log.Fields{
			"Topic":  "Policy",
			"Type":   "AsPathPrepend Action",
			"LastAs": asn,
			"Repeat": a.repeat,
		}).Debug("use last AS.")
	} else {
		asn = a.asn
	}

	path.PrependAsn(asn, a.repeat)
	return path
}
예제 #2
0
파일: policy.go 프로젝트: ttaanngg/gobgp
// Compare path with a policy's condition in stored order in the policy.
// If a condition match, then this function stops evaluation and
// subsequent conditions are skipped.
func (p *Policy) Apply(path *table.Path) (bool, RouteType, *table.Path) {
	for _, statement := range p.Statements {

		result := statement.evaluate(path)
		log.WithFields(log.Fields{
			"Topic":      "Policy",
			"Path":       path,
			"PolicyName": p.Name,
		}).Debug("statement.Conditions.evaluate : ", result)

		var p *table.Path
		if result {
			//Routing action
			p = statement.routingAction.apply(path)
			if p != nil {
				// apply all modification actions
				cloned := path.Clone(p.IsWithdraw)
				for _, action := range statement.modificationActions {
					cloned = action.apply(cloned)
				}
				return true, ROUTE_TYPE_ACCEPT, cloned
			} else {
				return true, ROUTE_TYPE_REJECT, nil
			}
		}
	}
	return false, ROUTE_TYPE_NONE, nil
}
예제 #3
0
파일: peer.go 프로젝트: ttaanngg/gobgp
func applyPolicy(component, owner string, d Direction, policies []*policy.Policy, original *table.Path) (bool, *table.Path) {
	var applied bool = true
	for _, pol := range policies {
		if result, action, newpath := pol.Apply(original); result {
			log.Debug("newpath: ", newpath)
			if action == policy.ROUTE_TYPE_REJECT {
				log.WithFields(log.Fields{
					"Topic": component,
					"Key":   owner,
					"NRLI":  original.GetNlri(),
					"Dir":   d,
				}).Debug("path was rejected")
				// return applied, nil, this means path was rejected
				return applied, nil
			} else {
				// return applied, new path
				return applied, newpath
			}
		}
	}

	log.WithFields(log.Fields{
		"Topic": component,
		"Key":   owner,
		"Len":   len(policies),
		"NRLI":  original,
		"Dir":   d,
	}).Debug("no policy applied")
	return !applied, original
}
예제 #4
0
파일: policy.go 프로젝트: ttaanngg/gobgp
// compare AS_PATH length in the message's AS_PATH attribute with
// the one in condition.
func (c *AsPathLengthCondition) evaluate(path *table.Path) bool {

	length := uint32(path.GetAsPathLen())
	result := false

	switch c.Operator {
	case ATTRIBUTE_EQ:
		result = c.Value == length

	case ATTRIBUTE_GE:
		result = c.Value <= length

	case ATTRIBUTE_LE:
		result = c.Value >= length
	default:
		return false
	}

	if result {
		log.WithFields(log.Fields{
			"Topic":     "Policy",
			"Condition": "aspath length",
			"Reason":    c.Operator,
		}).Debug("condition matched")
	}

	return result
}
예제 #5
0
func newIPRouteMessage(path *table.Path) *zebra.Message {
	l := strings.SplitN(path.GetNlri().String(), "/", 2)
	var command zebra.API_TYPE
	var prefix net.IP
	nexthops := []net.IP{}
	switch path.GetRouteFamily() {
	case bgp.RF_IPv4_UC:
		if path.IsWithdraw == true {
			command = zebra.IPV4_ROUTE_DELETE
		} else {
			command = zebra.IPV4_ROUTE_ADD
		}
		prefix = net.ParseIP(l[0]).To4()
		nexthops = append(nexthops, path.GetNexthop().To4())
	case bgp.RF_IPv6_UC:
		if path.IsWithdraw == true {
			command = zebra.IPV6_ROUTE_DELETE
		} else {
			command = zebra.IPV6_ROUTE_ADD
		}
		prefix = net.ParseIP(l[0]).To16()
		nexthops = append(nexthops, path.GetNexthop().To16())
	default:
		return nil
	}

	flags := uint8(zebra.MESSAGE_NEXTHOP)
	plen, _ := strconv.Atoi(l[1])
	med, err := path.GetMed()
	if err == nil {
		flags |= zebra.MESSAGE_METRIC
	}
	return &zebra.Message{
		Header: zebra.Header{
			Len:     zebra.HEADER_SIZE,
			Marker:  zebra.HEADER_MARKER,
			Version: zebra.VERSION,
			Command: command,
		},
		Body: &zebra.IPRouteBody{
			Type:         zebra.ROUTE_BGP,
			SAFI:         zebra.SAFI_UNICAST,
			Message:      flags,
			Prefix:       prefix,
			PrefixLength: uint8(plen),
			Nexthops:     nexthops,
			Metric:       med,
		},
	}
}
예제 #6
0
파일: policy.go 프로젝트: ttaanngg/gobgp
func ipPrefixCalculate(path *table.Path, cPrefix Prefix) bool {
	rf := path.GetRouteFamily()
	log.Debug("path routefamily : ", rf.String())
	var pAddr net.IP
	var pMasklen uint8

	if rf != cPrefix.AddressFamily {
		return false
	}

	switch rf {
	case bgp.RF_IPv4_UC:
		pAddr = path.GetNlri().(*bgp.NLRInfo).IPAddrPrefix.Prefix
		pMasklen = path.GetNlri().(*bgp.NLRInfo).IPAddrPrefix.Length
	case bgp.RF_IPv6_UC:
		pAddr = path.GetNlri().(*bgp.IPv6AddrPrefix).Prefix
		pMasklen = path.GetNlri().(*bgp.IPv6AddrPrefix).Length
	default:
		return false
	}

	cp := fmt.Sprintf("%s/%d", cPrefix.Address, cPrefix.Masklength)
	rMin, okMin := cPrefix.MasklengthRange[MASK_LENGTH_RANGE_MIN]
	rMax, okMax := cPrefix.MasklengthRange[MASK_LENGTH_RANGE_MAX]
	if !okMin && !okMax {
		if pAddr.Equal(cPrefix.Address) && pMasklen == cPrefix.Masklength {
			return true
		} else {
			return false
		}
	}

	_, ipNet, e := net.ParseCIDR(cp)
	if e != nil {
		log.WithFields(log.Fields{
			"Topic":  "Policy",
			"Prefix": ipNet,
			"Error":  e,
		}).Error("failed to parse the prefix of condition")
		return false
	}
	if ipNet.Contains(pAddr) && (rMin <= pMasklen && pMasklen <= rMax) {
		return true
	}
	return false
}
예제 #7
0
파일: policy.go 프로젝트: ttaanngg/gobgp
// compare AS_PATH in the message's AS_PATH attribute with
// the one in condition.
func (c *AsPathCondition) evaluate(path *table.Path) bool {

	aspath := path.GetAsSeqList()

	if len(aspath) == 0 {
		return false
	}

	matched := false
	for _, member := range c.AsPathList {

		switch member.postiion {
		case AS_FROM:
			matched = aspath[0] == member.asn
		case AS_ANY:
			for _, n := range aspath {
				if n == member.asn {
					matched = true
					break
				}
			}
		case AS_ORIGIN:
			matched = aspath[len(aspath)-1] == member.asn

		case AS_ONLY:
			matched = len(aspath) == 1 && aspath[0] == member.asn

		}

		if matched {
			log.WithFields(log.Fields{
				"Topic":     "Policy",
				"Condition": "aspath length",
				"ASN":       member.asn,
				"Position":  member.postiion,
			}).Debug("condition matched")
			return true
		}

	}
	return false
}
예제 #8
0
파일: policy.go 프로젝트: ttaanngg/gobgp
// compare neighbor ipaddress of this condition and source address of path
// and, subsequent comparisons are skipped if that matches the conditions.
// If NeighborList's length is zero, return true.
func (c *NeighborCondition) evaluate(path *table.Path) bool {

	if len(c.NeighborList) == 0 {
		log.Debug("NeighborList doesn't have elements")
		return true
	}

	for _, neighbor := range c.NeighborList {
		cAddr := neighbor
		pAddr := path.GetSource().Address
		if pAddr.Equal(cAddr) {
			log.WithFields(log.Fields{
				"Topic":           "Policy",
				"NeighborAddress": pAddr.String(),
			}).Debug("neighbor matched")
			return true
		}
	}
	return false
}
예제 #9
0
파일: policy.go 프로젝트: ttaanngg/gobgp
func (a *CommunityAction) apply(path *table.Path) *table.Path {

	list := a.Values
	switch a.action {
	case config.BGP_SET_COMMUNITY_OPTION_TYPE_ADD:
		path.SetCommunities(list, false)
	case config.BGP_SET_COMMUNITY_OPTION_TYPE_REMOVE:
		path.RemoveCommunities(list)
	case config.BGP_SET_COMMUNITY_OPTION_TYPE_REPLACE:
		path.SetCommunities(list, true)
	case config.BGP_SET_COMMUNITY_OPTION_TYPE_NULL:
		path.ClearCommunities()
	}
	return path
}
예제 #10
0
func (peer *Peer) filterpath(path *table.Path) *table.Path {
	// special handling for RTC nlri
	// see comments in (*Destination).Calculate()
	if path != nil && path.GetRouteFamily() == bgp.RF_RTC_UC && !path.IsWithdraw {
		// if we already sent the same nlri, ignore this
		if peer.adjRibOut.Exists(path) {
			return nil
		}
		dst := peer.localRib.GetDestination(path)
		path = nil
		// we send a path even if it is not a best path
		for _, p := range dst.GetKnownPathList(peer.TableID()) {
			// just take care not to send back it
			if peer.ID() != p.GetSource().Address.String() {
				path = p
				break
			}
		}
	}
	if filterpath(peer, path) == nil {
		return nil
	}
	if !peer.isRouteServerClient() {
		path = path.Clone(path.IsWithdraw)
		path.UpdatePathAttrs(peer.fsm.gConf, peer.fsm.pConf)
	}
	options := &table.PolicyOptions{
		Info: peer.fsm.peerInfo,
	}
	path = peer.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_EXPORT, path, options)

	// remove local-pref attribute
	// we should do this after applying export policy since policy may
	// set local-preference
	if path != nil && peer.fsm.pConf.Config.PeerType == config.PEER_TYPE_EXTERNAL {
		path.RemoveLocalPref()
	}
	return path
}
예제 #11
0
파일: policy.go 프로젝트: ttaanngg/gobgp
func (a *MedAction) apply(path *table.Path) *table.Path {

	var err error
	switch a.action {
	case MED_ACTION_REPLACE:
		err = path.SetMed(a.Value, true)
	case MED_ACTION_ADD:
		err = path.SetMed(a.Value, false)
	case MED_ACTION_SUB:
		err = path.SetMed(a.Value, false)
	}
	if err != nil {
		log.WithFields(log.Fields{
			"Topic": "Policy",
			"Type":  "Med Action",
		}).Warn(err)
	}

	return path
}
예제 #12
0
파일: peer.go 프로젝트: osrg/gobgp
func (peer *Peer) filterpath(path, old *table.Path) *table.Path {
	// special handling for RTC nlri
	// see comments in (*Destination).Calculate()
	if path != nil && path.GetRouteFamily() == bgp.RF_RTC_UC && !path.IsWithdraw {

		// If we already sent the same nlri, send unnecessary
		// update. Fix this after the API change between table
		// and server packages.

		dst := peer.localRib.GetDestination(path)
		path = nil
		// we send a path even if it is not a best path
		for _, p := range dst.GetKnownPathList(peer.TableID()) {
			// just take care not to send back it
			if peer.ID() != p.GetSource().Address.String() {
				path = p
				break
			}
		}
	}

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

	if path = filterpath(peer, path, old); path == nil {
		return nil
	}

	path = path.Clone(path.IsWithdraw)
	path.UpdatePathAttrs(peer.fsm.gConf, peer.fsm.pConf)

	options := &table.PolicyOptions{
		Info: peer.fsm.peerInfo,
	}
	path = peer.policy.ApplyPolicy(peer.TableID(), table.POLICY_DIRECTION_EXPORT, path, options)

	// 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
}
예제 #13
0
func path2data(path *table.Path) (map[string]interface{}, map[string]string) {
	fields := map[string]interface{}{
		"ASPath": path.GetAsPath().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
	}

	var prefix, prefixLen string
	l := strings.Split(path.GetNlri().String(), "/")
	if len(l) == 2 {
		prefix = l[0]
		prefixLen = l[1]
	}
	tags := map[string]string{
		"PeerAddress": path.GetSource().Address.String(),
		"PeerAS":      fmt.Sprintf("%v", path.GetSource().AS),
		"Prefix":      prefix,
		"PrefixLen":   prefixLen,
		"NextHop":     path.GetNexthop().String(),
		"OriginAS":    fmt.Sprintf("%v", path.GetSourceAs()),
		"Timestamp":   path.GetTimestamp().String(),
	}
	return fields, tags
}
예제 #14
0
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.Error(err)
	}
	for _, p := range path.GetPathAttrs() {
		if err := bgp.FlatUpdate(tags, p.Flat()); err != nil {
			log.Error(err)
		}
	}
	return fields, tags
}
예제 #15
0
//modrib receives route updates from BGP server and adds the endpoint
func (self *OfnetBgp) modRib(path *table.Path) error {
	var nlri bgp.AddrPrefixInterface
	var nextHop string
	var macAddrStr string
	var portNo uint32

	nlri = path.GetNlri()
	nextHop = path.GetNexthop().String()

	if nextHop == "0.0.0.0" {
		return nil
	}

	if nlri == nil {
		return fmt.Errorf("no nlri")
	}

	endpointIPNet, _ := netlink.ParseIPNet(nlri.String())
	log.Infof("Bgp Rib Received endpoint update for path %s", path.String())

	//check if bgp published a route local to the host
	epid := self.agent.getEndpointIdByIpVrf(endpointIPNet.IP.Mask(endpointIPNet.Mask), "default")

	//Check if the route is local
	if nextHop == self.routerIP {
		log.Debugf("This is a local route skipping endpoint create! ")
		return nil
	} else if ep := self.agent.getEndpointByID(epid); ep != nil {
		if ep.EndpointType != "external" {
			log.Debugf("Endpoint was learnt via internal protocol. skipping update! ")
			return nil
		}
	}

	nhEpid := self.agent.getEndpointIdByIpVrf(net.ParseIP(nextHop), "default")

	if ep := self.agent.getEndpointByID(nhEpid); ep == nil {
		//the nexthop is not the directly connected eBgp peer
		macAddrStr = ""
		portNo = 0
	} else {
		macAddrStr = ep.MacAddrStr
		portNo = ep.PortNo
	}

	ipmask := net.ParseIP("255.255.255.255").Mask(endpointIPNet.Mask)

	if path.IsWithdraw != true {
		epreg := &OfnetEndpoint{
			EndpointID:   epid,
			EndpointType: "external",
			IpAddr:       endpointIPNet.IP,
			IpMask:       ipmask,
			Vrf:          "default", // FIXME set VRF correctly
			MacAddrStr:   macAddrStr,
			Vlan:         1,
			OriginatorIp: self.agent.localIp,
			PortNo:       portNo,
			Timestamp:    time.Now(),
		}

		// Install the endpoint in datapath
		// First, add the endpoint to local routing table

		self.agent.endpointDb.Set(epreg.EndpointID, epreg)
		err := self.agent.datapath.AddEndpoint(epreg)
		if err != nil {
			log.Errorf("Error adding endpoint: {%+v}. Err: %v", epreg, err)
			return err
		}
	} else {
		log.Info("Received route withdraw from BGP for ", endpointIPNet)
		endpoint := self.agent.getEndpointByIpVrf(endpointIPNet.IP, "default")
		if endpoint != nil {
			self.agent.datapath.RemoveEndpoint(endpoint)
			self.agent.endpointDb.Remove(endpoint.EndpointID)
		}
	}
	return nil
}
예제 #16
0
파일: policy.go 프로젝트: ttaanngg/gobgp
// compare extended community in the message's attribute with
// the one in the condition.
func (c *ExtCommunityCondition) evaluate(path *table.Path) bool {
	makeAs4Str := func(ec *ExtCommunityElement) string {
		t := ec.ecType
		str := fmt.Sprintf("%d", ec.localAdmin)
		switch t {
		case bgp.EC_TYPE_TRANSITIVE_TWO_OCTET_AS_SPECIFIC:
			str = fmt.Sprintf("%d:%s", ec.globalAdmin.(uint16), str)
		case bgp.EC_TYPE_TRANSITIVE_IP4_SPECIFIC:
			str = fmt.Sprintf("%s:%s", ec.globalAdmin.(net.IP).String(), str)
		case bgp.EC_TYPE_TRANSITIVE_FOUR_OCTET_AS_SPECIFIC:
			ga := ec.globalAdmin.(uint32)
			upper := strconv.FormatUint(uint64(ga&0xFFFF0000>>16), 10)
			lower := strconv.FormatUint(uint64(ga&0x0000FFFF), 10)
			str = fmt.Sprintf("%s.%s:%s", upper, lower, str)
		}
		return str
	}

	makeTypeSubStr := func(st bgp.ExtendedCommunityAttrSubType) string {
		subStr := ""
		switch st {
		case bgp.EC_SUBTYPE_ROUTE_TARGET:
			subStr = "RT"
		case bgp.EC_SUBTYPE_ROUTE_ORIGIN:
			subStr = "SoO"
		}
		return subStr
	}
	eCommunities := path.GetExtCommunities()
	if len(eCommunities) == 0 || c == nil {
		return false
	}

	matched := false
	matchStr := ""
	for _, member := range c.ExtCommunityList {
		for _, eCommunity := range eCommunities {
			ec := eCommunity.(bgp.ExtendedCommunityInterface)
			t, st := ec.GetTypes()
			if member.isRegExp {
				ecString := fmt.Sprintf("%s:%s", makeTypeSubStr(st), ec.String())
				if member.regExp.MatchString(ecString) {
					matched = true
					log.WithFields(log.Fields{
						"Topic":  "Policy",
						"RegExp": member.regExp.String(),
					}).Debug("extended community regexp used")
					matchStr = ec.String()
					break
				}
			} else if member.ecType == t && member.ecSubType == st {
				if makeAs4Str(member) == ec.String() {
					matched = true
					matchStr = ec.String()
					break
				}

			}
		}
		if matched {
			log.WithFields(log.Fields{
				"Topic":              "Policy",
				"Condition":          "Extended Community",
				"Extended Community": matchStr,
			}).Debug("condition matched")

			return true
		}
	}
	return false
}
예제 #17
0
파일: policy.go 프로젝트: ttaanngg/gobgp
// compare community in the message's attribute with
// the one in the condition.
func (c *CommunityCondition) evaluate(path *table.Path) bool {

	communities := path.GetCommunities()

	if len(communities) == 0 {
		return false
	}

	makeStr := func(c uint32) string {
		upper := strconv.FormatUint(uint64(c&0xFFFF0000>>16), 10)
		lower := strconv.FormatUint(uint64(c&0x0000FFFF), 10)
		return upper + ":" + lower
	}

	var strCommunities []string = nil
	matched := false
	idx := -1
	for _, member := range c.CommunityList {
		if member.isRegExp {

			if strCommunities == nil {
				// create community string.
				strCommunities = make([]string, len(communities))
				for i, c := range communities {
					strCommunities[i] = makeStr(c)
				}
			}

			for i, c := range strCommunities {
				if member.communityRegExp.MatchString(c) {
					matched = true
					idx = i
					log.WithFields(log.Fields{
						"Topic":  "Policy",
						"RegExp": member.communityRegExp.String(),
					}).Debug("community regexp used")
					break
				}
			}

		} else {
			for i, c := range communities {
				if c == member.community {
					matched = true
					idx = i
					break
				}
			}
		}

		if matched {
			log.WithFields(log.Fields{
				"Topic":     "Policy",
				"Condition": "Community",
				"Community": makeStr(communities[idx]),
			}).Debug("condition matched")

			return true
		}
	}
	return false
}
예제 #18
0
func ToPathApi(path *table.Path) *Path {
	nlri := path.GetNlri()
	n, _ := nlri.Serialize()
	family := uint32(bgp.AfiSafiToRouteFamily(nlri.AFI(), nlri.SAFI()))
	pattrs := func(arg []bgp.PathAttributeInterface) [][]byte {
		ret := make([][]byte, 0, len(arg))
		for _, a := range arg {
			aa, _ := a.Serialize()
			ret = append(ret, aa)
		}
		return ret
	}(path.GetPathAttrs())
	return &Path{
		Nlri:           n,
		Pattrs:         pattrs,
		Age:            path.GetTimestamp().Unix(),
		IsWithdraw:     path.IsWithdraw,
		Validation:     int32(path.Validation().ToInt()),
		Filtered:       path.Filtered("") == table.POLICY_DIRECTION_IN,
		Family:         family,
		SourceAsn:      path.GetSource().AS,
		SourceId:       path.GetSource().ID.String(),
		NeighborIp:     path.GetSource().Address.String(),
		Stale:          path.IsStale(),
		IsFromExternal: path.IsFromExternal(),
	}
}