#include "include.h"
#include "targets.h"
#include "ripng_internal.h"
#include "inet6.h"

int ripng_n_trusted = 0;		/* Number of trusted gateways */
gw_entry *ripng_gw_list = 0;			/* List of RIPng gateways */
static task_job *ripng_target_list_job;		/* To rebuild target list */
target ripng_targets = { &ripng_targets, &ripng_targets };
						/* Target list */
metric_t ripng_default_metric = RIPNG_METRIC_UNREACHABLE;

task_timer *ripng_timer_update;	/* To send periodic advertisements */
task_timer *ripng_timer_expire;	/* To expire the old routes */
task_timer *ripng_timer_garbage;	/* To clean the expired routes */

adv_entry *ripng_import_list = 0;	/* List of networks to import into rip */
adv_entry *ripng_export_list = 0;	/* List of sources to export */
adv_entry *ripng_int_policy = 0;	/* List of interface policy */

static void
ripng_garbage_collect __PF2(tip, task_timer *,
                            interval, time_t)
{
    time_t clear_to = time_sec - RIPNG_T_GARBAGE_COLLECT;
    time_t nexttime = time_sec + 1;
    u_int deleted = 0;

    if (clear_to > 0) {
        target *tlp;

        rt_open(tip->task_timer_task);

        TARGET_LIST(tlp, &ripng_targets) {
            td_entry *tdp;

            TD_LIST(tdp, &tlp->target_td) {
                /* Check for termination of holddown */
                if (!BIT_TEST(ripng_flags, RIPNGF_TERMINATE)
                    && BIT_TEST(tdp->td_flags, TDF_HOLDDOWN|TDF_POISON)) {
                    if (tdp->td_rt->rt_time <= clear_to) {
                        /* Holddown is over - queue it to be released */

                        trace_only_tp(tip->task_timer_task,
                                      0,
                                      ("\t%A/%u %s ended",
                                       tdp->td_rt->rt_dest,
                                       inet6_prefix_mask(tdp->td_rt->rt_dest_mask),
                                       BIT_TEST(tdp->td_flags, TDF_POISON) ? "poison" : "holddown"));
                    
                        deleted++;
                        TD_CLEANUP(tlp, tdp, TRUE);
                    } else {
                        /* This is the next route to over */
                        if (tdp->td_rt->rt_time < nexttime) {
                            nexttime = tdp->td_rt->rt_time;
                        }
                        break;
                    }
                }
            } TD_LIST_END(tdp, &tlp->target_td);
        } TARGET_LIST_END(tlp, &ripng_targets);

        rt_close(tip->task_timer_task, (gw_entry *) 0, deleted, NULL);
    }

    if (nexttime > time_sec) {
        /* No route to over the holddown */
        nexttime = time_sec;
    }

    task_timer_set(tip, (time_t) 0, nexttime + RIPNG_T_GARBAGE_COLLECT - time_sec);

    trace_tp(tip->task_timer_task,
             TR_POLICY,
             0,
             ("ripng_garbage_collect: %d routes cleared, next garbage collection %d sec later",
              deleted,
              nexttime + RIPNG_T_GARBAGE_COLLECT - time_sec));
}

static int
ripng_policy __PF3(tp, task *,
		   tlp, target *,
		   change_list, rt_list *)
{
    int changes = 0;
    if_addr *ifap = tlp->target_ifap;
    if_addr *ifap2;
    rt_head *rth;
    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];

    RT_LIST(rth, change_list, rt_head) {
	register rt_entry *new_rt = rth->rth_active;
	adv_results result;
	td_entry *tdp;
	int changed = 0;
	int exportable = FALSE;
	int set_metric = FALSE;
	int move_bit = FALSE;
        int holddown = 0;
        int poison = 0;

	TD_TSI_GET(tlp, rth, tdp);

	/* Can we announce the new route (if there is one)? */
	if (new_rt) {

	    if (socktype(new_rt->rt_dest) != AF_INET6) {
		goto no_export;
	    }

	    switch (inet6_scope_of(new_rt->rt_dest)) {
	      case INET6_SCOPE_LINKLOCAL:
	      case INET6_SCOPE_MULTICAST:
		goto no_export;
	    }

	    if (BIT_TEST(new_rt->rt_state, RTS_NOADVISE|RTS_GROUP)) {
		/* Absolutely not */

		goto no_export;
	    }

            IF_ADDR(ifap2) {
                if ((ifap->ifa_link == ifap2->ifa_link)
                    && !RT_IFAP(new_rt)
                    && if_subnet(new_rt->rt_dest,
                                 ifap->ifa_addr,
                                 new_rt->rt_dest_mask)) {
                    /* Aggregate not to the subnet */
                    goto no_export;
                }
            } IF_ADDR_END(ifap2);
              

	    if (RT_IFAP(new_rt) /* aggregate route doesn't have if */
		&& new_rt->rt_gwp->gw_proto == tp->task_rtproto
                && RT_IFAP(new_rt)->ifa_link == ifap->ifa_link) {
		/* Split horizon */
		goto no_export;
	    }

	    if (RT_IFAP(new_rt)
		&& !BIT_TEST(RT_IFAP(new_rt)->ifa_state, IFS_UP)) {
		/* The interface is down */
		goto no_export;
	    }

	    /* Assign default metric */
	    if (!RT_IFAP(new_rt)
		|| (BIT_TEST(RT_IFAP(new_rt)->ifa_state, IFS_LOOPBACK) &&
		    SAME_ADDR6(sock2in6(RT_IFAP(new_rt)->ifa_addr),
			       sock2in6(inet6_addr_loopback)))) {
		/* Routes via the loopback interface must have */
		/* an explicit metric */
		result.res_metric = RIPNG_METRIC_UNREACHABLE;

	    } else if (new_rt->rt_gwp->gw_proto == RTPROTO_DIRECT) {
		/* Interface rotues */

		result.res_metric = 1;
	    } else {
		result.res_metric = ripng_default_metric;
	    }

	    if (!export(new_rt,
			tp->task_rtproto,
			ripng_export_list,
			ips->ips_export,
			tlp->target_gwp ? tlp->target_gwp->gw_export : (adv_entry *) 0,
			&result)) {

		/* Policy prohibits announcement */
		goto no_export;
	    } else {

		/* Add the interface metric */
		result.res_metric += ips->ips_metric_out;
	    }

	    if (result.res_metric < RIPNG_METRIC_UNREACHABLE) {
		exportable = TRUE;
	    }

	  no_export:;
	}

	/* Now that we have determined the exportability of the new */
	/* route we decide what changed need to be made.  The */
	/* complexity is required to surpress routing loops both with */
	/* RIPng and between RIPng and other protocols. */

	/* There are two types of holddowns used, the first one is */
	/* called HOLDDOWN and is used when a route goes away or is */
	/* overridden by a route that is not suspected to be an echo */
	/* of a route we are announcing.  The second is called POISON */
	/* and is used when a route is overridden by a route suspected */
	/* to be an echo of a route we are announcing. */

	if (!tdp) {
	    /* New route */

	    if (exportable) {
		TD_ALLOC(tdp);
		TD_TSI_SET(tlp, rth, tdp);
		tdp->td_rt = new_rt;
		rtbit_set(tdp->td_rt, tlp->target_rtbit);
		TD_ENQUE(tlp, tdp);
		set_metric = TRUE;
		changes++;
	    }
        } else if (!new_rt) {
            /* No new route, just an old one */

            if (!BIT_TEST(tdp->td_flags, TDF_POISON|TDF_HOLDDOWN)) {
                changed++;
                if (BIT_TEST(tdp->td_rt->rt_state, RTS_DELETE|RTS_HIDDEN)) {
                    /* Put int holddown */
                    holddown++;
                } else {
                    /* Poison the old route */
                    poison++;
                }
            }
        } else if (new_rt == tdp->td_rt) {
            /* Something has changed with the route we are announcing */

            if (BIT_TEST(tdp->td_flags, TDF_POISON|TDF_HOLDDOWN)) {
                if (exportable) {
                    set_metric++;
                    changed++;
                }
            } else {
                if (!exportable) {
                    poison++;
                    changed++;
                } else if (tdp->td_metric != result.res_metric) {
                    set_metric++;
                    changed++;
                }
            }
	} else {
	    /* New route is just better */

            if (BIT_TEST(tdp->td_rt->rt_state, RTS_DELETE|RTS_HIDDEN)) {
                if (!BIT_TEST(tdp->td_flags, TDF_HOLDDOWN)) {
                    holddown++;
                    changed++;
                }
            } else {
                if (!BIT_TEST(tdp->td_flags, TDF_POISON)) {
                    poison++;
                    changed++;
                }
                move_bit++;
            }
	}

        if (changed||set_metric) {
            trace_only_tp(tp,
                          TRC_NL_BEFORE,
                          ("ripng_policy: Policy for target %s -> %A%s",
                           ifap->ifa_link->ifl_name,
                           *tlp->target_dst,
                           BIT_TEST(tlp->target_flags, RIPNGTF_MC) ? "(mc)" : ""));
            tracef("\t%A/%u ",
                   rth->rth_dest,
                   inet6_prefix_mask(rth->rth_dest_mask));
        }
	if (set_metric) {
	    target_set_metric(tdp, result.res_metric);
	    trace_tp(tp,
		     TR_POLICY,
		     0,
		     ("\tmetric %u",
		      tdp->td_metric));
	} else if (holddown) {
            tdp->td_flags = TDF_HOLDDOWN|TDF_CHANGED;
            if (!ripng_timer_garbage) {
                /* Create the garbage-collection timer */
                ripng_timer_garbage =
                  task_timer_create(tp,
                                    "Garbage-Collection",
                                    0,
                                    (time_t) RIPNG_T_GARBAGE_COLLECT,
                                    (time_t) 0,
                                    ripng_garbage_collect,
                                    (void_t) 0);
            }
            trace_tp(tp,
                     TR_POLICY,
                     0,
                     ("starting holddown"));
        }
	if (changed) {
	    /* Changed entries need to be at the head of the queue to */
	    /* maek triggerd updates quick */

	    TD_DEQUE(tdp);
	    TD_ENQUE(tlp, tdp);
	    changes++;
	}

	if (move_bit) {
	    /* Move lock to new route */

	    rtbit_set(new_rt, tlp->target_rtbit);
	    (void) rtbit_reset(tdp->td_rt, tlp->target_rtbit);
	    tdp->td_rt = new_rt;
	}
    } RT_LIST_END(rth, change_list, rt_head);

    return changes;
}

static void
ripng_target_list __PF1(jp, task_job *)
{
    int targets;
    target *tlp;
    int have_list = 0;
    int changes = 0;
    task *tp = jp->task_job_task;
    rt_list *rthl = (rt_list *) 0;

#ifdef notdef
    タイマは、update(30+-15秒毎)、timeout(経路毎に180秒);
    garbage-collect(経路無効から120秒);
#endif /* notdef */
    if (!ripng_timer_update) {
        /* Create the update timer */
        ripng_timer_update = task_timer_create(jp->task_job_task,
                                               "Update",
                                               0,
                                               (time_t) 1,
                                               (time_t) 0,
                                               ripng_job,
                                               (void_t) 0);
    }

    /* Build or update target list */
    targets = target_build(jp->task_job_task,
			   &ripng_targets,
			   ripng_gw_list,
			   TARGETF_ALLINTF|TARGETF_BROADCAST,
			   ripng_tsi_dump);

    TARGET_LIST(tlp, &ripng_targets) {
	    /* joining RIPng multicast group (ff02::9) */
	if (BIT_TEST(tlp->target_flags, TARGETF_BROADCAST)
	    && BIT_TEST(tlp->target_ifap->ifa_state, IFS_MULTICAST)
	    && BIT_TEST(tlp->target_ifap->ifa_state, IFS_BROADCAST|IFS_POINTOPOINT)) {
	    ripng_mc_set(jp->task_job_task, tlp);
	}
    } TARGET_LIST_END(tlp, &ripng_targets);

    /* Send a RIPng REQUEST for everyone's routing table */
    if (!BIT_TEST(task_state, TASKS_TEST)) {
	byte buffer[sizeof (struct ripng) + sizeof(struct ripng_netinfo)];
	struct ripng *msg = (struct ripng *)buffer;
	struct ripng_netinfo *rte = (struct ripng_netinfo *)(msg+1);

	bzero((caddr_t) buffer, sizeof buffer);
	msg->ripng_command = RIPNG_COMMAND_REQUEST;
	msg->ripng_version = RIPNG_VERSION_1;

	rte->ripng_metric = RIPNG_METRIC_UNREACHABLE;

	TARGET_LIST(tlp, &ripng_targets) {
	    if (!BIT_TEST(tlp->target_flags, RIPNGTF_POLL)) {
		/* Do a poll if one has not been done on this interface */
		ripng_send(tp,
			   tlp->target_ifap,
			   MSG_DONTROUTE,
			   *tlp->target_dst,
			   (void_t) buffer,
			   sizeof(buffer));
		BIT_SET(tlp->target_flags, RIPNGTF_POLL);
	    }
	} TARGET_LIST_END(tlp, &ripng_targets);
    }

    /* Evaluate policy for new targets */
    rt_open(tp);

    TARGET_LIST(tlp, &ripng_targets) {
	switch (BIT_TEST(tlp->target_flags, TARGETF_POLICY|TARGETF_SUPPLY)) {
	  case TARGETF_SUPPLY:
	    /* Need to run policy for this target */

	    if (!have_list) {
		/* Get target list */
		rthl = rthlist_active(AF_INET6);
		have_list = 1;
	    }

	    if (rthl) {
		/* and run policy */
		changes += ripng_policy(tp, tlp, rthl);
	    }

	    /* Indicate policy has been run */
	    BIT_SET(tlp->target_flags, TARGETF_POLICY);
	    break;

	  case TARGETF_POLICY:
	    /* Indicate policy run on this target */

	    BIT_RESET(tlp->target_flags, TARGETF_POLICY);
	    break;

	  default:
	    break;
	}
    } TARGET_LIST_END(tlp, &ripng_targets);

    if (rthl) {
	RTLIST_RESET(rthl);
    }

    rt_close(tp, (gw_entry *) 0, 0, NULL);

    /* rip_need_flash() */
}

/*
 *	Deal with interface policy
 */
static void
ripng_control_reset __PF2(tp, task *,
			  ifap, if_addr *)
{
    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];

    BIT_RESET(ips->ips_state, IFPS_RESET);
    ips->ips_metric_in = ifap->ifa_metric + 1;
    ips->ips_metric_out = 0;
}

static void
ripng_control_set __PF2(tp, task *,
			ifap, if_addr *)
{
    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];
    config_entry **list = config_resolv_ifa(ripng_int_policy,
					    ifap,
					    RIPNG_CONFIG_MAX);
    /* Reset */
    ripng_control_reset(tp, ifap);

    /* Set defaults */
    if (BIT_TEST(ifap->ifa_state, IFS_LOOPBACK)) {
	BIT_SET(ips->ips_state, IFPS_NOIN|IFPS_NOOUT);
    }

    /* Process configuration info */
    if (list) {
	int type = RIPNG_CONFIG_MAX;
	config_entry *cp;

	/* Fill in the parameters */
	while (--type) {
	    if ((cp = list[type])) {
		switch (type) {
		case RIPNG_CONFIG_IN:
		    if (GA2S(cp->config_data)) {
			BIT_RESET(ips->ips_state, IFPS_NOIN);
		    } else {
			BIT_SET(ips->ips_state, IFPS_NOIN);
		    }
		    break;

		case RIPNG_CONFIG_OUT:
		    if (GA2S(cp->config_data)) {
			BIT_RESET(ips->ips_state, IFPS_NOOUT);
		    } else {
			BIT_SET(ips->ips_state, IFPS_NOOUT);
		    }
		    break;

		case RIPNG_CONFIG_METRICIN:
		    BIT_SET(ips->ips_state, IFPS_METRICIN);
		    ips->ips_metric_in = (metric_t) GA2S(cp->config_data);
		    break;

		case RIPNG_CONFIG_METRICOUT:
		    BIT_SET(ips->ips_state, IFPS_METRICOUT);
		    ips->ips_metric_out = (metric_t) GA2S(cp->config_data);
		    break;
		}
	    }
	}

	config_resolv_free(list, RIPNG_CONFIG_MAX);
    }
}

void
ripng_ifachange __PF2(tp, task *,
		      ifap, if_addr *)
{
    int changes = 0;
    gw_entry *gwp;

    if (socktype(ifap->ifa_addr) != AF_INET6) {
	return;
    }

    rt_open(tp);

    switch(ifap->ifa_change) {
      case IFC_NOCHANGE:
      case IFC_ADD:
	if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
	    ripng_control_set(tp, ifap);
	}
	break;

      case IFC_DELETE:
      case IFC_DELETE|IFC_UPDOWN:
      Down:
	GW_LIST(ripng_gw_list, gwp) {
	    rt_entry *rt;

	    RTQ_LIST(&gwp->gw_rtq, rt) {
		if (RT_IFAP(rt) == ifap) {
		    rt_delete(rt);
		    changes ++;
		}
	    } RTQ_LIST_END(&gwp->gw_rtq, rt);
	} GW_LIST_END(ripng_gw_list, gwp);

	ripng_control_reset(tp, ifap);
	break;

      default:
	/* Something has changed */

	if (BIT_TEST(ifap->ifa_change, IFC_UPDOWN)) {
	    if (BIT_TEST(ifap->ifa_state, IFS_UP)) {
		ripng_control_set(tp, ifap);
	    } else {
		goto Down;
	    }
	}

	if (BIT_TEST(ifap->ifa_change, IFC_METRIC)) {
	    struct ifa_ps *ips = &ifap->ifa_ps[tp->task_rtproto];

	    /* The metric has changed, reset the POLL bit on any */
	    /* targets using this inteface so we will send another */
	    /* POLL */
	    if (!BIT_TEST(ips->ips_state, IFPS_METRICIN)) {
		target *tlp;

		ips->ips_metric_in = ifap->ifa_metric + 1;
		TARGET_LIST(tlp, &ripng_targets) {
		    if (tlp->target_ifap == ifap) {
			BIT_RESET(tlp->target_flags, RIPNGTF_POLL);
		    }
		} TARGET_LIST_END(tlp, &ripng_targets);
	    }

	    if (!BIT_TEST(ifap->ifa_state, IFS_UP)) {
		goto Down;
	    }
	}

	break;
    }

    rt_close(tp, (gw_entry *) 0, changes, NULL);

    /* Schedule a target list rebuild if necessary */
    if (!ripng_target_list_job) {
	ripng_target_list_job = task_job_create(tp,
						TASK_JOB_FG,
						"Target_Build",
						ripng_target_list,
						(void_t) 0);
    }
}

/*
 *	Process changes in the routing table.
 */
void
ripng_flash __PF2(tp, task *,
		  change_list, rt_list *)
{
    int changes = 0;
    target *tlp;

    /* Re-evaluate policy */
    rt_open(tp);

    TARGET_LIST(tlp, &ripng_targets) {
	if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
	    changes += ripng_policy(tp, tlp, change_list);
	}
    } TARGET_LIST_END(tlp, &ripng_targets);

    /* Close the table */
    rt_close(tp, (gw_entry *) 0, 0, NULL);

    /* Do the changes */
    if (changes) {
	TARGET_LIST(tlp, &ripng_targets) {
	    if (BIT_TEST(tlp->target_flags, TARGETF_SUPPLY)) {
		ripng_supply(tlp,
			     *tlp->target_src,
			     tlp->target_flags,
			     0,
			     TRUE);
	    }
	} TARGET_LIST_END(tlp, &ripng_targets);
    }
}

/*
 * Expire the RIPng routes
 */
void
ripng_expire __PF2(tip, task_timer *,
                   inteval, time_t)
{
    time_t expire_to = time_sec - RIPNG_T_EXPIRE;
    time_t nexttime = time_sec + 1;
    u_int deleted = 0;

    if (expire_to > 0) {
        gw_entry *gwp;

        rt_open(tip->task_timer_task);

        GW_LIST(ripng_gw_list, gwp) {
            rt_entry *rt;

            if (gwp->gw_n_routes == 0) {
                /* No routes for this gateway */
                continue;
            }

            /* Expire any old routes */
            RTQ_LIST(&gwp->gw_rtq, rt) {
                if (rt->rt_time <= expire_to) {
                    /* This route has exipired */

                    rt_delete(rt);
                    deleted++;
                } else {
                    /* This is the next route to expire */
                    if (rt->rt_time < nexttime) {
                        nexttime = rt->rt_time;
                    }
                    break;
                }
            } RTQ_LIST_END(&gwp->gw_rtq, rt);
        } GW_LIST_END(ripng_gw_list, gwp);

        rt_close(tip->task_timer_task, (gw_entry *) 0, 0, NULL);
    }

    if (nexttime > time_sec) {
        /* No routes to expire */
        nexttime = time_sec;
    }

    task_timer_set(tip, (time_t) 0, nexttime + RIPNG_T_EXPIRE - time_sec);

    trace_tp(tip->task_timer_task,
             TR_POLICY,
             0,
             ("task_expire: %d routes holddown, next expire %d sec later",
              deleted,
              nexttime + RIPNG_T_EXPIRE - time_sec));
}
