#!/usr/bin/awk -f
#
# $XORP: xorp/fea/xorp_fea_click_config_generator,v 1.14 2007/11/29 01:52:36 pavlin Exp $
#

#
# A sample script to generate Click configuration from XORP configuration.
# The first argument is the XORP configuration file name.
#
# Currently, the script is called on-demand by the FEA whenever
# the network interface information changes.
#
# Requirements:
# 1. The Click lookup element names must be "_xorp_rt4" for IPv4 and
#    "_xorp_rt6" for IPv6.  Those elements must support handler interface
#    compatible to elements LinearIPLookup() and LookupIP6Route()
#    respectively for adding and removing forwarding entries.
# 2. The first network interface/vif must be connected to _xorp_rt4[0] and
#    _xorp_rt6[0], the second network interface/vif must be connected to
#    _xorp_rt4[1] and _xorp_rt6[1], and so-on.
#    The last port in _xorp_rt{4,6} is reserved for local delivery.
#    Note that the interfaces/vifs are ordered lexigraphically by their names.
#
# Note that if both kernel-level and user-level Click are enabled
# (which is allowed), this script will print the kernel-level Click
# configuration.
#
# TODO: for now the IPv6 path generation is disabled by default, because:
#   - There are few incomplete IPv6-related pieces (search for the "TODO"
#     marker in the code below).
#   - Currently the lastest Click release (1.4.3) does not support an
#     IPv6 lookup element that has "add" and "delete" handlers to
#     add/delete routing entries.
#     Note that the lastest implementation of the LookupIP6Route()
#     in the CVS tree supports those two handlers.
#
# To enable IPv6 path generation, modify below:
#    enable_ipv6 = 0;
# to
#    enable_ipv6 = 1;
#

BEGIN {
    # XXX: for now IPv6 generation is disabled
    enable_ipv6 = 0;

    # Lookup element flavor selection
    # IPv4: LinearIPLookup, DirectIPLookup and TrieIPLookup should work
    ipv4_lookup_element = "LinearIPLookup";
    # IPv6: LookupIP6Route is the only option at the moment
    ipv6_lookup_element = "LookupIP6Route";

    # Various constants
    ipv4_addr_bitlen = 32;	# IPv4 address bitmask length
    ipv6_addr_bitlen = 128;	# IPv6 address bitmask length

    iftree_interfaces_max = 0;
    iftree_vifs_max = 0;
    is_click_enabled = 0;
    is_kernel_click = 0;
    is_user_click = 0;
    is_debug = 0;

    xorp_ip4 = "_xorp_ip4";
    xorp_rt4 = "_xorp_rt4";
    xorp_ip6 = "_xorp_ip6";
    xorp_rt6 = "_xorp_rt6";
    xorp_arpt = "_xorp_arpt";
    xorp_toh = "_xorp_toh";
    # TODO: XXX: fix ether_encap if necessary
    ether_encap4 = "EtherEncap(0x0800, 1:1:1:1:1:1, 2:2:2:2:2:2)";
    ether_encap6 = "EtherEncap(0x86dd, 1:1:1:1:1:1, 2:2:2:2:2:2)";

    statement = "";
    in_comment = 0;
    is_syntax_error = 0;
    is_internal_error = 0;
    config_level = 0;
    ignore_level[0] = 1;
    is_level0_interfaces = 0;
    is_level0_fea = 0;
    is_level0_fea_level1_click = 0;
    is_level0_fea_level1_click_level2_kernel_click = 0;
    is_level0_fea_level1_click_level2_user_click = 0;
    is_ipv4_address = 0;
    is_ipv6_address = 0;
    max_xorp_rt_port = 0;
    iftree_interfaces_targetname = "fea";
    fea_targetname = "fea";
}

function calculate_state()
{
    iftree_vifs_max = 0;
    xorp_rt_port = 0;

    #
    # Clear-up some temporary state
    # At the same time, compute the value of iftree_vifs_max
    #
    for (ii = 0; ii < iftree_interfaces_max; ii++) {
	ifaces_chosen[ii] = 0;
	for (vi = 0; vi < iftree_interfaces_vifs_max[ii]; vi++) {
	    ifaces_vifs_chosen[ii,vi] = 0;
	    iftree_vifs_max++;
	}
    }

    #
    # Select the interfaces and vifs by using string comparison:
    # the "smaller" name first.
    #
    for (ci = 0; ci < iftree_interfaces_max; ci++) {
	best_ii = -1;
	for (ii = 0; ii < iftree_interfaces_max; ii++) {
	    if (ifaces_chosen[ii])
		continue;
	    if (best_ii == -1) {
		best_ii = ii;
		continue;
	    }
	    best_ifname = iftree_interfaces_ifname[best_ii];
	    if (iftree_interfaces_ifname[ii] < best_ifname) {
		best_ii = ii;
	    }
	}
	if (best_ii < 0) {
	    internal_error("Internal error: cannot sort the interface names\n");
	}
	ifaces_chosen[best_ii] = 1;

	for (cv = 0; cv < iftree_interfaces_vifs_max[best_ii]; cv++) {
	    best_vi = -1;
	    for (vi = 0; vi < iftree_interfaces_vifs_max[best_ii]; vi++) {
		if (ifaces_vifs_chosen[best_ii,vi])
		    continue;
		if (best_vi == -1) {
		    best_vi = vi;
		    continue;
		}
		best_vifname = iftree_interfaces_vifs_vifname[best_ii,best_vi];
		if (iftree_interfaces_vifs_vifname[best_ii,vi] < best_vifname) {
		    best_vi = vi;
		}
	    }
	    if (best_vi < 0) {
		internal_error("Internal error: cannot sort the vif names\n");
	    }
	    ifaces_vifs_chosen[best_ii,best_vi] = 1;

	    iftree_interfaces_vifs_port[best_ii,best_vi] = xorp_rt_port++;
	}
    }

    max_xorp_rt_port = xorp_rt_port;
}

function check_state()
{
    for (ii = 0; ii < iftree_interfaces_max; ii++) {
	ifname = iftree_interfaces_ifname[ii];
	if (iftree_interfaces_has_mac[ii] != 1) {
	    message = "Missing MAC address configuration for interface " ifname;
	    syntax_error(message);
	}
	if (iftree_interfaces_has_mtu[ii] != 1) {
	    message = "Missing MTU configuration for interface " ifname;
	    syntax_error(message);
	}
    }
}


function generate_click_config_header()
{
    printf("//\n");
    printf("// Generated by XORP FEA\n");
    printf("//\n");
}

function generate_click_shared_ip_input()
{
    check_ipv4_header = "CheckIPHeader(INTERFACES";
    local_ipv4_routes = "";
    check_ipv6_header = "CheckIP6Header(";	# TODO: is this enough?
    local_ipv6_routes = "";
    is_first_address4 = 1;
    is_first_address6 = 1;

    for (ii = 0; ii < iftree_interfaces_max; ii++) {
	for (vi = 0; vi < iftree_interfaces_vifs_max[ii]; vi++) {
	    for (ai4 = 0; ai4 < iftree_interfaces_vifs_addresses4_max[ii,vi]; ai4++) {
		addr4 = iftree_interfaces_vifs_addresses4_addr[ii,vi,ai4];
		prefix4 = iftree_interfaces_vifs_addresses4_prefix[ii,vi,ai4];
		check_ipv4_header = check_ipv4_header " " addr4 "/" prefix4;
		route = addr4 "/" ipv4_addr_bitlen " " max_xorp_rt_port;
		if (is_first_address4) {
		    local_ipv4_routes = route;
		    is_first_address4 = 0;
		} else {
		    local_ipv4_routes = local_ipv4_routes ", " route;
		}
	    }
	    for (ai6 = 0; ai6 < iftree_interfaces_vifs_addresses6_max[ii,vi]; ai6++) {
		addr6 = iftree_interfaces_vifs_addresses6_addr[ii,vi,ai6];
		prefix6 = iftree_interfaces_vifs_addresses6_prefix[ii,vi,ai6];
		#
		# XXX: unlike the CheckIPHeader() element, the
		# CheckIP6Header() element arguments are the list of 
		# bad addresses instead of our addresses.
		#
		#check_ipv6_header = check_ipv6_header " " addr6 "/" prefix6;
		route = addr6 "/" ipv6_addr_bitlen " " max_xorp_rt_port;
		if (is_first_address6) {
		    local_ipv6_routes = route;
		    is_first_address6 = 0;
		} else {
		    local_ipv6_routes = local_ipv6_routes ", " route;
		}
	    }
	}
    }
    check_ipv4_header = check_ipv4_header ")";
    check_ipv6_header = check_ipv6_header ")";

    printf("\n\n");
    printf("// Shared IPv4 input path and routing table\n");
    printf("\n");
    printf("    %s :: Strip(14)\n", xorp_ip4);
    printf("   -> %s\n", check_ipv4_header);
    printf("   -> %s :: %s(%s);\n", xorp_rt4, ipv4_lookup_element,
	   local_ipv4_routes);

    if (enable_ipv6) {
	printf("\n\n");
	printf("// Shared IPv6 input path and routing table\n");
	printf("\n");
	printf("    %s :: Strip(14)\n", xorp_ip6);
	printf("   -> %s\n", check_ipv6_header);
	printf("   -> GetIP6Address(24)\n");	# XXX: need this in IPv6!
	printf("   -> %s :: %s(%s)\n", xorp_rt6, ipv6_lookup_element,
	       local_ipv6_routes);
    }
}

function generate_click_arp_responses()
{
    printf("\n\n");
    printf("// ARP responses are copied to each ARPQuerier and the host.\n");
    printf("\n");
    printf("    %s :: Tee(%u);\n", xorp_arpt, iftree_vifs_max + 1);
}

function generate_click_input_output_paths()
{
    port = 0;

    for (ii = 0; ii < iftree_interfaces_max; ii++) {
	for (vi = 0; vi < iftree_interfaces_vifs_max[ii]; vi++) {
	    xorp_c = "_xorp_c" port;
	    xorp_out = "_xorp_out" port;
	    xorp_to_device = "_xorp_to_device" port;
	    xorp_ar = "_xorp_ar" port;
	    xorp_arpq = "_xorp_arpq" port;
	    xorp_nda = "_xorp_nda" port;
	    xorp_nds = "_xorp_nds" port;

	    if (! iftree_interfaces_vifs_enabled[ii,vi]) {
		from_device = "NullDevice";
	    } else {
		#
		# TODO: XXX: we should find a way to configure polling
		# devices. Such devices should use "PollDevice" instead
		# of "FromDevice".
		#
		from_device = "FromDevice(" iftree_interfaces_vifs_vifname[ii,vi] ")";
	    }

	    if (! iftree_interfaces_vifs_enabled[ii,vi]) {
		to_device = "Discard";
	    } else {
		to_device = "ToDevice(" iftree_interfaces_vifs_vifname[ii,vi] ")";
	    }

	    // IPv4 ARP
	    arp_responder = "ARPResponder(";
	    mac = iftree_interfaces_mac[ii];
	    is_begin = 1;
	    for (ai4 = 0; ai4 < iftree_interfaces_vifs_addresses4_max[ii,vi]; ai4++) {
		addr4 = iftree_interfaces_vifs_addresses4_addr[ii,vi,ai4];
		if (is_begin)
		    arp_responder = arp_responder addr4;
		else
		    arp_responder = arp_responder " " addr4;
		is_begin = 0;
	    }
	    arp_responder = arp_responder " " mac ")";

	    first_addr4 = "0.0.0.0";
	    if (iftree_interfaces_vifs_addresses4_max[ii,vi] > 0) {
		first_addr4 = iftree_interfaces_vifs_addresses4_addr[ii,vi,0];
	    }
	    arp_querier = "ARPQuerier(" first_addr4 ", " mac ")";

	    # IPv6 Neighbor Discovery
	    ip6_ndadvertiser = "IP6NDAdvertiser(";
	    is_begin = 1;
	    for (ai6 = 0; ai6 < iftree_interfaces_vifs_addresses6_max[ii,vi]; ai6++) {
		if (is_begin)
		    is_begin = 0;
		else
		    ip6_ndadvertiser = ip6_ndadvertiser ", ";
		addr6 = iftree_interfaces_vifs_addresses6_addr[ii,vi,ai6];
		# TODO: fix prefix length fetching!!!
		ip6_ndadvertiser = ip6_ndadvertiser addr6 "/64 " mac;
	    }
	    ip6_ndadvertiser = ip6_ndadvertiser ")";

	    first_addr6 = "0::0";
	    if (iftree_interfaces_vifs_addresses6_max[ii,vi] > 0) {
		first_addr6 = iftree_interfaces_vifs_addresses6_addr[ii,vi,0];
	    }
	    ip6_ndsolicitor = "IP6NDSolicitor(" first_addr6 ", " mac ")";

	    paint = "Paint(" port + 1 ")";
	    print_unknown = "Print(\"" iftree_interfaces_vifs_vifname[ii,vi] " unknown protocol\")";

	    printf("\n\n");
	    printf("// Input and output paths for %s\n",
		   iftree_interfaces_vifs_vifname[ii,vi]);
	    printf("\n");
	    printf("    %s -> %s :: Classifier(\n",
		   from_device,
		   xorp_c);
	    printf("    12/0800,		// [0] IPv4 packet\n");
	    printf("    12/0806 20/0001,	// [1] ARP request\n");
	    printf("    12/0806 20/0002,	// [2] ARP reply\n");
	    printf("    12/86dd 20/3aff 54/87,	// [3] IPv6 ICMP ND solicitation\n");
	    printf("    12/86dd 20/3aff 54/88,	// [4] IPv6 ICMP ND advertisment\n");
	    printf("	12/86dd,		// [5] IPv6 packet\n");
	    printf("    -)			// [6] Unsupported protocol;\n");
	    printf("    %s :: Queue(200) -> %s :: %s;\n",
		   xorp_out,
		   xorp_to_device,
		   to_device);

	    # IPv4
	    printf("\n");
	    printf("    // IPv4\n");
	    # Plain IPv4 packets
	    printf("    %s[0] -> %s -> %s;\n",
		   xorp_c,
		   paint,
		   xorp_ip4);
	    # ARP request
	    printf("    %s[1] -> %s -> %s;\n",
		   xorp_c,
		   arp_responder,
		   xorp_out);
	    # ARP reply
	    printf("    %s :: %s -> %s;\n",
		   xorp_arpq,
		   arp_querier,
		   xorp_out);
	    printf("    %s[2] -> %s;\n",
		   xorp_c,
		   xorp_arpt);
	    printf("    %s[%u] -> [1]%s;\n",
		   xorp_arpt,
		   port,
		   xorp_arpq);

	    # IPv6
	    if (enable_ipv6) {
		printf("\n");
		printf("    // IPv6\n");
		# Plain IPv6 packets
		printf("    %s[5] -> %s -> %s;\n",
		       xorp_c,
		       paint,
		       xorp_ip6);
		# ICMP Neighbor Discovery Solicitation
		printf("    %s[3] -> %s -> %s;\n",
		       xorp_c,
		       ip6_ndadvertiser,
		       xorp_out);
		# ICMP Neighbor Discovery Advertisment
		printf("    %s :: %s -> %s;\n",
		       xorp_nds,
		       ip6_ndsolicitor,
		       xorp_out);
		printf("    %s[4] -> [1]%s;\n",
		       xorp_c,
		       xorp_nds);
	    } else {
		printf("\n");
		printf("    // Discard IPv6\n");
		# Plain IPv6 packets
		printf("    %s[5] -> Discard;\n",
		       xorp_c);
		# ICMP Neighbor Discovery Solicitation
		printf("    %s[3] -> Discard;\n",
		       xorp_c);
		printf("    %s[4] -> Discard;\n",
		       xorp_c);
	    }

	    # Unknown protocol
	    printf("\n");
	    printf("    // Unknown protocol\n");
	    printf("    %s[6] -> %s -> Discard;\n",
		   xorp_c,
		   print_unknown);

	    port++;
	}
    }
}

function generate_click_send_packets_to_host()
{
    printf("\n\n");
    printf("// Local delivery\n");
    # TODO: XXX: Fix the Local delivery for *BSD and/or user-level Click
    printf("\n");
    if (is_kernel_click) {
	#
	# XXX: Note that if both kernel-level and user-level Click are enabled
	# (which is allowed), this script will print the kernel-level Click
	# configuration.
	#
	printf("    %s :: ToHost;\n", xorp_toh);
    } else {
	printf("    %s :: Discard;\n", xorp_toh);
    }

    # XXX: last port in xorp_rt{4,6} is reserved for local delivery
    printf("\n");
    printf("    // IPv4\n");
    printf("    %s[%u] -> %s -> %s;\n",
	   xorp_rt4,
	   max_xorp_rt_port,
	   ether_encap4,
	   xorp_toh);
    printf("    %s[%u] -> %s;\n",
	   xorp_arpt,
	   iftree_vifs_max,
	   xorp_toh);

    if (enable_ipv6) {
	printf("\n");
	printf("    // IPv6\n");
	printf("    %s[%u] -> %s -> %s;\n",
	       xorp_rt6,
	       max_xorp_rt_port,
	       ether_encap6,
	       xorp_toh);
    }
}

function generate_click_forwarding_paths()
{
    #
    # Forwarding paths for each interface
    #
    port = 0;
    for (ii = 0; ii < iftree_interfaces_max; ii++) {
	for (vi = 0; vi < iftree_interfaces_vifs_max[ii]; vi++) {
	    xorp_cp = "_xorp_cp" port;
	    paint_tee = "PaintTee(" port + 1 ")";
	    xorp_gio = "_xorp_gio" port;
	    xorp_dt = "_xorp_dt" port;
	    xorp_fr = "_xorp_fr" port;
	    xorp_arpq = "_xorp_arpq" port;
	    xorp_nds = "_xorp_nds" port;

	    ipgw_options = "IPGWOptions(";
	    is_begin = 1;
	    for (ai4 = 0; ai4 < iftree_interfaces_vifs_addresses4_max[ii,vi]; ai4++) {
		addr4 = iftree_interfaces_vifs_addresses4_addr[ii,vi,ai4];
		if (is_begin)
		    ipgw_options = ipgw_options addr4;
		else
		    ipgw_options = ipgw_options ", " addr4;
		is_begin = 0;
	    }
	    ipgw_options = ipgw_options ")";

	    first_addr4 = "0.0.0.0";
	    if (iftree_interfaces_vifs_addresses4_max[ii,vi] > 0) {
		first_addr4 = iftree_interfaces_vifs_addresses4_addr[ii,vi,0];
	    }
	    fix_ip_src = "FixIPSrc(" first_addr4 ")";
	    mtu = iftree_interfaces_mtu[ii];
	    ip_fragmenter = "IPFragmenter(" mtu ")";

	    printf("\n\n");
	    printf("// Forwarding path for %s\n",
		   iftree_interfaces_vifs_vifname[ii,vi]);
	    xorp_rt_port = iftree_interfaces_vifs_port[ii,vi];

	    printf("\n");
	    printf("    // IPv4\n");
	    printf("    %s[%u] -> DropBroadcasts\n",
		   xorp_rt4,
		   xorp_rt_port);
	    printf("    -> %s :: %s\n",
		   xorp_cp,
		   paint_tee);
	    printf("    -> %s :: %s\n",
		   xorp_gio,
		   ipgw_options);
	    printf("    -> %s\n",
		   fix_ip_src);
	    printf("    -> %s :: DecIPTTL\n",
		   xorp_dt);
	    printf("    -> %s :: %s\n",
		   xorp_fr,
		   ip_fragmenter);
	    printf("    -> [0]%s;\n",
		   xorp_arpq);

	    printf("    %s[1] -> ICMPError(%s, timeexceeded) -> %s;\n",
		   xorp_dt,
		   first_addr4,
		   xorp_rt4);
	    printf("    %s[1] -> ICMPError(%s, unreachable, needfrag) -> %s;\n",
		   xorp_fr,
		   first_addr4,
		   xorp_rt4);
	    printf("    %s[1] -> ICMPError(%s, parameterproblem) -> %s;\n",
		   xorp_gio,
		   first_addr4,
		   xorp_rt4);
	    printf("    %s[1] -> ICMPError(%s, redirect, host) -> %s;\n",
		   xorp_cp,
		   first_addr4,
		   xorp_rt4);

	    if (enable_ipv6) {
		printf("\n");
		printf("    // IPv6\n");
		# TODO: Must fix this bellow!!!
		printf("    %s[%u] -> DropBroadcasts\n",
		       xorp_rt6,
		       xorp_rt_port);
		printf("    -> DecIP6HLIM\n");
		printf("    -> [0]%s\n",
		       xorp_nds);
	    }

	    port++;
	}
    }
}

function is_end_of_statement(token)
{
    # Get the last character
    l = length(token);
    s = substr(token, l, 1);
    if (s == ";")
	return 1;
    else
	return 0;
}

function is_end_of_string(token)
{
    # Get the last character
    l = length(token);
    s = substr(token, l, 1);
    if (s == "\"")
	return 1;
    else
	return 0;
}

function is_begin_of_comment(token)
{
    # Get the first two characters
    l = length(token);
    if (l < 2)
	return 0;
    s = substr(token, 1, 2);
    if (s == "/*")
	return 1;
    else
	return 0;
}

function is_end_of_comment(token)
{
    # Get the last two characters
    l = length(token);
    if (l < 2)
	return 0;
    s = substr(token, l-1, 2);
    if (s == "*/")
	return 1;
    else
	return 0;
}

function process_statement(statement)
{
    if (is_debug)
	printf("Statement: %s\n", statement);

    tokens_n = split(statement, tokens);
    for (i = 1; i <= tokens_n; i++) {
	# printf("Token: %s\n", tokens[i]);
    }
    # TODO: need to deal with strings

    for (i = 1; i <= tokens_n; i++) {
	if (tokens[i] == "{") {
	    config_level++;
	    continue;
	}
	if (tokens[i] == "}") {
	    config_level--;
	    if (config_level < 0)
		syntax_error("Too many }");
	    continue;
	}

	if (config_level == 0) {
	    ignore_level[0] = 1;
	    is_level0_interfaces = 0;
	    is_level0_fea = 0;
	    is_level0_fea_level1_click = 0;
	    is_level0_fea_level1_click_level2_kernel_click = 0;
	    is_level0_fea_level1_click_level2_user_click = 0;
	    if (tokens[i] == "interfaces") {
		ignore_level[0] = 0;
		is_level0_interfaces = 1;
	    }
	    if (tokens[i] == "fea") {
		ignore_level[0] = 0;
		is_level0_fea = 1;
		is_level0_fea_level1_click = 0;
		is_level0_fea_level1_click_level2_kernel_click = 0;
		is_level0_fea_level1_click_level2_user_click = 0;
	    }
	}
	if (ignore_level[0])
	    continue;

	#
	# Configuration section: "interfaces {}"
	#
	if (is_level0_interfaces && (config_level == 1)) {
	    if (tokens[i] == "targetname:") {
		if (i == tokens_n)
		    syntax_error("Missing target name");
		i++;
		v = tokens[i];
		iftree_interfaces_targetname = v;
		continue;
	    }

	    if (tokens[i] == "interface") {
		if (i == tokens_n)
		    syntax_error("Missing interface name");
		i++;
		# TODO: check that the interface name is valid
		v = tokens[i];
		iftree_interfaces_max++;
		p = iftree_interfaces_max - 1;
		iftree_interfaces_ifname[p] = v;
		# XXX: by default each interface is enabled
		iftree_interfaces_enabled[p] = 1;
		continue;
	    }
	    syntax_error("Invalid keyword");
	}


	if (is_level0_interfaces && (config_level == 2)) {
	    if (tokens[i] == "disable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		if (v == "true")
		    iftree_interfaces_enabled[p] = 0;
		else
		    iftree_interfaces_enabled[p] = 1;
		continue;
	    }

	    if (tokens[i] == "description:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		while (! is_end_of_string(v)) {
		    if (i == tokens_n)
			syntax_error("Missing end-of-string");
		    i++;
		    v = v " " tokens[i];
		}
		iftree_interfaces_description[p] = v;
		continue;
	    }

	    if (tokens[i] == "discard:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		iftree_interfaces_discard[p] = v;
		continue;
	    }

	    if (tokens[i] == "unreachable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		iftree_interfaces_unreachable[p] = v;
		continue;
	    }

	    if (tokens[i] == "management:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		iftree_interfaces_management[p] = v;
		continue;
	    }

	    if (tokens[i] == "mac:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		iftree_interfaces_mac[p] = v;
		iftree_interfaces_has_mac[p] = 1;
		continue;
	    }

	    if (tokens[i] == "mtu:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		iftree_interfaces_mtu[p] = v;
		iftree_interfaces_has_mtu[p] = 1;
		continue;
	    }

	    if (tokens[i] == "vif") {
		if (i == tokens_n)
		    syntax_error("Missing vif name");
		i++;
		# TODO: check that the vif name is valid
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		iftree_interfaces_vifs_max[p]++;
		q = iftree_interfaces_vifs_max[p] - 1;
		iftree_interfaces_vifs_vifname[p,q] = v;
		# XXX: by default each vif is enabled
		iftree_interfaces_vifs_enabled[p,q] = 1;
		continue;
	    }
	    syntax_error("Invalid keyword");
	}

	if (is_level0_interfaces && (config_level == 3)) {
	    if (tokens[i] == "disable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (v == "true")
		    iftree_interfaces_vifs_enabled[p,q] = 0;
		else
		    iftree_interfaces_vifs_enabled[p,q] = 1;
		continue;
	    }

	    if (tokens[i] == "address") {
		if (i == tokens_n)
		    syntax_error("Missing address value");
		i++;
		# TODO: check that the address value is valid
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		is_ipv4_address = 0;
		is_ipv6_address = 0;
		if (split(v, tmp_array, ".") == 4)
		    is_ipv4_address = 1;
		if (split(v, tmp_array, ":") > 1)
		    is_ipv6_address = 1;
		if (!( is_ipv4_address || is_ipv6_address))
		    syntax_error("Invalid address value");
		if (is_ipv4_address) {
		    iftree_interfaces_vifs_addresses4_max[p,q]++;
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_addr[p,q,r] = v;
		    # XXX: by default each address is enabled
		    iftree_interfaces_vifs_addresses4_enabled[p,q,r] = 1;
		}
		if (is_ipv6_address) {
		    iftree_interfaces_vifs_addresses6_max[p,q]++;
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses6_addr[p,q,r] = v;
		    # XXX: by default each address is enabled
		    iftree_interfaces_vifs_addresses6_enabled[p,q,r] = 1;
		}
		continue;
	    }
	    syntax_error("Invalid keyword");
	}

	if (is_level0_interfaces && (config_level == 4)) {
	    if (tokens[i] == "prefix-length:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_prefix[p,q,r] = v;
		}
		if (is_ipv6_address) {
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses6_prefix[p,q,r] = v;
		}
		continue;
	    }

	    if (tokens[i] == "broadcast:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_destination[p,q,r] = v;
		}
		if (is_ipv6_address) {
		    syntax_error("Invalid keyword");
		}
		continue;
	    }

	    if (tokens[i] == "destination:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_destination[p,q,r] = v;
		}
		if (is_ipv6_address) {
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses6_destination[p,q,r] = v;
		}
		continue;
	    }

	    if (tokens[i] == "multicast-capable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_multicast[p,q,r] = v;
		}
		if (is_ipv6_address) {
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses6_multicast[p,q,r] = v;
		}
		continue;
	    }

	    if (tokens[i] == "point-to-point:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_p2p[p,q,r] = v;
		}
		if (is_ipv6_address) {
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses6_p2p[p,q,r] = v;
		}
		continue;
	    }

	    if (tokens[i] == "loopback:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses4_loopback[p,q,r] = v;
		}
		if (is_ipv6_address) {
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    iftree_interfaces_vifs_addresses6_loopback[p,q,r] = v;
		}
		continue;
	    }

	    if (tokens[i] == "disable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		p = iftree_interfaces_max - 1;
		q = iftree_interfaces_vifs_max[p] - 1;
		if (is_ipv4_address) {
		    r = iftree_interfaces_vifs_addresses4_max[p,q] - 1;
		    if (v == "true")
			iftree_interfaces_vifs_addresses4_enabled[p,q,r] = 0;
		    else
			iftree_interfaces_vifs_addresses4_enabled[p,q,r] = 1;
		}
		if (is_ipv6_address) {
		    r = iftree_interfaces_vifs_addresses6_max[p,q] - 1;
		    if (v == "true")
			iftree_interfaces_vifs_addresses6_enabled[p,q,r] = 0;
		    else
			iftree_interfaces_vifs_addresses6_enabled[p,q,r] = 1;
		}
		continue;
	    }

	    syntax_error("Invalid keyword");
	}

	#
	# Configuration section: "fea {}"
	#
	if (is_level0_fea && (config_level == 1)) {
	    if (tokens[i] == "targetname:") {
		if (i == tokens_n)
		    syntax_error("Missing target name");
		i++;
		v = tokens[i];
		fea_targetname = v;
		continue;
	    }

	    if (tokens[i] == "click") {
		is_level0_fea_level1_click = 1;
		is_level0_fea_level1_click_level2_kernel_click = 0;
		is_level0_fea_level1_click_level2_user_click = 0;
		# XXX: by default Click is enabled
		is_click_enabled = 1;
		continue;
	    }

	    # XXX: ignore the rest of the configuration
	    continue;
	}

	if (is_level0_fea && is_level0_fea_level1_click &&
	    (config_level == 2)) {

	    if (tokens[i] == "disable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		if (v == "true")
		    is_click_enabled = 0;
		else
		    is_click_enabled = 1;
		continue;
	    }

	    if (tokens[i] == "kernel-click") {
		is_level0_fea_level1_click_level2_kernel_click = 1;
		is_level0_fea_level1_click_level2_user_click = 0;
		# XXX: by default kernel-level Click is enabled
		is_kernel_click = 1;
		continue;
	    }

	    if (tokens[i] == "user-click") {
		is_level0_fea_level1_click_level2_kernel_click = 0;
		is_level0_fea_level1_click_level2_user_click = 1;
		# XXX: by default user-level Click is enabled
		is_user_click = 1;
		continue;
	    }

	    # XXX: ignore the rest of the configuration
	    continue;
	}

	if (is_level0_fea && is_level0_fea_level1_click &&
	    is_level0_fea_level1_click_level2_kernel_click &&
	    (config_level == 3)) {
	    if (tokens[i] == "disable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		if (v == "true") {
		    is_kernel_click = 0;
		} else {
		    is_kernel_click = 1;
		}
		continue;
	    }

	    # XXX: ignore the rest of the configuration
	    continue;
	}

	if (is_level0_fea && is_level0_fea_level1_click &&
	    is_level0_fea_level1_click_level2_user_click &&
	    (config_level == 3)) {
	    if (tokens[i] == "disable:") {
		if (i == tokens_n)
		    syntax_error("Missing value");
		i++;
		v = tokens[i];
		if (v == "true") {
		    is_user_click = 0;
		} else {
		    is_user_click = 1;
		}
		continue;
	    }

	    # XXX: ignore the rest of the configuration
	    continue;
	}
    }
}

function syntax_error(message)
{
    is_syntax_error = 1;
    printf("Syntax error (file %s line %d): %s\n", FILENAME, FNR,
	   message) > "/dev/stderr";
    exit(1);
}

function internal_error(message)
{
    is_internal_error = 1;
    printf("Internal error (file %s): %s\n", FILENAME,
	   message) > "/dev/stderr";
    exit(1);
}

{
    for (i = 1; i <= NF; i++) {
	token = $i;

	# Filter-out the comments
	if (is_begin_of_comment(token)) {
	    if (in_comment)
		syntax_error("Nested comments");
	    in_comment = 1;
	}
	if (in_comment) {
	    if (is_end_of_comment(token))
		in_comment = 0;
	    continue;
	}
	if (is_end_of_comment(token))
	    syntax_error("Missing begin-of-comment");

	statement = statement " " token;
	if (is_end_of_statement(token)) {
	    process_statement(statement);
	    statement = "";
	}
    }
    process_statement(statement);
    statement = "";
}

END {
    #
    # Initialize internal state
    #
    calculate_state();
    check_state();

    #
    # Check for syntax and internal errors
    #
    if (is_syntax_error || is_internal_error)
	exit(1);

    #
    # Check if Click is enabled and whether it is user-level or kernel Click
    #
    if (! is_click_enabled)
	exit(0);		# XXX: don't print any Click configuration
    if ((! is_user_click) && (! is_kernel_click))
	exit(0);		# XXX: don't print any Click configuration

    generate_click_config_header();
    generate_click_shared_ip_input();
    generate_click_arp_responses();
    generate_click_input_output_paths();
    generate_click_send_packets_to_host();
    generate_click_forwarding_paths();

    #
    # Check again for syntax and internal errors
    #
    if (is_syntax_error || is_internal_error)
	exit(1);

    exit(0);
}

# Local Variables:
# mode: AWK
# sh-indentation: 4
# End:
