// -*- c-basic-offset: 4; tab-width: 8; indent-tabs-mode: t -*-

// Copyright (c) 2001-2007 International Computer Science Institute
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software")
// to deal in the Software without restriction, subject to the conditions
// listed in the XORP LICENSE file. These conditions include: you must
// preserve this copyright notice, and you cannot mention the copyright
// holders in advertising related to the Software without their permission.
// The Software is provided WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED. This
// notice is a summary of the XORP LICENSE file; the license in that file is
// legally binding.

#ident "$XORP: xorp/fea/ifconfig_set.cc,v 1.39 2007/02/16 22:45:43 pavlin Exp $"

#include "fea_module.h"

#include "libxorp/xorp.h"
#include "libxorp/xlog.h"
#include "libxorp/debug.h"
#include "libxorp/ether_compat.h"

#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif

#include "ifconfig.hh"
#include "ifconfig_set.hh"


//
// Set information about network interfaces configuration with the
// underlying system.
//


IfConfigSet::IfConfigSet(IfConfig& ifc)
    : _is_running(false),
      _ifc(ifc),
      _is_primary(true)
{
    
}

IfConfigSet::~IfConfigSet()
{
    
}

void
IfConfigSet::register_ifc_primary()
{
    _ifc.register_ifc_set_primary(this);
}

void
IfConfigSet::register_ifc_secondary()
{
    _ifc.register_ifc_set_secondary(this);

    //
    // XXX: push the current config into the new secondary
    //
    if (_is_running)
	push_config(_ifc.pushed_config());
}

bool
IfConfigSet::push_config(IfTree& it)
{
    IfTree::IfMap::iterator ii;
    IfTreeInterface::VifMap::iterator vi;

    // Clear errors associated with error reporter
    ifc().er().reset();

    //
    // Sanity check config - bail on bad interface and bad vif names
    //
    for (ii = it.ifs().begin(); ii != it.ifs().end(); ++ii) {
	IfTreeInterface& i = ii->second;
	//
	// Skip the ifindex check if the interface has no mapping to
	// an existing interface in the system.
	//
	if (i.is_soft() || (i.discard() && is_discard_emulated(i)))
	    continue;

	//
	// Check that the interface is recognized by the system
	//
	if (ifc().get_insert_ifindex(i.ifname()) == 0) {
	    if (i.state() == IfTreeItem::DELETED) {
		// XXX: ignore deleted interfaces that are not recognized
		continue;
	    }
	    ifc().er().interface_error(i.ifname(), "interface not recognized");
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return false;
	}

	//
	// Check the interface and vif name
	//
	for (vi = i.vifs().begin(); vi != i.vifs().end(); ++vi) {
	    IfTreeVif& v= vi->second;
	    if (v.vifname() != i.ifname()) {
		ifc().er().vif_error(i.ifname(), v.vifname(), "bad vif name");
		XLOG_ERROR("%s", ifc().er().last_error().c_str());
		return false;
	    }
	}
    }

    //
    // Walk config
    //
    push_iftree_begin();
    for (ii = it.ifs().begin(); ii != it.ifs().end(); ++ii) {
	IfTreeInterface& i = ii->second;

	// Set the "discard_emulated" flag if the discard interface is emulated
	if (i.discard() && is_discard_emulated(i))
	    i.set_discard_emulated(true);

	// Soft interfaces and their child nodes should never be pushed.
	if (i.is_soft())
	    continue;

	if (i.discard() && is_discard_emulated(i))
	    continue;

	if ((ifc().get_insert_ifindex(i.ifname()) == 0)
	    && (i.state() == IfTreeItem::DELETED)) {
		// XXX: ignore deleted interfaces that are not recognized
		continue;
	}

	push_interface_begin(i);

	for (vi = i.vifs().begin(); vi != i.vifs().end(); ++vi) {
	    IfTreeVif& v = vi->second;

	    // XXX: delete the vif if the interface is deleted
	    if (i.state() == IfTreeItem::DELETED)
		v.mark(IfTreeItem::DELETED);

	    push_vif_begin(i, v);

	    IfTreeVif::V4Map::iterator a4i;
	    for (a4i = v.v4addrs().begin(); a4i != v.v4addrs().end(); ++a4i) {
		IfTreeAddr4& a = a4i->second;
		// XXX: delete the address if the vif is deleted
		if (v.state() == IfTreeItem::DELETED)
		    a.mark(IfTreeItem::DELETED);
		if (a.state() != IfTreeItem::NO_CHANGE)
		    push_vif_address(i, v, a);
	    }

#ifdef HAVE_IPV6
	    IfTreeVif::V6Map::iterator a6i;
	    for (a6i = v.v6addrs().begin(); a6i != v.v6addrs().end(); ++a6i) {
		IfTreeAddr6& a = a6i->second;
		// XXX: delete the address if the vif is deleted
		if (v.state() == IfTreeItem::DELETED)
		    a.mark(IfTreeItem::DELETED);
		if (a.state() != IfTreeItem::NO_CHANGE)
		    push_vif_address(i, v, a);
	    }
#endif // HAVE_IPV6

	    push_vif_end(i, v);
	}

	push_interface_end(i);
    }
    push_iftree_end();

    if (ifc().er().error_count() != 0)
	return false;
    
    return true;
}

void
IfConfigSet::push_iftree_begin()
{
    string error_msg;

    //
    // Begin the configuration
    //
    do {
	if (config_begin(error_msg) < 0) {
	    error_msg = c_format("Failed to begin configuration: %s",
				 error_msg.c_str());
	    ifc().er().config_error(error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}
	break;
    } while (false);
}

void
IfConfigSet::push_iftree_end()
{
    string error_msg;

    //
    // End the configuration
    //
    do {
	if (config_end(error_msg) < 0) {
	    error_msg = c_format("Failed to end configuration: %s",
				 error_msg.c_str());
	    ifc().er().config_error(error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}
	break;
    } while (false);
}

void
IfConfigSet::push_interface_begin(const IfTreeInterface& i)
{
    string error_msg;
    uint32_t if_index = ifc().get_insert_ifindex(i.ifname());
    XLOG_ASSERT(if_index > 0);

    //
    // Add the interface
    //
    do {
	if (add_interface(i.ifname(), if_index, error_msg) < 0) {
	    error_msg = c_format("Failed to add interface: %s",
				 error_msg.c_str());
	    ifc().er().interface_error(i.ifname(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}
	break;
    } while (false);
}

void
IfConfigSet::push_interface_end(IfTreeInterface& i)
{
    string error_msg;
    uint32_t if_index = ifc().get_insert_ifindex(i.ifname());
    XLOG_ASSERT(if_index > 0);
    uint32_t new_flags = 0;
    bool new_up = false;
    bool deleted = false;

    //
    // Set the flags
    //
    do {
	uint32_t pulled_flags = 0;
	bool pulled_up, enabled;
	IfTree::IfMap::const_iterator ii = ifc().pulled_config().get_if(i.ifname());

	deleted = i.is_marked(IfTreeItem::DELETED);
	enabled = i.enabled();

	// Get the flags from the pulled config
	if (ii != ifc().pulled_config().ifs().end())
	    pulled_flags = ii->second.if_flags();
	new_flags = pulled_flags;

	pulled_up = pulled_flags & IFF_UP;
	if (pulled_up && (deleted || !enabled))
	    new_flags &= ~IFF_UP;
	if ( (!pulled_up) && enabled)
	    new_flags |= IFF_UP;

	new_up = (new_flags & IFF_UP)? true : false;

	if (is_primary() && (new_flags == pulled_flags))
	    break;		// XXX: nothing changed

	if (config_interface(i.ifname(), if_index, new_flags, new_up,
			     deleted, error_msg)
	    < 0) {
	    error_msg = c_format("Failed to configure interface: %s",
				 error_msg.c_str());
	    ifc().er().interface_error(i.ifname(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}
	break;
    } while (false);

    //
    // Set the MTU
    //
    do {
	bool was_disabled = false;
	uint32_t new_mtu = i.mtu();
	uint32_t pulled_mtu = 0;
	IfTree::IfMap::const_iterator ii = ifc().pulled_config().get_if(i.ifname());

	if (ii != ifc().pulled_config().ifs().end())
	    pulled_mtu = ii->second.mtu();

	if (is_secondary() && (new_mtu == 0)) {
	    // Get the MTU from the pulled config
	    new_mtu = pulled_mtu;
	}

	if (new_mtu == 0)
	    break;		// XXX: ignore the MTU setup

	if (is_primary() && (new_mtu == pulled_mtu))
	    break;		// Ignore: the MTU hasn't changed

	if (is_primary()
	    && (ii != ifc().pulled_config().ifs().end())
	    && new_up) {
	    //
	    // XXX: Set the interface DOWN otherwise we may not be able to
	    // set the MTU (limitation imposed by the Linux kernel).
	    //
	    config_interface(i.ifname(), if_index, new_flags & ~IFF_UP, false,
			     deleted, error_msg);
	    was_disabled = true;
	}

	if (set_interface_mtu(i.ifname(), if_index, new_mtu, error_msg) < 0) {
	    error_msg = c_format("Failed to set MTU to %u bytes: %s",
				 XORP_UINT_CAST(new_mtu),
				 error_msg.c_str());
	    ifc().er().interface_error(i.name(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    if (was_disabled) {
		config_interface(i.ifname(), if_index, new_flags, true,
				 deleted, error_msg);
	    }
	    return;
	}

	if (was_disabled) {
	    config_interface(i.ifname(), if_index, new_flags, true,
			     deleted, error_msg);
	    i.set_flipped(true);	// XXX: the interface was flipped
	}

	break;
    } while (false);

    //
    // Set the MAC address
    //
    do {
	bool was_disabled = false;
	Mac new_mac = i.mac();
	Mac pulled_mac;
	IfTree::IfMap::const_iterator ii = ifc().pulled_config().get_if(i.ifname());

	if (ii != ifc().pulled_config().ifs().end())
	    pulled_mac = ii->second.mac();

	if (is_secondary() && new_mac.empty()) {
	    // Get the MAC from the pulled config
	    new_mac = pulled_mac;
	}

	if (new_mac.empty())
	    break;		// XXX: ignore the MAC setup

	if (is_primary() && (new_mac == pulled_mac))
	    break;		// Ignore: the MAC hasn't changed

	struct ether_addr ea;
	try {
	    EtherMac em;
	    em = EtherMac(new_mac);
	    if (em.get_ether_addr(ea) == false) {
		error_msg = c_format("Expected Ethernet MAC address, "
				     "got \"%s\"",
				     new_mac.str().c_str());
		ifc().er().interface_error(i.name(), error_msg);
		XLOG_ERROR("%s", ifc().er().last_error().c_str());
		return;
	    }
	} catch (const BadMac& bm) {
	    error_msg = c_format("Invalid MAC address \"%s\"",
				 new_mac.str().c_str());
	    ifc().er().interface_error(i.name(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}

	if (is_primary()
	    && (ii != ifc().pulled_config().ifs().end())
	    && new_up) {
	    //
	    // XXX: Set the interface DOWN otherwise we may not be able to
	    // set the MAC address (limitation imposed by the Linux kernel).
	    //
	    config_interface(i.ifname(), if_index, new_flags & ~IFF_UP, false,
			     deleted, error_msg);
	    was_disabled = true;
	}

	if (set_interface_mac_address(i.ifname(), if_index, ea, error_msg)
	    < 0) {
	    error_msg = c_format("Failed to set the MAC address: %s",
				 error_msg.c_str());
	    ifc().er().interface_error(i.name(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    if (was_disabled) {
		config_interface(i.ifname(), if_index, new_flags, true,
				 deleted, error_msg);
	    }
	    return;
	}

	if (was_disabled) {
	    config_interface(i.ifname(), if_index, new_flags, true,
			     deleted, error_msg);
	    i.set_flipped(true);	// XXX: the interface was flipped
	}

	break;
    } while(false);
}

void
IfConfigSet::push_vif_begin(const IfTreeInterface&	i,
			  const IfTreeVif&		v)
{
    string error_msg;
    uint32_t if_index = ifc().get_insert_ifindex(i.ifname());
    XLOG_ASSERT(if_index > 0);

    //
    // Add the vif
    //
    do {
	if (add_vif(i.ifname(), v.vifname(), if_index, error_msg) < 0) {
	    error_msg = c_format("Failed to add vif: %s", error_msg.c_str());
	    ifc().er().vif_error(i.ifname(), v.vifname(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}
	break;
    } while (false);
}

void
IfConfigSet::push_vif_end(const IfTreeInterface&	i,
			  const IfTreeVif&		v)
{
    string error_msg;
    uint32_t if_index = ifc().get_insert_ifindex(i.ifname());
    XLOG_ASSERT(if_index > 0);

    //
    // Set the flags
    //
    do {
	uint32_t pulled_flags = 0;
	uint32_t new_flags;
	bool pulled_up, new_up, deleted, enabled;
	bool pulled_broadcast = false;
	bool pulled_loopback = false;
	bool pulled_point_to_point = false;
	bool pulled_multicast = false;
	bool new_broadcast = false;
	bool new_loopback = false;
	bool new_point_to_point = false;
	bool new_multicast = false;
	IfTree::IfMap::const_iterator ii = ifc().pulled_config().get_if(i.ifname());

	deleted = (i.is_marked(IfTreeItem::DELETED) |
		   v.is_marked(IfTreeItem::DELETED));
	enabled = i.enabled() & v.enabled();

	// Get the flags from the pulled config
	if (ii != ifc().pulled_config().ifs().end())
	    pulled_flags = ii->second.if_flags();
	new_flags = pulled_flags;

	pulled_up = pulled_flags & IFF_UP;
	if (pulled_up && (deleted || !enabled))
	    new_flags &= ~IFF_UP;
	if ( (!pulled_up) && enabled)
	    new_flags |= IFF_UP;

	if (is_primary() && (new_flags == pulled_flags))
	    break;		// XXX: nothing changed

	// Set the vif flags
	if (ii != ifc().pulled_config().ifs().end()) {
	    IfTreeInterface::VifMap::const_iterator vi = ii->second.get_vif(v.vifname());
	    if (vi != ii->second.vifs().end()) {
		pulled_broadcast = vi->second.broadcast();
		pulled_loopback = vi->second.loopback();
		pulled_point_to_point = vi->second.point_to_point();
		pulled_multicast = vi->second.multicast();
	    }
	}
	if (is_primary()) {
	    new_broadcast = v.broadcast();
	    new_loopback = v.loopback();
	    new_point_to_point = v.point_to_point();
	    new_multicast = v.multicast();
	}
	if (is_secondary()) {
	    new_broadcast = pulled_broadcast;
	    new_loopback = pulled_loopback;
	    new_point_to_point = pulled_point_to_point;
	    new_multicast = pulled_multicast;
	}

	new_up = (new_flags & IFF_UP)? true : false;
	if (config_vif(i.ifname(), v.vifname(), if_index, new_flags, new_up,
		       deleted, new_broadcast, new_loopback,
		       new_point_to_point, new_multicast, error_msg)
	    < 0) {
	    error_msg = c_format("Failed to configure vif: %s",
				 error_msg.c_str());
	    ifc().er().vif_error(i.ifname(), v.vifname(), error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	    return;
	}
	break;
    } while (false);
}

void
IfConfigSet::push_vif_address(const IfTreeInterface&	i,
			      const IfTreeVif&		v,
			      const IfTreeAddr4&	a)
{
    string error_msg;
    uint32_t if_index = ifc().get_insert_ifindex(i.ifname());
    XLOG_ASSERT(if_index > 0);

    bool enabled = (i.enabled() & v.enabled() & a.enabled());

    debug_msg("Pushing %s\n", a.str().c_str());

    if (a.is_marked(IfTreeItem::CREATED) && enabled == false) {
	//
	// XXX:
	// A created but disabled address will not appear in the live
	// config that was read from the kernel.
	// 
	// This is a lot of work!
	//
	IfTree::IfMap::iterator ii = ifc().live_config().get_if(i.ifname());
	XLOG_ASSERT(ii != ifc().live_config().ifs().end());
	IfTreeInterface::VifMap::iterator vi = ii->second.get_vif(v.vifname());
	XLOG_ASSERT(vi != ii->second.vifs().end());
	vi->second.add_addr(a.addr());
	IfTreeVif::V4Map::iterator ai = vi->second.get_addr(a.addr());
	ai->second = a;
	return;
    }

    bool deleted = (i.is_marked(IfTreeItem::DELETED) |
		    v.is_marked(IfTreeItem::DELETED) |
		    a.is_marked(IfTreeItem::DELETED));

    if (deleted || !enabled) {
	if (delete_vif_address(i.ifname(), v.vifname(), if_index,
			       IPvX(a.addr()), a.prefix_len(), error_msg)
	    < 0) {
	    error_msg = c_format("Failed to delete address: %s",
				 error_msg.c_str());
	    ifc().er().vifaddr_error(i.ifname(), v.vifname(), a.addr(),
				     error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	}
	return;
    }

    //
    // Set the address
    //
    do {
	IPv4 oaddr(IPv4::ZERO());
	if (a.broadcast())
	    oaddr = a.bcast();
	else if (a.point_to_point())
	    oaddr = a.endpoint();
	
	uint32_t prefix_len = a.prefix_len();
	
	const IfTreeAddr4* ap = NULL;
	const IfTreeVif* vp = NULL;
	do {
	    IfTree::IfMap::const_iterator ii = ifc().pulled_config().get_if(i.ifname());
	    if (ii == ifc().pulled_config().ifs().end())
		break;
	    IfTreeInterface::VifMap::const_iterator vi = ii->second.get_vif(v.vifname());
	    if (vi == ii->second.vifs().end())
		break;
	    vp = &vi->second;
	    IfTreeVif::V4Map::const_iterator ai = vi->second.get_addr(a.addr());
	    if (ai == vi->second.v4addrs().end())
		break;
	    ap = &ai->second;
	    break;
	} while (false);

	// Test if a new address
	bool new_address = true;
	do {
	    if (is_secondary())
		break;
	    if (ap == NULL)
		break;
	    if (ap->addr() != a.addr())
		break;
	    if (a.broadcast() && (ap->bcast() != a.bcast()))
		break;
	    if (a.point_to_point() && (ap->endpoint() != a.endpoint()))
		break;
	    if (ap->prefix_len() != prefix_len)
		break;
	    new_address = false;
	    break;
	} while (false);
	if (! new_address)
	    break;		// Ignore: the address hasn't changed

	//
	// XXX: If the broadcast address was omitted, recompute it here.
	// Note that we recompute it only if the underlying vif is
	// broadcast-capable.
	//
	bool is_broadcast = a.broadcast();
	if ((vp != NULL)
	    && (! (a.broadcast() || a.point_to_point()))
	    && (prefix_len > 0)
	    && vp->broadcast()) {
	    IPv4 mask = IPv4::make_prefix(prefix_len);
	    oaddr = a.addr() | ~mask;
	    is_broadcast = true;
	}

	//
	// Try to add the address.
	// If the address exists already (e.g., with different prefix length),
	// then delete it first.
	//
	if (ap != NULL) {
	    delete_vif_address(i.ifname(), v.vifname(), if_index,
			       IPvX(a.addr()), ap->prefix_len(), error_msg);
	}
	if (add_vif_address(i.ifname(), v.vifname(), if_index, is_broadcast,
			    a.point_to_point(), IPvX(a.addr()), IPvX(oaddr),
			    prefix_len, error_msg)
	    < 0) {
	    error_msg = c_format("Failed to configure address: %s",
				 error_msg.c_str());
	    ifc().er().vifaddr_error(i.ifname(), v.vifname(), a.addr(),
				     error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	}
	
	break;
    } while (false);
}

#ifdef HAVE_IPV6
void
IfConfigSet::push_vif_address(const IfTreeInterface&	i,
			      const IfTreeVif&		v,
			      const IfTreeAddr6&	a)
{
    string error_msg;
    uint32_t if_index = ifc().get_insert_ifindex(i.ifname());
    XLOG_ASSERT(if_index > 0);

    bool enabled = (i.enabled() & v.enabled() & a.enabled());

    if (a.is_marked(IfTreeItem::CREATED) && enabled == false) {
	//
	// A created but disabled address will not appear in the live
	// config rippled up from the kernel via the routing socket
	// 
	// This is a lot of work!
	//
	IfTree::IfMap::iterator ii = ifc().live_config().get_if(i.ifname());
	XLOG_ASSERT(ii != ifc().live_config().ifs().end());
	IfTreeInterface::VifMap::iterator vi = ii->second.get_vif(v.vifname());
	XLOG_ASSERT(vi != ii->second.vifs().end());
	vi->second.add_addr(a.addr());
	IfTreeVif::V6Map::iterator ai = vi->second.get_addr(a.addr());
	ai->second = a;
	return;
    }

    bool deleted = (i.is_marked(IfTreeItem::DELETED) |
		    v.is_marked(IfTreeItem::DELETED) |
		    a.is_marked(IfTreeItem::DELETED));

    if (deleted || !enabled) {
	if (delete_vif_address(i.ifname(), v.vifname(), if_index, a.addr(),
			       a.prefix_len(), error_msg)
	    < 0) {
	    error_msg = c_format("Failed to delete address: %s",
				 error_msg.c_str());
	    ifc().er().vifaddr_error(i.ifname(), v.vifname(), a.addr(),
				     error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	}
	return;
    }

    //
    // Set the address
    //
    do {
	IPv6 oaddr(IPv6::ZERO());
	if (a.point_to_point())
	    oaddr = a.endpoint();
	
	// XXX: for whatever reason a prefix length of zero does not cut it, so
	// initialize prefix to 64.  This is exactly as ifconfig does.
	uint32_t prefix_len = a.prefix_len();
	if (0 == prefix_len)
	    prefix_len = 64;

	const IfTreeAddr6* ap = NULL;
	do {
	    IfTree::IfMap::const_iterator ii = ifc().pulled_config().get_if(i.ifname());
	    if (ii == ifc().pulled_config().ifs().end())
		break;
	    IfTreeInterface::VifMap::const_iterator vi = ii->second.get_vif(v.vifname());
	    if (vi == ii->second.vifs().end())
		break;
	    IfTreeVif::V6Map::const_iterator ai = vi->second.get_addr(a.addr());
	    if (ai == vi->second.v6addrs().end())
		break;
	    ap = &ai->second;
	    break;
	} while (false);

	// Test if a new address
	bool new_address = true;
	do {
	    if (is_secondary())
		break;
	    if (ap == NULL)
		break;
	    if (ap->addr() != a.addr())
		break;
	    if (a.point_to_point() && (ap->endpoint() != a.endpoint()))
		break;
	    if (ap->prefix_len() != prefix_len)
		break;
	    new_address = false;
	    break;
	} while (false);
	if (! new_address)
	    break;		// Ignore: the address hasn't changed

	//
	// Try to add the address.
	// If the address exists already (e.g., with different prefix length),
	// then delete it first.
	//
	if (ap != NULL) {
	    delete_vif_address(i.ifname(), v.vifname(), if_index,
			       IPvX(a.addr()), ap->prefix_len(), error_msg);
	}
	if (add_vif_address(i.ifname(), v.vifname(), if_index, false,
			    a.point_to_point(), IPvX(a.addr()), IPvX(oaddr),
			    prefix_len, error_msg)
	    < 0) {
	    error_msg = c_format("Failed to configure address: %s",
				 error_msg.c_str());
	    ifc().er().vifaddr_error(i.ifname(), v.vifname(), a.addr(),
				     error_msg);
	    XLOG_ERROR("%s", ifc().er().last_error().c_str());
	}
	
	break;
    } while (false);
}
#endif // HAVE_IPV6


syntax highlighted by Code2HTML, v. 0.9.1