/* 
 * $Id: idrp_rt_phase2.c,v 1.3 1995/08/01 20:26:29 sjr Exp $ 
 * Merit IDRP release 1.1 (gated 3.5.4).  Copyright (c) 1994 by Merit Network, Inc. 
 */

#include "include.h"
#include "iso.h"
#include "idrp.h"

/* idrp_rt_phase2.c - 
 * Phase 2 code for idrp handling
 *
 * idrp_to_gated_pref - generated gated preference 
 *  
 * Phase 2 decision routes
 * 
 * tie_break(p_idrp_rt1,p_idrp_rt2)  - general tie break
 * 
 * - utilities for iso tie breaking 
 * 
 * multi_exit_compare(p_idrp_rt1,p_idrp_rt2) 
 * 
 * 
 * %% general comments
 *
 * We have the following  entry points to the IDRP code:
 * idrp_process_update -> parse_update -> idrp_route_mod
 *			 and when done -> idrp_process_pdu_routes
 * 
 * idrp_process_pdu_routes -> calls phase 1 processing routines:
 * -> phase1_internal->ph1_int_add_route 
 *		     ->ph1_with_route
 *		     ->ph1_with_rep_route
 * -> phase1_external->ph1_ext_add_route
 *		     ->ph1_with_route
 *		     ->ph1_with_rep_route
 *	-> send_best_ext -> del_routes_best_ext
 *   
 * Before the routes are handed to gated, preferences for
 *  gated are set so that it can proform phase 2 processing:
 *    check for other route
 *    tie_break routine
 *    gated_pref routine 
 *   
 *  
 * idrp_flash - called to handle the list of new active routes
 * 		from the gated routing table.
 *              The phase 3 routes are only sent to external peers.
 * 
 * idrp_flash -> phase3_processing -> phase3_status_change
 * ->idrp_send_phase3_route -> idrp_del_ph3_routes
 * 
 * 
*/ 



/*  
 * Phase 2 decision routes
 * 
 * - phase 2 installation is done by gated
 *   phase two references are done route by route
 *   upon input
 */

/* 
 * idrp_to_gated_pref(idrp_pref)
 */

int 
idrp_to_gated_pref(p_idrp_rt)
idrpRoute	*p_idrp_rt;
{

/*
 * 1) if gated preference is set to invalid - leave it
 * 2) if gated preference is not set, set it to a default   	
 *
 */
	

u_int	pref;

	pref =  p_idrp_rt->gated_pref; 

	if (pref == IDRP_GATED_INVALID_ROUTE_PREF)
		{
		/* if gated has an invalid route preference
		 * - leave it 
		 */ 
		

		return(pref);
		}
 
	if (pref != IDRP_GATED_INIT_ROUTE_PREF)
		{
		return(pref);
		}	

	/* Gated preference indicates no run of import call
	 *  
	 * (gated preference = IDRP_GATED_INIT_ROUTE_PREF)  
	 * 
	 * set-up a default until an import statement can be run
	  */


	/* if IDRP preference indicates an 
	 *   invalid route, set GATED to invalid
	 */

	if (p_idrp_rt->pref_cal == IDRP_INVALID_ROUTE_PREF)
		{
		pref = IDRP_GATED_INVALID_ROUTE_PREF; 
		return(pref);
		}
	
	pref = RTPREF_KERNEL_REMNANT;
	switch (p_idrp_rt->peer->type)
		{	
		case IDRP_PEER_INTERNAL:
			{
			if (idrp_my_rdi_rdpath(p_idrp_rt->p_attr))
				{
				/* It is this domain's routes - use the			
				 * - local preference received
				 * - someone has more policy than 
				 * - old calculation without import calls 	
				 */
		
			 	pref = RTPREF_IDRP_INT +  (p_idrp_rt->pref_cal*IDRP_INT_PREF_MULTI);
				}
			else
				{
				/* it is not my RD - so use
				 * the preference for external RDs   
				 */ 

				pref = RTPREF_IDRP_EXT  + (p_idrp_rt->pref_cal*IDRP_EXT_PREF_MULTI);
				if (p_idrp_rt->pref_cal != p_idrp_rt->pref_rcvd)
					{	
					  trace_log_tf(idrp_trace_options, 0, LOG_ERR,
					       ("route %s pref_calc = %i preference rcv = %i\n", iso_ptoa(&p_idrp_rt->nlri),
						p_idrp_rt->pref_cal,p_idrp_rt->pref_rcvd));
					
					}
				}
			}
				break;
	
		case IDRP_PEER_EXTERNAL:
			pref = RTPREF_IDRP_EXT  + (p_idrp_rt->pref_cal*IDRP_EXT_PREF_MULTI);
			break;
		
	
		case IDRP_PEER_LOCAL:
			pref = RTPREF_IDRP_INT + (p_idrp_rt->pref_cal*IDRP_INT_PREF_MULTI);
			break;
	
		trace_tf(idrp_trace_options, TR_POLICY,0,("gated preference not set by policy call , so calculating in idrp code, route = %s pref = %i",
			iso_ptoa(&p_idrp_rt->nlri),pref));	 
				

		}

	return (pref);

} 



/*
 * tie_break(idrpRoute*, idrpRoute*)
 *
 * Description: Tie break between two addresses.  Value returned
 * is PFX_ADDR_LESS_THAN if the first addr should be chosen and 
 * it's PFX_ADDR_GREATER_THAN if the second address should be chosen.
 * I think that any other return value (e.g. PFX_ADDR_EQUAL) is an
 * error.
 *
 */

int 
tie_break(p_idrp_rt1,p_idrp_rt2)
idrpRoute	*p_idrp_rt1;
idrpRoute	*p_idrp_rt2;
{
u_int		res,res2;
idrpPeer	*p_this_node;

	p_this_node = &idrp_this_node;
	if (p_idrp_rt1->family != p_idrp_rt2->family)
		{
		trace_tf (idrp_trace_options, TR_NORMAL,0,("bogus - tie break 2 familes peer1 %s peer 2 %s Route1 %s Route 2 %s",  
			p_idrp_rt1->peer->name,p_idrp_rt2->peer->name,  
			iso_ptoa(&p_idrp_rt1->nlri),iso_ptoa(&p_idrp_rt2->nlri)));
	
		/* two different families, all comparisons off */
		/* give iso priority for now */

		if (p_idrp_rt1->family ==  AF_ISO)
			{
			return (PFX_ADDR_GREATER_THAN);
			}
		else
			{
			return (PFX_ADDR_LESS_THAN);
			}
		}



 	/* section 7.16.2.1 IDRP specification 
	 *  1) hop count compared  
	 *  2) MULTI_EXIT_DISC
	 *  3) compare NETs of peer sending route 
	 */

	if (p_idrp_rt1->p_attr == p_idrp_rt2->p_attr)
		{
		/* totally path attribute - so compare NETs 
		 * .16.2.1 a)
		 * I'm not sure when you would have identical
		 * attributes - so this is a log error 
		 */  

		trace_log_tf (idrp_trace_options, 0, LOG_ERR, ("??? tie break attributes the same peer1 %s peer 2 %s Route1 %s Route 2 %s",  
			p_idrp_rt1->peer->name,p_idrp_rt2->peer->name,  
			iso_ptoa(&p_idrp_rt1->nlri),iso_ptoa(&p_idrp_rt2->nlri)));

		return(tie_break_use_peer(p_idrp_rt1,p_idrp_rt2));
		}

	trace_tf(idrp_trace_options, TR_NORMAL,0,("route %s peer1 mask %x peer2 mask %x",iso_ptoa(&p_idrp_rt1->nlri),
				p_idrp_rt1->p_attr->idrp_mask,
				p_idrp_rt2->p_attr->idrp_mask));

	/* 17.16.2.1. - says differ in NEXT_HOP attribute
 	 * - if one route has a NEXT_HOP and the other has an implied NEXT_HOP
	 * - go to check in the idrp_dif_paths
	 */

	if (p_idrp_rt1->p_attr->idrp_mask != p_idrp_rt2->p_attr->idrp_mask &&  
		((p_idrp_rt1->p_attr->idrp_mask & IDRP_ATTBT_ALL_BUT_NEXT_HOP) !=
		(p_idrp_rt2->p_attr->idrp_mask & IDRP_ATTBT_ALL_BUT_NEXT_HOP))) 
		{
		/* Not even the same path attribute list - do NET compare */ 
		/* %%%% make sure this compare fits the description in 7.16.2.1 c) */

		  trace_tf (idrp_trace_options, TR_ROUTE_TIES,0,("route %s peer1:  %s peer 2: %s ties but not same path attributes",
								 iso_ptoa(&p_idrp_rt1->nlri),p_idrp_rt1->peer->name, p_idrp_rt2->peer->name));
			

		return(tie_break_use_peer_local(p_idrp_rt1,p_idrp_rt2));
		}

	/* does the path diff only in Next hop and multi-exit discriminator */
 
	res = idrp_dif_path(p_idrp_rt1->p_attr,p_idrp_rt2->p_attr);
	trace_tf(idrp_trace_options, TR_NORMAL,0,("res from tie break = %x",res));	

	if ((res & IDRP_ATTBT_NEXT_HOP) == res || (res == 0))
		{
		/*  only - next hop - compare next hops
		 * - would be identical if attribute records are same
		 * - if res is zero, or next_hops are different
		 */ 

		/* IDRP_TRACE_BIT_TEST(p_this_node,IDRP_TRACE_ROUTE_TIES) */
			
			trace_tf (idrp_trace_options, TR_ROUTE_TIES,0,("route %s peer1:  %s peer 2: %s ties differ in  next_hop ",
				iso_ptoa(&p_idrp_rt1->nlri),p_idrp_rt1->peer->name, p_idrp_rt2->peer->name));
			

		/* using the peer as next hop for now
	 	 * - next phase use the next_hop in the attribute record 
		 */

		return(tie_break_use_peer(p_idrp_rt1,p_idrp_rt2));
		}
	
	res2 = res & (IDRP_ATTBT_NEXT_HOP | IDRP_ATTBT_MULTI_EXIT_DISC);

	if (res2  == (IDRP_ATTBT_NEXT_HOP | IDRP_ATTBT_MULTI_EXIT_DISC) || 
		(res2 == (IDRP_ATTBT_MULTI_EXIT_DISC)))
		{
		/* only next hop and multi_exit differ
		 * 7.16.2.1) b paragraph 1  if multi_exit true
		 *	- multi_exit_disc value
		 *	- tie break using peer  
		 *	    adjacent Peer NET 1st, lower NET if both adjacent 
		 *	-   internal Peer NET 2nd , lower NET if both adjacent 
		 *
		 */

		if (idrp_this_node.multi_exit) 
			{

				
				trace_tf (idrp_trace_options, TR_ROUTE_TIES,0,("route %s peer1:  %s peer 2: %s ties differ in multi-exit next hop ",
					iso_ptoa(&p_idrp_rt1->nlri),p_idrp_rt1->peer->name, p_idrp_rt2->peer->name));
				

			res = multi_exit_compare(p_idrp_rt1->p_attr,p_idrp_rt2->p_attr);
			if (res !=ISO_ADDR_EQUAL)
				{
				return(res);
				}
			return(tie_break_use_peer(p_idrp_rt1,p_idrp_rt2));
			}
			
		}
	
	/* 7.16.2.1 b) paragraph 2 
	 * if paths compare and multi_exit parameter value is false on node
	 *   	so we do not use multi_exit to tie break
	 * 7.16.2.c)  
	 * or if the paths differ by more than NEXT_HOP And MULTI_EXIT 
	 */

		
		trace_tf (idrp_trace_options, TR_ROUTE_TIES,0,("route %s peer1:  %s peer 2: %s ties differ 17.16.2.1 b-2/c using peer NET compare ",
			iso_ptoa(&p_idrp_rt1->nlri),p_idrp_rt1->peer->name, p_idrp_rt2->peer->name));
		

	return(tie_break_use_peer_local(p_idrp_rt1,p_idrp_rt2));	

}


int  
tie_break_use_peer(p_idrp_rt1,p_idrp_rt2)	
idrpRoute *p_idrp_rt1;
idrpRoute *p_idrp_rt2;
{
int res;
	switch (p_idrp_rt1->peer->type)
		{
		case IDRP_PEER_EXTERNAL:
			if (p_idrp_rt2->peer->type != IDRP_PEER_EXTERNAL)
				{
				return (PFX_ADDR_LESS_THAN); /*@@@@@*/
		 		}
			break;

		case IDRP_PEER_LOCAL:
		case IDRP_PEER_INTERNAL:
			if (p_idrp_rt2->peer->type == IDRP_PEER_EXTERNAL)
				{
				return (PFX_ADDR_GREATER_THAN); /*@@@@@@*/
				}
			break;


		case IDRP_PEER_TEST:
			return(PFX_ADDR_LESS_THAN);
			break;

		}

	switch (p_idrp_rt1->family)
		{
		case AF_ISO:
			return (compare_iso_addr(&p_idrp_rt1->peer->neighbor,&p_idrp_rt2->peer->neighbor));
			break;

		case AF_INET:
			res = sockaddrcmp2(p_idrp_rt1->peer->ip_neighbor, p_idrp_rt2->peer->ip_neighbor);
			if (res < 0) 
				{
				return (PFX_ADDR_LESS_THAN);
				}
			else if (res > 0)
				{
				return (PFX_ADDR_GREATER_THAN); 
				}
			return (PFX_ADDR_EQUAL);				
			break;
		}

	/* if we get here - make some determination
	 */

	
	return(PFX_ADDR_LESS_THAN);		   
}	

int 
tie_break_use_peer_local(p_idrp_rt1,p_idrp_rt2)
idrpRoute	*p_idrp_rt1;
idrpRoute	*p_idrp_rt2;
{
int res;
idrpPeer	*peer1;
idrpPeer	*peer2;

	peer1 = p_idrp_rt1->peer;
	peer2 = p_idrp_rt2->peer; 
	switch (peer1->type)
		{
		case IDRP_PEER_EXTERNAL:
			if (peer2->type != IDRP_PEER_EXTERNAL)
				{
				/* set the peer to this node
				 */
				peer1 = &idrp_this_node;	
		 		}
			break;

		case IDRP_PEER_LOCAL:
		case IDRP_PEER_INTERNAL:
			if (peer2->type == IDRP_PEER_EXTERNAL)
				{
				peer2 = &idrp_this_node;	
				}
			break;


		case IDRP_PEER_TEST:
			return(PFX_ADDR_LESS_THAN);
			break;

		}


	switch (p_idrp_rt1->family)
		{
		case AF_ISO:
			return (compare_iso_addr(&peer1->neighbor,&peer2->neighbor));
			break;

		case AF_INET:
			res = sockaddrcmp2(peer1->ip_neighbor, peer2->ip_neighbor);
			if (res < 0) 
				{
				return (PFX_ADDR_LESS_THAN);
				}
			else if (res > 0)
				{
				return (PFX_ADDR_GREATER_THAN); 
				}
			return (PFX_ADDR_EQUAL);				
			break;
		}

	/* if we get here - make some determination
	 */
	
	return(PFX_ADDR_LESS_THAN);		   
}


int 
multi_exit_compare(p_att1,p_att2)
idrp_attribute_record *p_att1;
idrp_attribute_record *p_att2;
{
u_int8   i1;
u_int8   i2;

	i1 = p_att1->p_opts->multi_exit_rcvd;
	i2 = p_att2->p_opts->multi_exit_rcvd;
        if (i1  < i2 )
                return(PFX_ADDR_LESS_THAN);
        if (i1 == i2)
                return(PFX_ADDR_EQUAL);
        return(PFX_ADDR_GREATER_THAN);
}

