You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2010/10/12 18:10:48 UTC

svn commit: r1021825 [2/4] - in /trafficserver/traffic/branches/wccp: ./ libinktomi++/ proxy/ proxy/mgmt2/ proxy/mgmt2/api2/include/ proxy/wccp/

Added: trafficserver/traffic/branches/wccp/proxy/wccp/Makefile.am
URL: http://svn.apache.org/viewvc/trafficserver/traffic/branches/wccp/proxy/wccp/Makefile.am?rev=1021825&view=auto
==============================================================================
--- trafficserver/traffic/branches/wccp/proxy/wccp/Makefile.am (added)
+++ trafficserver/traffic/branches/wccp/proxy/wccp/Makefile.am Tue Oct 12 16:10:47 2010
@@ -0,0 +1,48 @@
+#
+# Makefile.am for WCCP module.
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+AM_CPPFLAGS = \
+  -I$(top_srcdir)/proxy/mgmt2/api2/include \
+  -DPKGSYSUSER=\"$(pkgsysuser)\"
+
+#WCCP_DEFS = @WCCP_DEFS@
+#DEFS += $(WCCP_DEFS)
+
+noinst_LIBRARIES = libwccp.a
+noinst_PROGRAMS = test-cache
+
+libwccp_a_SOURCES = \
+  Wccp.h \
+  WccpLocal.h \
+  WccpStatic.cc \
+  WccpMsg.cc \
+  WccpEndPoint.cc \
+  WccpConfig.cc \
+  Errata.h \
+  Errata.cc \
+  AtsBase.h \
+  AtsBaseStatic.cc \
+  AtsMeta.h \
+  NumericType.h \
+  IntrusivePtr.h
+
+test_cache_SOURCES = \
+  wccp-test-cache.cc
+
+test_cache_LDADD = ./libwccp.a -lconfig++ -lcrypto
\ No newline at end of file

Added: trafficserver/traffic/branches/wccp/proxy/wccp/Wccp.h
URL: http://svn.apache.org/viewvc/trafficserver/traffic/branches/wccp/proxy/wccp/Wccp.h?rev=1021825&view=auto
==============================================================================
--- trafficserver/traffic/branches/wccp/proxy/wccp/Wccp.h (added)
+++ trafficserver/traffic/branches/wccp/proxy/wccp/Wccp.h Tue Oct 12 16:10:47 2010
@@ -0,0 +1,482 @@
+# if ! defined(ATS_WCCP_API_H)
+# define ATS_WCCP_API_H
+
+/** @file
+    WCCP (v2) support API.
+    Original implementation: Alan M. Carroll
+    Sponsored by: Pavlov Media
+ */
+
+# include "AtsBase.h"
+# include "IntrusivePtr.h"
+# include "Errata.h"
+
+# include <memory.h>
+
+// INADDR_ANY
+# include <netinet/in.h>
+
+/// WCCP Support.
+namespace Wccp {
+using namespace ats::fixed_integers;
+
+/// Forward declare implementation classes.
+class Impl;
+class CacheImpl;
+class RouterImpl;
+
+/// Namespace for implementation details.
+namespace detail {
+  /// Cache implementation details.
+  namespace cache {
+    class GroupData;
+  }
+  namespace router {
+    class GroupData;
+  }
+}
+
+/// Basic time unit for WCCP in seconds
+/// @note Sec 4.14: HERE_I_AM_T.
+static time_t const TIME_UNIT = 10;
+static time_t const ASSIGN_WAIT = (3 * TIME_UNIT) / 2;
+static time_t const RAPID_TIME = TIME_UNIT/10;
+
+/// Service group related constants.
+/// @internal In a struct so enum values can be imported to more than
+/// one class.
+struct ServiceConstants {
+  /// Methods for forwarding intercepted packets to cache.
+  /// @internal Enumerations values match protocol values.
+  enum PacketStyle {
+    NO_PACKET_STYLE = 0, ///< Undefined or invalid.
+    GRE = 1, ///< GRE tunnel only. [default]
+    L2 = 2, ///< L2 rewrite only.
+    GRE_OR_L2 = 3 ///< L2 rewrite or GRE tunnel.
+  };
+
+  /// Cache assignment supported methods.
+  /// @internal Enumerations values match protocol values.
+  enum CacheAssignmentStyle {
+    NO_CACHE_ASSIGN_STYLE = 0, ///< Undefined or invalid.
+    HASH_ONLY = 1, ///< Use only hash assignment. [default]
+    MASK_ONLY = 2, ///< Use only mask assignment.
+    HASH_OR_MASK = 3 ///< Use hash or mask assignment.
+  };
+};
+
+/** Service group definition.
+
+    Also used as serialized layout internally by ServiceGroupElt. This is kept
+    in serialized form because it is copied to and from message data far more
+    often then it is accessed directly.
+ */
+class ServiceGroup : public ServiceConstants {
+public:
+  typedef ServiceGroup self; ///< Self reference type.
+
+  /// Type of service.
+  enum Type {
+    STANDARD = 0, ///< Well known service.
+    DYNAMIC = 1 ///< Dynamic (locally defined) service.
+  };
+
+  /// Result codes for service definition.
+  enum Result {
+    DEFINED = 0, ///< Service group was created by the call.
+    EXISTS = 1, ///< Service group already existed.
+    CONFLICT = 2 ///< Service group existed but didn't match new definition.
+  };
+
+  /// @name Well known (standard) services.
+  //@{
+  /// HTTP
+  static uint8 const HTTP = 0;
+  //@}
+  /// Service IDs of this value or less are reserved.
+  static uint8 const RESERVED = 50;
+  
+  /// Number of ports in component (defined by protocol).
+  static int const N_PORTS = 8;
+
+  /// @name Flag mask values.
+  //@{
+  /// Source IP address hash
+  static uint32 const SRC_IP_HASH = 1<<0;
+  /// Destination IP address hash
+  static uint32 const DST_IP_HASH = 1<<1;
+  /// Source port hash.
+  static uint32 const SRC_PORT_HASH = 1<<2;
+  /// Destination port hash
+  static uint32 const DST_PORT_HASH = 1<<3;
+  /// @a m_ports has port information.
+  static uint32 const PORTS_DEFINED = 1<<4;
+  /// @a m_ports has source ports (otherwise destination ports).
+  static uint32 const PORTS_SOURCE = 1<<5;
+  /// Alternate source IP address hash
+  static uint32 const SRC_IP_ALT_HASH = 1<<8;
+  /// Alternate destination IP address hash
+  static uint32 const DST_IP_ALT_HASH = 1<<9;
+  /// Alternate source port hash
+  static uint32 const SRC_PORT_ALT_HASH = 1<<10;
+  /// Alternate destination port hash
+  static uint32 const DST_PORT_ALT_HASH = 1<<11;
+  //@}
+
+  /// Default constructor - no member initialization.
+  ServiceGroup();
+  /// Test for equivalent.
+  bool operator == (self const& that) const;
+  /// Test for not equivalent.
+  bool operator != (self const& that) const;
+
+  /// @name Accessors
+  //@{
+  ServiceGroup::Type getSvcType() const; ///< Get service type field.
+  /** Set the service type.
+      If @a svc is @c SERVICE_STANDARD then all fields except the
+      component header and service id are set to zero as required
+      by the protocol.
+  */
+  self& setSvcType(ServiceGroup::Type svc);
+
+  uint8 getSvcId() const; ///< Get service ID field.
+  self& setSvcId(uint8 id); ///< Set service ID field to @a id.
+
+  uint8 getPriority() const; ///< Get priority field.
+  self& setPriority(uint8 pri); ///< Set priority field to @a p.
+
+  uint8 getProtocol() const; ///< Get protocol field.
+  self& setProtocol(uint8 p); ///< Set protocol field to @a p.
+
+  uint32 getFlags() const; ///< Get flags field.
+  self& setFlags(uint32 f); ///< Set the flags flags in field to @a f.
+  /// Set the flags in the flag field that are set in @a f.
+  /// Other flags are unchanged.
+  self& enableFlags(uint32 f);
+  /// Clear the flags in the flag field that are set in @a f.
+  /// Other flags are unchanged.
+  self& disableFlags(uint32 f);
+
+  /// Get a port value.
+  uint16 getPort(
+    int idx ///< Index of target port.
+  ) const;
+  /// Set a port value.
+  self& setPort(
+    int idx, ///< Index of port.
+    uint16 port ///< Value for port.
+  );
+  /// Zero (clear) all ports.
+  self& clearPorts();
+  //@}
+
+protected:
+  uint8 m_svc_type; ///< @ref Type.
+  uint8 m_svc_id; ///< ID for service type.
+  uint8 m_priority; ///< Redirection priority ordering.
+  uint8 m_protocol; ///< IP protocol for service.
+  uint32 m_flags; ///< Flags.
+  uint16 m_ports[N_PORTS]; ///< Service ports.
+};
+
+/// Security component option (sub-type)
+enum SecurityOption {
+  SECURITY_NONE = 0, ///< No security @c WCCP2_NO_SECURITY
+  SECURITY_MD5 = 1 ///< MD5 security @c WCCP2_MD5_SECURITY
+};
+
+class EndPoint {
+public:
+  typedef EndPoint self; ///< Self reference type.
+  typedef Impl ImplType; ///< Implementation type.
+
+  /** Set the identifying IP address.
+      This is also used as the address for the socket.
+  */
+  self& setAddr(
+    uint32 addr ///< IP address.
+  );
+
+  /** Check if this endpoint is ready to use.
+      @return @c true if the address has been set and services
+      have been added.
+  */
+  bool isConfigured() const;
+
+  /** Open a socket for communications.
+      
+      If @a addr is @c INADDR_ANY then the identifying address is used.
+      If that is not set this method will attempt to find an arbitrary
+      local address and use that as the identifying address.
+
+      Otherwise @a addr replaces any previously set address.
+
+      @return 0 on success, -ERRNO on failure.
+      @see setAddr
+  */
+  int open(
+    uint32 addr = INADDR_ANY ///< Local IP address for socket.
+  );
+
+  /// Get the internal socket.
+  /// Useful primarily for socket options and using
+  /// in @c select.
+  int getSocket() const;
+
+  /// Use MD5 based security, specifying the @a key.
+  void useMD5Security(
+    char const* key ///< Shared hash key.
+  );
+
+  /// Perform house keeping, including sending outbound messages.
+  int housekeeping();
+
+  /// Recieve and process a message on the socket.
+  /// @return 0 for success, -ERRNO on system error.
+  ats::Rv<int> handleMessage();
+
+protected:
+  /// Default constructor.
+  EndPoint();
+  /// Copy constructor.
+  EndPoint(self const& that);
+  /// Force virtual destructor
+  virtual ~EndPoint();
+
+  ats::IntrusivePtr<ImplType> m_ptr; ///< Implementation instance.
+
+  /** Get a pointer to the implementation instance, creating it if needed.
+      @internal This is paired with @c make so that the implementation check
+      can be done non-virtually inline, while still allowing the actual
+      implementation instantiation to be virtual so the correct type is
+      created.
+   */
+  ImplType* instance();
+
+  virtual ImplType* make() = 0; ///< Create a new implementation instance.
+};
+
+class Cache : public EndPoint {
+public:
+  typedef Cache self; ///< Self reference type.
+  typedef EndPoint super; ///< Parent type.
+  typedef CacheImpl ImplType; ///< Implementation type.
+
+  class Service;
+
+  /// Default constructor.
+  Cache();
+  /// Destructor
+  ~Cache();
+
+  /// Define services from a configuration file.
+  ats::Errata loadServicesFromFile(
+    char const* path ///< Path to file.
+  );
+
+  /** Define a service group.
+
+      Return a service reference object which references the group.
+
+      If @a result is not @c NULL then its target is set to
+      - @c ServiceGroup::DEFINED if the service was created.
+      - @c ServiceGroup::EXISTS if the service matches the existing service.
+      - @c ServiceGroup::CONFLICT if the service doesn't match the existing service.
+   */
+  Service defineServiceGroup(
+    ServiceGroup const& svc, ///< Service group description.
+    ServiceGroup::Result* result = 0
+  );
+
+  /** Add a seed router to the service group.
+
+      A seed router is one that is defined at start up and is where
+      initial messages will be sent. Other routers will be added as
+      discovered. The protocol cannot start successfully without at
+      least one seed router.
+
+      Seed routers are removed when a reply is received from that router.
+
+  */
+  self& addSeedRouter(
+    uint8 id, ///< Service group ID.
+    uint32 addr ///< IP address of router.
+  );
+
+  /// Number of seconds until next housekeeping activity is due.
+  time_t waitTime() const;
+protected:
+  /// Get implementation instance, creating if needed.
+  ImplType* instance();
+  /// Get the current implementation instance cast to correct type.
+  ImplType* impl();
+  /// Get the current implementation instance cast to correct type.
+  ImplType const* impl() const;
+  /// Create a new implementation instance.
+  super::ImplType* make();
+};
+
+/** Hold a reference to a service group in this end point.
+    This is useful when multiple operations are to be done on the
+    same group, rather than doing a lookup by id every time.
+*/
+class Cache::Service : public ServiceConstants {
+public:
+  typedef Service self; ///< Self reference type.
+
+  /// Default constructor (invalid reference).
+  Service();
+
+  /// Add an address for a seed router.
+  self& addSeedRouter(
+    uint32 addr ///< Router IP address.
+  );
+  /// Set the security key.
+  self& setKey(
+    char const* key /// Shared key.
+  );
+  /// Set the service local security option.
+  self& setSecurity(
+    SecurityOption opt ///< Security style to use.
+  );
+  /// Set intercepted packet forwarding style.
+  self& setForwarding(
+    PacketStyle style ///< Type of forwarding supported.
+  );
+  /// Enable or disable packet return by layer 2 rewrite.
+  self& setReturn(
+    PacketStyle style ///< Type of return supported.
+  );
+
+  /// Set cache assignment style.
+  self& setCacheAssignment(
+    CacheAssignmentStyle style ///< Style to use.
+  );
+  
+  
+private:
+  Service(Cache const& cache, detail::cache::GroupData& group);
+  Cache m_cache; ///< Parent cache.
+  detail::cache::GroupData* m_group; ///< Service Group data.
+  friend class Cache;
+};
+
+
+class Router : public EndPoint {
+public:
+  typedef Router self; ///< Self reference type.
+  typedef EndPoint super; ///< Parent type.
+  typedef RouterImpl ImplType; ///< Implementation type.
+
+  /// Default constructor
+  Router();
+  /// Destructor.
+  ~Router();
+
+  /// Transmit pending messages.
+  int sendPendingMessages();
+protected:
+  /// Get implementation instance, creating if needed.
+  ImplType* instance();
+  /// Get the current implementation instance cast to correct type.
+  ImplType* impl();
+  /// Create a new implementation instance.
+  super::ImplType* make();
+};
+
+// ------------------------------------------------------
+inline bool
+ServiceGroup::operator != (self const& that) const {
+  return !(*this == that);
+}
+
+inline ServiceGroup::Type
+ServiceGroup::getSvcType() const {
+  return static_cast<ServiceGroup::Type>(m_svc_type);
+}
+
+inline uint8_t
+ServiceGroup::getSvcId() const {
+  return m_svc_id;
+}
+
+inline ServiceGroup&
+ServiceGroup::setSvcId(uint8_t id) {
+  m_svc_id = id;
+  return *this;
+}
+
+inline uint8_t
+ServiceGroup::getPriority() const {
+  return m_priority;
+}
+
+inline ServiceGroup&
+ServiceGroup::setPriority(uint8_t pri) {
+  m_priority = pri;
+  return *this;
+}
+
+inline uint8_t
+ServiceGroup::getProtocol() const {
+  return m_protocol;
+}
+
+inline ServiceGroup&
+ServiceGroup::setProtocol(uint8_t proto) {
+  m_protocol = proto;
+  return *this;
+}
+
+inline uint32
+ServiceGroup::getFlags() const {
+  return ntohl(m_flags);
+}
+
+inline ServiceGroup&
+ServiceGroup::setFlags(uint32 flags) {
+  m_flags = htonl(flags);
+  return *this;
+}
+
+inline ServiceGroup&
+ServiceGroup::enableFlags(uint32 flags) {
+  m_flags |= htonl(flags);
+  return *this;
+}
+
+inline ServiceGroup&
+ServiceGroup::disableFlags(uint32 flags) {
+  m_flags &= ~htonl(flags);
+  return *this;
+}
+
+inline uint16_t
+ServiceGroup::getPort (int idx) const {
+  return ntohs(m_ports[idx]);
+}
+
+inline ServiceGroup&
+ServiceGroup::setPort(int idx, uint16_t port) {
+  m_ports[idx] = htons(port);
+  return *this;
+}
+
+inline ServiceGroup&
+ServiceGroup::clearPorts() {
+  memset(m_ports, 0, sizeof(m_ports));
+  return *this;
+}
+
+inline Cache::Service::Service() : m_group(0) { }
+
+inline Cache::Service::Service(
+  Cache const& cache,
+  detail::cache::GroupData& group
+) : m_cache(cache), m_group(&group) {
+}
+// ------------------------------------------------------
+
+} // namespace Wccp
+# endif // Include guard

Added: trafficserver/traffic/branches/wccp/proxy/wccp/WccpConfig.cc
URL: http://svn.apache.org/viewvc/trafficserver/traffic/branches/wccp/proxy/wccp/WccpConfig.cc?rev=1021825&view=auto
==============================================================================
--- trafficserver/traffic/branches/wccp/proxy/wccp/WccpConfig.cc (added)
+++ trafficserver/traffic/branches/wccp/proxy/wccp/WccpConfig.cc Tue Oct 12 16:10:47 2010
@@ -0,0 +1,777 @@
+# include "WccpLocal.h"
+# include <sstream>
+# include <libconfig.h++>
+# include <arpa/inet.h>
+# include <iostream>
+# include <errno.h>
+
+// Support that must go in the standard namespace.
+namespace std {
+ostream& operator << ( ostream& s, libconfig::Setting::Type const& t ) {
+  switch (t) {
+  case libconfig::Setting::TypeString: s << "string"; break;
+  case libconfig::Setting::TypeInt: s << "integer"; break;
+  case libconfig::Setting::TypeInt64: s << "integer (64 bit)"; break;
+  case libconfig::Setting::TypeFloat: s << "floating point"; break;
+  case libconfig::Setting::TypeBoolean: s << "boolean"; break;
+  case libconfig::Setting::TypeArray: s << "array"; break;
+  case libconfig::Setting::TypeList: s << "list"; break;
+  case libconfig::Setting::TypeGroup: s << "group"; break;
+  default: s << "*unknown*"; break;
+  };
+  return s;
+}
+} // namespace std
+
+// WCCP related things that are file local.
+namespace {
+using namespace Wccp;
+
+// Scratch global list of seed router addresses.
+// Yeah, not thread safe, but it's just during configuration load.
+std::vector<ats::uint32> Seed_Router;
+
+// Names used for various elements and properties.
+static char const * const SVC_NAME = "service";
+
+static char const * const SVC_PROP_ID = "id";
+static char const * const SVC_PROP_TYPE = "type";
+static char const * const SVC_PROP_PRIORITY = "priority";
+static char const * const SVC_PROP_PROTOCOL = "protocol";
+static char const * const SVC_PROP_FLAGS = "flags";
+static char const * const SVC_PROP_PRIMARY_HASH = "primary-hash";
+static char const * const SVC_PROP_ALT_HASH = "alt-hash";
+static char const * const SVC_PROP_PORTS = "ports";
+static char const * const SVC_PROP_PORT_TYPE = "port-type";
+static char const * const SVC_PROP_SECURITY = "security";
+static char const * const SVC_PROP_ROUTERS = "routers";
+static char const * const SVC_PROP_FORWARD = "forward";
+static char const * const SVC_PROP_RETURN = "return";
+static char const * const SVC_PROP_ASSIGN = "assignment";
+
+static char const * const SECURITY_PROP_OPTION = "option";
+static char const * const SECURITY_PROP_KEY = "key";
+
+/// Helper structure for processing configuration strings.
+struct CfgString {
+  char const* m_text; ///< Text value of the option.
+  bool m_found; ///< String was found.
+};
+typedef std::vector<CfgString> CfgOpts;
+
+# define N_OPTS(x) (sizeof(x) / sizeof(*x))
+
+CfgString FORWARD_OPTS[] = { { "gre" } , { "l2" } };
+size_t const N_FORWARD_OPTS = sizeof(FORWARD_OPTS)/sizeof(*FORWARD_OPTS);
+
+CfgString RETURN_OPTS[] = { { "gre" } , { "l2" } };
+size_t const N_RETURN_OPTS = sizeof(RETURN_OPTS)/sizeof(*RETURN_OPTS);
+
+CfgString ASSIGN_OPTS[] = { { "hash" } , { "mask" } };
+
+CfgString HASH_OPTS[] = { { "src_ip" } , { "dst_ip" } , { "src_port" } , { "dst_port" } };
+
+ats::Errata::Code code_max(ats::Errata const& err) {
+  ats::Errata::Code zret = std::numeric_limits<ats::Errata::Code::raw_type>::min();
+  ats::Errata::const_iterator spot = err.begin();
+  ats::Errata::const_iterator limit = err.end();
+  for ( ; spot != limit ; ++spot )
+    zret = std::max(zret, spot->getCode());
+  return zret;
+}
+
+ats::Errata::Message File_Syntax_Error(int line, char const* text) {
+  std::ostringstream out;
+  out << "Service configuration error. Line "
+      << line
+      << ": " << text
+    ;
+  return ats::Errata::Message(1, LVL_FATAL, out.str());
+}
+
+ats::Errata::Message File_Read_Error(char const* text) {
+  std::ostringstream out;
+  out << "Failed to parse configuration file."
+      << ": " << text
+    ;
+  return ats::Errata::Message(2, LVL_FATAL, out.str());
+}
+
+ats::Errata::Message Unable_To_Create_Service_Group(int line) {
+  std::ostringstream out;
+  out << "Unable to create service group at line " << line
+      << " because of configuration errors."
+    ;
+  return ats::Errata::Message(23, LVL_FATAL, out.str());
+}
+
+ats::Errata::Message Services_Not_Found() {
+  return ats::Errata::Message(3, LVL_INFO, "No services found in configuration.");
+}
+
+ats::Errata::Message Services_Not_A_Sequence() {
+  return ats::Errata::Message(4, LVL_INFO, "The 'services' setting was not a list nor array.");
+}
+
+ats::Errata::Message Service_Not_A_Group(int line) {
+  std::ostringstream out;
+  out << "'" << SVC_NAME << "' must be a group at line "
+      << line << "."
+    ;
+  return ats::Errata::Message(5, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Service_Type_Defaulted(Wccp::ServiceGroup::Type type, int line) {
+  std::ostringstream out;
+  out << "'type' not found in " << SVC_NAME << " at line "
+      << line << "' -- defaulting to "
+      << ( type == Wccp::ServiceGroup::STANDARD ? "STANDARD" : "DYNAMIC" )
+    ;
+  return ats::Errata::Message(6, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Service_Type_Invalid(char const* text, int line) {
+  std::ostringstream out;
+  out << "Service type '" << text 
+      << "' at line " << line
+      << " invalid. Must be \"STANDARD\" or \"DYNAMIC\""
+    ;
+  return ats::Errata::Message(7, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Prop_Not_Found(char const* prop_name, char const* group_name, int line) {
+  std::ostringstream out;
+  out << "Required '" << prop_name << "' property not found in '"
+      << group_name << "' at line " << line << "."
+    ;
+  return ats::Errata::Message(8, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Prop_Invalid_Type(
+  libconfig::Setting& prop_cfg,
+  libconfig::Setting::Type expected
+) {
+  std::ostringstream out;
+  out << "'" << prop_cfg.getName() << "' at line " << prop_cfg.getSourceLine()
+      << " is of type '" << prop_cfg.getType()
+      << "' instead of required type '" << expected << "'."
+    ;
+  return ats::Errata::Message(9, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Prop_List_Invalid_Type(
+  libconfig::Setting& elt_cfg, ///< List element.
+  libconfig::Setting::Type expected
+) {
+  std::ostringstream out;
+  out << "Element ";
+  if (elt_cfg.getName()) out << "'" << elt_cfg.getName() << "'";
+  else out << elt_cfg.getIndex();
+  out << " at line " << elt_cfg.getSourceLine()
+      << " in the aggregate property '" << elt_cfg.getParent().getName()
+      << "' is of type '" << elt_cfg.getType()
+      << "' instead of required type '" << expected << "'."
+    ;
+  return ats::Errata::Message(9, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Svc_Prop_Out_Of_Range(
+  char const* name,
+  libconfig::Setting& elt_cfg,
+  int v, int min, int max
+) {
+  std::ostringstream out;
+  out << "Service property '" << name
+      << "' at line " << elt_cfg.getSourceLine()
+      << " has a value " << v
+      << " that is not in the allowed range of "
+      << min << ".." << max << "."
+    ;
+  return ats::Errata::Message(10, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Svc_Prop_Ignored(char const* name, int line) {
+  std::ostringstream out;
+  out << "Service property '" << name << "' at line " << line
+      << " ignored because the service is of type standard."
+    ;
+  return ats::Errata::Message(11, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Svc_Flags_No_Hash_Set(int line) {
+  std::ostringstream out;
+  out << "Service flags have no hash set at line " << line
+    ;
+  return ats::Errata::Message(12, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Svc_Flags_Ignored(int line) {
+  std::ostringstream out;
+  out << "Invalid service flags at line  " << line
+      << " ignored."
+    ;
+  return ats::Errata::Message(13, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Svc_Ports_Too_Many(int line, int n) {
+  std::ostringstream out;
+  out << "Excess ports ignored at line " << line
+      << ". " << n << " ports specified, only" 
+      << Wccp::ServiceGroup::N_PORTS << " supported."
+    ;
+  return ats::Errata::Message(14, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Svc_Ports_Malformed(int line) {
+  std::ostringstream out;
+  out << "Port value ignored (not a number) at line " << line
+      << "."
+    ;
+  return ats::Errata::Message(15, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Svc_Ports_None_Valid(int line) {
+  std::ostringstream out;
+  out << "A '" << SVC_PROP_PORTS << "' property was found at line "
+      << line << " but none of the ports were valid."
+    ;
+  return  ats::Errata::Message(17, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Svc_Ports_Not_Found(int line) {
+  std::ostringstream out;
+  out << "Ports not found in service at line " << line
+      << ". Ports must be defined for a dynamic service.";
+  return  ats::Errata::Message(18, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Svc_Prop_Ignored_In_Standard(const char* name, int line) {
+  std::ostringstream out;
+  out << "Service property '" << name << "' at line " << line
+      << " ignored because the service is of type STANDARD."
+    ;
+  return ats::Errata::Message(19, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Security_Opt_Invalid(char const* text, int line) {
+  std::ostringstream out;
+  out << "Security option '" << text
+      << "' at line " << line
+      << " is invalid. It must be 'none' or 'md5'."
+    ;
+  return ats::Errata::Message(20, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Value_Malformed(char const* name, char const* text, int line) {
+  std::ostringstream out;
+  out << "'" << name << "' value '" << text
+      << "' malformed at line " << line << "."
+    ;
+  return ats::Errata::Message(21, LVL_WARN, out.str());
+}
+
+ats::Errata::Message No_Valid_Routers(int line) {
+  std::ostringstream out;
+  out << "No valid IP address for routers found for Service Group at line "
+      << line << "."
+    ;
+  return ats::Errata::Message(22, LVL_WARN, out.str());
+}
+
+ats::Errata::Message Ignored_Option_Value(
+  char const* text,
+  char const* name,
+  int line
+) {
+  std::ostringstream out;
+  out << "Value '" << text << "' at line " << line
+      << " was ignored because it is not a valid option for '"
+      << name << "'."
+    ;
+  return ats::Errata::Message(24, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Ignored_Opt_Errors(
+  char const* name,
+  int line
+) {
+  std::ostringstream out;
+  out << "Errors in  '" << name << "' at line " << line
+      << " were ignored."
+    ;
+  return ats::Errata::Message(28, LVL_INFO, out.str());
+}
+
+ats::Errata::Message List_Valid_Opts(
+  char const* name,
+  int line,
+  CfgString* values,
+  size_t n
+) {
+  std::ostringstream out;
+  out << "Valid values for the '" << name << "' property at line " << line
+      << " are: "
+    ;
+  out << '"' << values[0].m_text << '"';
+  for ( size_t i = 1 ; i < n ; ++i )
+    out << ", \"" << values[i].m_text << '"';
+  out << '.';
+  return ats::Errata::Message(29, LVL_INFO, out.str());
+}
+
+ats::Errata::Message Port_Type_Invalid(char const* text, int line) {
+  std::ostringstream out;
+  out << "Value '" << text
+      << "' at line " << line
+      << "for property '" << SVC_PROP_PORT_TYPE
+      << "' is invalid. It must be 'src' or 'dst'."
+    ;
+  return ats::Errata::Message(30, LVL_WARN, out.str());
+}
+
+} // anon namespace
+
+namespace Wccp {
+
+ats::Errata
+load_option_set(libconfig::Setting& setting, CfgString* opts, size_t count) {
+  ats::Errata zret;
+  CfgString* spot;
+  CfgString* limit = opts + count;
+  char const* name = setting.getName();
+  int src_line = setting.getSourceLine();
+
+  // Clear all found flags.
+  for ( spot = opts ; spot < limit ; ++spot ) spot->m_found = false;
+
+  // Walk through the strings in the setting.
+  if (setting.isAggregate()) {
+    int nr = setting.getLength();
+    bool list_opts = false;
+    for ( int i = 0 ; i < nr ; ++i ) {
+      libconfig::Setting& item = setting[i];
+      if (libconfig::Setting::TypeString == item.getType()) {
+        char const* text = static_cast<char const*>(item);
+        for ( spot = opts ; spot < limit ; ++spot ) {
+          if (0 == strcasecmp(text, spot->m_text)) {
+            spot->m_found = true;
+            break;
+          }
+        }
+        if (spot >= limit) {
+          zret.push(Ignored_Option_Value(text, name, item.getSourceLine()));
+          list_opts = true;
+        }
+      } else {
+        zret.push(Prop_Invalid_Type(setting, libconfig::Setting::TypeString));
+      }
+    }
+    if (list_opts)
+      zret.push(List_Valid_Opts(name, src_line, opts, count));
+  } else {
+    zret.push(Prop_Invalid_Type(setting, libconfig::Setting::TypeArray));
+  }
+  return zret;
+}
+
+/** On success this returns a non @c NULL pointer if the MD5 option is
+    set.  In that case the pointer points at the MD5 key.  Otherwise
+    the option was none and the pointer is @c NULL
+ */
+ats::Rv<char const*>
+load_security (
+  libconfig::Setting& setting ///< Security setting.
+) {
+  ats::Rv<char const*> zret;
+  int src_line;
+  char const* text;
+
+  zret = 0;
+
+  src_line = setting.getSourceLine();
+  if (libconfig::Setting::TypeGroup == setting.getType()) {
+    if (setting.exists(SECURITY_PROP_OPTION)) {
+      libconfig::Setting& opt = setting[SECURITY_PROP_OPTION];
+      if (libconfig::Setting::TypeString == opt.getType()) {
+        text = static_cast<char const*>(opt);
+        if (0 == strcasecmp("none", text)) {
+        } else if (0 == strcasecmp("md5", text)) {
+          if (setting.exists(SECURITY_PROP_KEY)) {
+            libconfig::Setting& key = setting[SECURITY_PROP_KEY];
+            if (libconfig::Setting::TypeString == key.getType()) {
+              zret = static_cast<char const*>(key);
+            } else {
+              zret.push(Prop_Invalid_Type(key, libconfig::Setting::TypeString));
+            }
+          } else {
+            zret.push(Prop_Not_Found(SECURITY_PROP_KEY, SVC_PROP_SECURITY, src_line));
+          }
+        } else {
+          zret.push(Security_Opt_Invalid(text, opt.getSourceLine()));
+        }
+      } else {
+        zret.push(Prop_Invalid_Type(opt, libconfig::Setting::TypeString));
+      }
+    } else {
+      zret.push(Prop_Not_Found(SECURITY_PROP_OPTION, SVC_PROP_SECURITY, src_line));
+    }
+  } else {
+    zret.push(Prop_Invalid_Type(setting, libconfig::Setting::TypeGroup));
+  }
+  return zret;
+}
+
+/// Process a router address list.
+ats::Errata
+load_routers (
+  libconfig::Setting& setting, ///< Source of addresses.
+  std::vector<uint32>& addrs ///< Output list
+) {
+  ats::Errata zret;
+  int src_line;
+  char const* text;
+  static char const * const NAME = "IPv4 Address";
+
+  src_line = setting.getSourceLine();
+  if (setting.isAggregate()) {
+    int nr = setting.getLength();
+    for ( int i = 0 ; i < nr ; ++i ) {
+      libconfig::Setting& addr_cfg = setting[i];
+      int addr_line = addr_cfg.getSourceLine();
+      in_addr addr;
+      if (libconfig::Setting::TypeString == addr_cfg.getType()) {
+        text = static_cast<char const*>(addr_cfg);
+        if (inet_aton(text, &addr)) addrs.push_back(addr.s_addr);
+        else zret.push(Value_Malformed(NAME, text, addr_line));
+      } else {
+        zret.push(Prop_List_Invalid_Type(addr_cfg, libconfig::Setting::TypeString));
+      }
+    }
+  } else {
+    zret.push(Prop_Invalid_Type(setting, libconfig::Setting::TypeArray));
+  }
+  return zret;
+}
+
+ats::Errata
+CacheImpl::loadServicesFromFile(char const* path) {
+  ats::Errata zret;
+  libconfig::Config cfg;
+  int src_line = 0; // scratch for local source line caching.
+  std::vector<uint32> routers; // scratch per service loop.
+
+  // Can we read and parse the file?
+  try {
+    cfg.readFile(path);
+  } catch (libconfig::ParseException & x) {
+    return File_Syntax_Error(x.getLine(), x.getError());
+  } catch (std::exception const& x) {
+    return File_Read_Error(x.what());
+  }
+
+  // No point in going on from here.
+  if (!cfg.exists("services")) return Services_Not_Found();
+
+  libconfig::Setting& svc_list = cfg.lookup("services");
+  if (!svc_list.isAggregate()) return Services_Not_A_Sequence();
+
+  // Check for global (default) security setting.
+  if (cfg.exists(SVC_PROP_SECURITY)) {
+    libconfig::Setting& security = cfg.lookup(SVC_PROP_SECURITY);
+    ats::Rv<char const*> rv = load_security(security);
+    if (rv.isOK()) this->useMD5Security(rv);
+    else zret.join(rv.errata());
+  }
+
+  if (cfg.exists(SVC_PROP_ROUTERS)) {
+    libconfig::Setting& routers = cfg.lookup(SVC_PROP_ROUTERS);
+    zret.join(load_routers(routers, Seed_Router));
+  }
+
+  int idx, nsvc;
+  for ( idx = 0, nsvc = svc_list.getLength() ; idx < nsvc ; ++idx ) {
+    int x; // scratch int.
+    char const* text; // scratch text.
+    char const* md5_key = 0;
+    SecurityOption security_style = SECURITY_NONE;
+    bool use_group_local_security = false;
+    libconfig::Setting& svc_cfg = svc_list[idx];
+    int svc_line = svc_cfg.getSourceLine();
+    ServiceGroup svc_info;
+
+    if (!svc_cfg.isGroup()) {
+      zret.push(Service_Not_A_Group(svc_line));
+      continue;
+    }
+
+    // Get the service ID.
+    if (svc_cfg.exists(SVC_PROP_ID)) {
+      libconfig::Setting& id_prop = svc_cfg[SVC_PROP_ID];
+      if (id_prop.isNumber()) {
+        x = static_cast<int>(id_prop);
+        if (0 <= x && x <= 255)
+          svc_info.setSvcId(x);
+        else
+          zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_ID, id_prop, x, 0, 255));
+      } else {
+        zret.push(Prop_Invalid_Type(id_prop, libconfig::Setting::TypeInt));
+      }
+    } else {
+      zret.push(Prop_Not_Found(SVC_PROP_ID, SVC_NAME, svc_line));
+    }
+
+    // Service type.
+    if (svc_cfg.exists(SVC_PROP_TYPE)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_TYPE];
+      if (libconfig::Setting::TypeString == prop.getType()) {
+        text = static_cast<char const*>(prop);
+        if (0 == strcasecmp("DYNAMIC", text))
+          svc_info.setSvcType(ServiceGroup::DYNAMIC);
+        else if (0 == strcasecmp("STANDARD", text))
+          svc_info.setSvcType(ServiceGroup::STANDARD);
+        else
+          zret.push(Service_Type_Invalid(text, prop.getSourceLine()));
+      } else {
+        zret.push(Prop_Invalid_Type(prop, libconfig::Setting::TypeString));
+      }
+    } else { // default type based on ID.
+      ServiceGroup::Type svc_type =
+        svc_info.getSvcId() <= ServiceGroup::RESERVED
+          ? ServiceGroup::STANDARD
+          : ServiceGroup::DYNAMIC
+        ;
+      svc_info.setSvcType(svc_type);
+      zret.push(Service_Type_Defaulted(svc_type, svc_line));
+    }
+
+    // Get the protocol.
+    if (svc_cfg.exists(SVC_PROP_PROTOCOL)) {
+      libconfig::Setting& proto_prop = svc_cfg[SVC_PROP_PROTOCOL];
+      if (svc_info.getSvcType() == ServiceGroup::STANDARD) {
+        zret.push(Svc_Prop_Ignored(SVC_PROP_PROTOCOL, proto_prop.getSourceLine()));
+      } else if (proto_prop.isNumber()) {
+        x = static_cast<int>(proto_prop);
+        if (0 <= x && x <= 255)
+          svc_info.setProtocol(x);
+        else
+          zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_ID, proto_prop, x, 0, 255));
+      } else {
+        zret.push(Prop_Invalid_Type(proto_prop, libconfig::Setting::TypeInt));
+      }
+    } else if (svc_info.getSvcType() != ServiceGroup::STANDARD) {
+      // Required if it's not standard / predefined.
+      zret.push(Prop_Not_Found(SVC_PROP_PROTOCOL, SVC_NAME, svc_line));
+    }
+
+    // Get the priority.
+    svc_info.setPriority(0); // OK to default to this value.
+    if (svc_cfg.exists(SVC_PROP_PRIORITY)) {
+      libconfig::Setting& pri_prop = svc_cfg[SVC_PROP_PRIORITY];
+      if (svc_info.getSvcType() == ServiceGroup::STANDARD) {
+        zret.push(Svc_Prop_Ignored(SVC_PROP_PRIORITY, pri_prop.getSourceLine()));
+      } else if (pri_prop.isNumber()) {
+        x = static_cast<int>(pri_prop);
+        if (0 <= x && x <= 255)
+          svc_info.setPriority(x);
+        else
+          zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_ID, pri_prop, x, 0, 255));
+      } else {
+        zret.push(Prop_Invalid_Type(pri_prop, libconfig::Setting::TypeInt));
+      }
+    }
+
+    // Service flags.
+    svc_info.setFlags(0);
+
+    if (svc_cfg.exists(SVC_PROP_PRIMARY_HASH)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_PRIMARY_HASH];
+      ats::Errata status = load_option_set(prop, HASH_OPTS, N_OPTS(HASH_OPTS));
+      uint32 f = 0;
+      src_line = prop.getSourceLine();
+      for ( size_t i = 0 ; i < N_OPTS(HASH_OPTS) ; ++i )
+        if (HASH_OPTS[i].m_found) f |= ServiceGroup::SRC_IP_HASH << i;
+      if (f) {
+        svc_info.enableFlags(f);
+        if (!status) zret.push(Ignored_Opt_Errors(SVC_PROP_PRIMARY_HASH, src_line).set(status));
+      } else {
+        zret.push(List_Valid_Opts(SVC_PROP_PRIMARY_HASH, src_line, HASH_OPTS, N_OPTS(HASH_OPTS)).set(status));
+      }
+    } else {
+      zret.push(Prop_Not_Found(SVC_PROP_PRIMARY_HASH, SVC_NAME, svc_line));
+    }
+
+    if (svc_cfg.exists(SVC_PROP_ALT_HASH)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_ALT_HASH];
+      ats::Errata status = load_option_set(prop, HASH_OPTS, N_OPTS(HASH_OPTS));
+      uint32 f = 0;
+      src_line = prop.getSourceLine();
+      for ( size_t i = 0 ; i < N_OPTS(HASH_OPTS) ; ++i )
+        if (HASH_OPTS[i].m_found) f |= ServiceGroup::SRC_IP_ALT_HASH << i;
+      if (f) svc_info.enableFlags(f);
+      if (!status) zret.push(Ignored_Opt_Errors(SVC_PROP_ALT_HASH, src_line).set(status));
+    }
+
+    if (svc_cfg.exists(SVC_PROP_PORT_TYPE)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_PORT_TYPE];
+      src_line = prop.getSourceLine();
+      if (libconfig::Setting::TypeString == prop.getType()) {
+        text = static_cast<char const*>(prop);
+        if (0 == strcasecmp("src", text))
+          svc_info.enableFlags(ServiceGroup::PORTS_SOURCE);
+        else if (0 == strcasecmp("dst", text))
+          svc_info.disableFlags(ServiceGroup::PORTS_SOURCE);
+        else
+          zret.push(Port_Type_Invalid(text, src_line));
+      } else {
+        zret.push(Prop_Invalid_Type(prop, libconfig::Setting::TypeString));
+      }
+    }
+
+    // Ports for service.
+    svc_info.clearPorts();
+    if (svc_cfg.exists(SVC_PROP_PORTS)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_PORTS];
+      src_line = prop.getSourceLine();
+      if (ServiceGroup::STANDARD == svc_info.getSvcType()) {
+        zret.push(Svc_Prop_Ignored_In_Standard(SVC_PROP_PORTS, src_line));
+      } else {
+        if ( prop.isAggregate() ) {
+          int nport = prop.getLength();
+          int pidx, sidx;
+          bool malformed_error = false;
+          // Clip to maximum protocol allowed ports.
+          if (nport > ServiceGroup::N_PORTS) {
+            zret.push(Svc_Ports_Too_Many(src_line, nport));
+            nport = ServiceGroup::N_PORTS;
+          }
+          // Step through the ports.
+          for ( pidx = sidx = 0 ; pidx < nport ; ++pidx ) {
+            libconfig::Setting& port_cfg = prop[pidx];
+            if (port_cfg.isNumber()) {
+              x = static_cast<int>(port_cfg);
+              if (0 <= x && x <= 65535)
+                svc_info.setPort(sidx++, x);
+              else
+                zret.push(Svc_Prop_Out_Of_Range(SVC_PROP_PORTS, port_cfg, x, 0, 65535));
+            } else if (!malformed_error) { // only report this once.
+              zret.push(Svc_Ports_Malformed(src_line));
+              malformed_error = true;
+            }
+          }
+          if (sidx) svc_info.enableFlags(ServiceGroup::PORTS_DEFINED);
+          else zret.push(Svc_Ports_None_Valid(src_line));
+        } else {
+          zret.push(Prop_Invalid_Type(prop, libconfig::Setting::TypeArray));
+        }
+      }
+    } else if (ServiceGroup::STANDARD != svc_info.getSvcType()) {
+      zret.push(Svc_Ports_Not_Found(svc_line));
+    }
+
+    // Security option for this service group.
+    if (svc_cfg.exists(SVC_PROP_SECURITY)) {
+      ats::Rv<char const*> security = load_security(svc_cfg[SVC_PROP_SECURITY]);
+      if (security.isOK()) {
+        use_group_local_security = true;
+        if (security.result()) {
+          md5_key = security;
+          security_style = SECURITY_MD5;
+        } else {
+          security_style = SECURITY_NONE;
+        }
+      }
+      zret.join(security.errata());
+    }
+
+    // Get any group specific routers.
+    routers.clear(); // reset list.
+    if (svc_cfg.exists(SVC_PROP_ROUTERS)) {
+      libconfig::Setting& rtr_cfg = svc_cfg[SVC_PROP_ROUTERS];
+      ats::Errata status = load_routers(rtr_cfg, routers);
+      if (!status)
+        zret.push(ats::Errata::Message(23, LVL_INFO, "Router specification invalid.").set(status));
+    }
+    if (!routers.size() && !Seed_Router.size())
+      zret.push(No_Valid_Routers(svc_line));
+
+    // See if can proceed with service group creation.
+    ats::Errata::Code code = code_max(zret);
+    if (code >= LVL_WARN) {
+      zret = Unable_To_Create_Service_Group(svc_line).set(zret);
+      return zret;
+    }
+
+    // Properties after this are optional so we can proceed if they fail.
+    GroupData& svc = this->defineServiceGroup(svc_info);
+    // Add seed routers.
+    std::vector<uint32>::iterator rspot, rlimit;
+    for ( rspot = routers.begin(), rlimit = routers.end() ; rspot != rlimit ; ++rspot )
+      svc.seedRouter(*rspot);
+    for ( rspot = Seed_Router.begin(), rlimit = Seed_Router.end() ; rspot != rlimit ; ++rspot )
+      svc.seedRouter(*rspot);
+
+    if (use_group_local_security)
+      svc.setSecurity(security_style).setKey(md5_key);
+
+    // Look for optional properties.
+
+    svc.m_packet_forward = ServiceGroup::GRE; // default
+    if (svc_cfg.exists(SVC_PROP_FORWARD)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_FORWARD];
+      ats::Errata status = load_option_set(prop, FORWARD_OPTS, N_FORWARD_OPTS);
+      bool gre = FORWARD_OPTS[0].m_found;
+      bool l2 = FORWARD_OPTS[1].m_found;
+      if (gre || l2) {
+        svc.m_packet_forward = 
+          gre
+            ? l2 ? ServiceGroup::GRE_OR_L2 : ServiceGroup::GRE
+            : ServiceGroup::L2
+          ;
+        if (!status.isOK())
+          zret.push(Ignored_Opt_Errors(SVC_PROP_FORWARD, prop.getSourceLine()).set(status));
+      } else {
+        zret.push(ats::Errata::Message(26, LVL_INFO, "Defaulting to GRE forwarding.").set(status));
+      }
+    }
+
+    svc.m_packet_return = ServiceGroup::GRE; // default.
+    if (svc_cfg.exists(SVC_PROP_RETURN)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_RETURN];
+      ats::Errata status = load_option_set(prop, RETURN_OPTS, N_RETURN_OPTS);
+      bool gre = RETURN_OPTS[0].m_found;
+      bool l2 = RETURN_OPTS[1].m_found;
+      if (gre || l2) {
+        svc.m_packet_return =
+          gre
+            ? l2 ? ServiceGroup::GRE_OR_L2 : ServiceGroup::GRE
+            : ServiceGroup::L2
+          ;
+        if (!status.isOK()) zret.push(Ignored_Opt_Errors(SVC_PROP_RETURN, prop.getSourceLine()).set(status));
+      } else {
+        zret.push(ats::Errata::Message(26, LVL_INFO, "Defaulting to GRE return.").set(status));
+      }
+    }
+
+    svc.m_cache_assign = ServiceGroup::HASH_ONLY; // default
+    if (svc_cfg.exists(SVC_PROP_ASSIGN)) {
+      libconfig::Setting& prop = svc_cfg[SVC_PROP_ASSIGN];
+      ats::Errata status = load_option_set(prop, ASSIGN_OPTS, N_OPTS(ASSIGN_OPTS));
+      bool hash = ASSIGN_OPTS[0].m_found;
+      bool mask = ASSIGN_OPTS[1].m_found;
+      if (hash || mask) {
+        svc.m_cache_assign =
+          hash
+            ? mask ? ServiceGroup::HASH_OR_MASK : ServiceGroup::HASH_ONLY
+            : ServiceGroup::MASK_ONLY
+          ;
+        if (!status.isOK()) zret.push(Ignored_Opt_Errors(SVC_PROP_ASSIGN, prop.getSourceLine()).set(status));
+      } else {
+        status.push(ats::Errata::Message(26, LVL_INFO, "Defaulting to hash assignment only."));
+        zret.push(List_Valid_Opts(SVC_PROP_ASSIGN, src_line, ASSIGN_OPTS, N_OPTS(ASSIGN_OPTS)).set(status));
+      }
+    }
+  }
+  return zret;
+}
+
+} // namespace.

Added: trafficserver/traffic/branches/wccp/proxy/wccp/WccpEndPoint.cc
URL: http://svn.apache.org/viewvc/trafficserver/traffic/branches/wccp/proxy/wccp/WccpEndPoint.cc?rev=1021825&view=auto
==============================================================================
--- trafficserver/traffic/branches/wccp/proxy/wccp/WccpEndPoint.cc (added)
+++ trafficserver/traffic/branches/wccp/proxy/wccp/WccpEndPoint.cc Tue Oct 12 16:10:47 2010
@@ -0,0 +1,1117 @@
+# include "WccpLocal.h"
+# include "WccpUtil.h"
+# include "AtsMeta.h"
+# include <errno.h>
+// ------------------------------------------------------
+namespace Wccp {
+// ------------------------------------------------------
+Impl::GroupData::GroupData()
+  : m_generation(0)
+  , m_use_security_opt(false)
+  , m_use_security_key(false) {
+}
+
+Impl::GroupData&
+Impl::GroupData::setKey(char const* key) {
+  m_use_security_key = true;
+  memset(m_security_key, 0, SecurityComp::KEY_SIZE);
+  strncpy(m_security_key, key, SecurityComp::KEY_SIZE);
+  return *this;
+}
+
+Impl::GroupData&
+Impl::GroupData::setSecurity(SecurityOption style) {
+  m_use_security_opt = true;
+  m_security_opt = style;
+  return *this;
+}
+
+Impl::Impl()
+  : m_addr(INADDR_ANY)
+  , m_fd(ats::NO_FD) {
+}
+
+Impl::~Impl() {
+  this->close();
+}
+
+int
+Impl::open(uint addr) {
+  struct sockaddr saddr;
+  sockaddr_in& in_addr = reinterpret_cast<sockaddr_in&>(saddr);
+  int fd;
+
+  if (ats::NO_FD != m_fd) {
+    log(LVL_INFO, "Attempted to open already open WCCP Endpoint");
+    return -EALREADY;
+  }
+
+  if (ats::NO_FD == (fd = socket(PF_INET, SOCK_DGRAM, 0))) {
+    log_errno(LVL_FATAL, "Failed to create socket");
+    return -errno;
+  }
+
+  if (INADDR_ANY != addr) m_addr = addr; // overridden.
+  memset(&saddr, 0, sizeof(saddr));
+  in_addr.sin_family = AF_INET;
+  in_addr.sin_port = htons(DEFAULT_PORT);
+  in_addr.sin_addr.s_addr = m_addr;
+  int zret = bind(fd, &saddr, sizeof(saddr));
+  if (-1 == zret) {
+    log_errno(LVL_FATAL, "Failed to bind socket to port");
+    this->close();
+    return -errno;
+  }
+  logf(LVL_INFO, "WCCP socket bound to %s:%d", ip_addr_to_str(m_addr), DEFAULT_PORT);
+
+  // Now get the address. Usually the same but possibly different,
+  // certainly if addr was INADDR_ANY.
+  if (INADDR_ANY == m_addr && INADDR_ANY == (m_addr = Get_Local_Address(fd))) {
+    log_errno(LVL_FATAL, "Failed to get local address for socket");
+    this->close();
+    return -errno;
+  }
+
+  // Enable retrieval of destination address on packets.
+  int ip_pktinfo_flag = 1;
+  if (-1 == setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &ip_pktinfo_flag, sizeof(ip_pktinfo_flag))) {
+    log_errno(LVL_FATAL, "Failed to enable destination address retrieval");
+    this->close();
+    return -errno;
+  }
+
+  /// Disable PMTU on Linux because of a bug in IOS routers.
+  /// WCCP packets are rejected as duplicates if the IP fragment
+  /// identifier is 0, which is the value used when PMTU is enabled.
+  int pmtu = IP_PMTUDISC_DONT;
+  if (-1 == setsockopt(fd, IPPROTO_IP, IP_MTU_DISCOVER, &pmtu, sizeof(pmtu))) {
+    log_errno(LVL_FATAL, "Failed to disable PMTU on WCCP socket.");
+    this->close();
+    return -errno;
+  }
+
+  m_fd = fd;
+  return 0;
+}
+
+void
+Impl::close() {
+  if (ats::NO_FD != m_fd) {
+    ::close(m_fd);
+    m_fd = ats::NO_FD;
+  }
+}
+
+void
+Impl::useMD5Security(char const* key) {
+  m_use_security_opt = true;
+  m_security_opt = SECURITY_MD5;
+  m_use_security_key = true;
+  memset(m_security_key, 0, SecurityComp::KEY_SIZE);
+  strncpy(m_security_key, key, SecurityComp::KEY_SIZE);
+}
+
+SecurityOption
+Impl::setSecurity(BaseMsg& msg, GroupData const& group) const {
+  SecurityOption zret = SECURITY_NONE;
+  if (group.m_use_security_opt) zret = group.m_security_opt;
+  else if (m_use_security_opt) zret =  m_security_opt;
+  if (group.m_use_security_key) msg.m_security.setKey(group.m_security_key);
+  else if (m_use_security_key) msg.m_security.setKey(m_security_key);
+  return zret;
+}
+
+bool
+Impl::validateSecurity(BaseMsg& msg, GroupData const& group) {
+  SecurityOption opt = msg.m_security.getOption();
+  if (group.m_use_security_opt) {
+    if (opt != group.m_security_opt) return false;
+  } else if (m_use_security_opt) {
+    if (opt != m_security_opt) return false;
+  }
+  if (opt == SECURITY_MD5) {
+    if (group.m_use_security_key) msg.m_security.setKey(group.m_security_key);
+    else if (m_use_security_key) msg.m_security.setKey(m_security_key);
+    return msg.validateSecurity();
+  }
+  return true;
+}
+
+ats::Rv<int>
+Impl::handleMessage() {
+  ats::Rv<int> zret;
+  ssize_t n; // recv byte count.
+  struct sockaddr src_addr; // sender's address.
+  msghdr recv_hdr;
+  iovec recv_buffer;
+  IpHeader ip_header;
+  static ssize_t const BUFFER_SIZE = 65536;
+  char buffer[BUFFER_SIZE];
+  static size_t const ANC_BUFFER_SIZE = CMSG_ALIGN(CMSG_SPACE(sizeof(in_pktinfo)));
+  char anc_buffer[ANC_BUFFER_SIZE];
+
+  if (ats::NO_FD == m_fd) return -ENOTCONN;
+
+  recv_buffer.iov_base = buffer;
+  recv_buffer.iov_len = BUFFER_SIZE;
+
+  recv_hdr.msg_name = &src_addr;
+  recv_hdr.msg_namelen = sizeof(src_addr);
+  recv_hdr.msg_iov = &recv_buffer;
+  recv_hdr.msg_iovlen = 1;
+  recv_hdr.msg_control = anc_buffer;
+  recv_hdr.msg_controllen = ANC_BUFFER_SIZE;
+  
+  n = recvmsg(m_fd, &recv_hdr, MSG_TRUNC);
+  if (n > BUFFER_SIZE) return -EMSGSIZE;
+  else if (n < 0) return -errno;
+
+  // Extract the original destination address.
+  ip_header.m_src = access_field(&sockaddr_in::sin_addr, &src_addr).s_addr;
+  for ( cmsghdr* anc = CMSG_FIRSTHDR(&recv_hdr);
+        anc;
+        anc = CMSG_NXTHDR(&recv_hdr, anc)
+  ) {
+    if (anc->cmsg_level == IPPROTO_IP && anc->cmsg_type == IP_PKTINFO) {
+      ip_header.m_dst = access_field(&in_pktinfo::ipi_addr, CMSG_DATA(anc)).s_addr;
+      break;
+    }
+  }
+
+  // Check to see if there is a valid header.
+  MsgHeaderComp header;
+  MsgBuffer msg_buffer(buffer,n);
+  if (PARSE_SUCCESS == header.parse(msg_buffer)) {
+    message_type_t msg_type = header.getType();
+    ats::Buffer chunk(buffer,n);
+
+    switch (msg_type) {
+    case HERE_I_AM: this->handleHereIAm(ip_header, chunk); break;
+    case I_SEE_YOU: this->handleISeeYou(ip_header, chunk); break;
+    case REDIRECT_ASSIGN: this->handleRedirectAssign(ip_header, chunk); break;
+    case REMOVAL_QUERY: this->handleRemovalQuery(ip_header, chunk); break;
+    default: fprintf(stderr, "Unknown message type %d ignored.\n", msg_type);
+      break;
+    };
+  } else {
+    fprintf(stderr, "Malformed message ignored.\n");
+  }
+  return zret;
+}
+
+ats::Errata
+Impl::handleHereIAm(IpHeader const&, ats::Buffer const&) {
+  return log(LVL_INFO, "Unanticipated WCCP2_HERE_I_AM message ignored");
+}
+ats::Errata
+Impl::handleISeeYou(IpHeader const&, ats::Buffer const& data) {
+  return log(LVL_INFO, "Unanticipated WCCP2_I_SEE_YOU message ignored.");
+}
+ats::Errata
+Impl::handleRedirectAssign(IpHeader const&, ats::Buffer const& data) {
+  return log(LVL_INFO, "Unanticipated WCCP2_REDIRECT_ASSIGN message ignored.");
+}
+ats::Errata
+Impl::handleRemovalQuery(IpHeader const&, ats::Buffer const& data) {
+  return log(LVL_INFO, "Unanticipated WCCP2_REMOVAL_QUERY message ignored.");
+}
+// ------------------------------------------------------
+CacheImpl::GroupData::GroupData()
+  : m_assignment_pending(false) {
+}
+
+CacheImpl::GroupData&
+CacheImpl::GroupData::seedRouter(uint32 addr) {
+  // Be nice and don't add it if it's already there.
+  if (m_seed_routers.end() == find_by_member(m_seed_routers, &SeedRouter::m_addr, addr))
+    m_seed_routers.push_back(SeedRouter(addr));
+  return *this;
+}
+
+time_t
+CacheImpl::GroupData::removeSeedRouter(uint32 addr) {
+  time_t zret = 0;
+  std::vector<SeedRouter>::iterator begin = m_seed_routers.begin(); 
+  std::vector<SeedRouter>::iterator end = m_seed_routers.end();
+  std::vector<SeedRouter>::iterator spot = 
+    std::find_if(begin, end, ats::predicate(&SeedRouter::m_addr, addr));
+
+  if (end != spot) {
+    zret = spot->m_xmit;
+    m_seed_routers.erase(spot);
+  }
+
+  return zret;
+}
+
+CacheImpl::GroupData& CacheImpl::GroupData::setKey(char const* key) { return static_cast<self&>(this->super::setKey(key)); }
+
+CacheImpl::GroupData& CacheImpl::GroupData::setSecurity(SecurityOption style) { return static_cast<self&>(this->super::setSecurity(style)); }
+
+CacheImpl::CacheBag::iterator
+CacheImpl::GroupData::findCache(uint32 addr) {
+  return std::find_if(
+    m_caches.begin(),
+    m_caches.end(),
+    ats::predicate(&CacheData::idAddr, addr)
+  );
+}
+
+void
+CacheImpl::GroupData::resizeCacheSources() {
+  int count = m_routers.size();
+  for ( CacheBag::iterator spot = m_caches.begin(),
+          limit = m_caches.end();
+        spot != limit;
+        ++spot
+  ) {
+    spot->m_src.resize(count);
+  }
+}
+
+inline CacheImpl::RouterData::RouterData() 
+  : m_addr(0)
+  , m_generation(0)
+  , m_rapid(0)
+  , m_assign(false)
+  , m_send_caps(false) {
+}
+
+inline CacheImpl::RouterData::RouterData(uint32 addr)
+  : m_addr(addr)
+  , m_generation(0)
+  , m_rapid(0)
+  , m_assign(false)
+  , m_send_caps(false) {
+}
+
+time_t
+CacheImpl::RouterData::pingTime(time_t now) const {
+  time_t tx = m_xmit.m_time + (m_rapid ? TIME_UNIT/10 : TIME_UNIT);
+  return tx < now ? 0 : tx - now;
+}
+
+time_t
+CacheImpl::RouterData::waitTime(time_t now) const {
+  return m_assign ? 0 : this->pingTime(now);
+}
+
+inline uint32
+CacheImpl::CacheData::idAddr() const {
+  return m_id.getAddr();
+}
+
+CacheImpl::GroupData&
+CacheImpl::defineServiceGroup(
+  ServiceGroup const& svc,
+  ServiceGroup::Result* result
+) {
+  uint8 svc_id = svc.getSvcId();
+  GroupMap::iterator spot = m_groups.find(svc_id);
+  GroupData* group; // service with target ID.
+  ServiceGroup::Result zret;
+  if (spot == m_groups.end()) { // not defined
+    group = &(m_groups[svc_id]);
+    group->m_svc = svc;
+    memset(&group->m_id, 0, sizeof(group->m_id));
+    group->m_id.setAddr(m_addr);
+    group->m_id.setUnassigned(true);
+    group->m_id.setBuckets(false);
+    zret = ServiceGroup::DEFINED;
+  } else {
+    group = &spot->second;
+    zret = group->m_svc == svc ? ServiceGroup::EXISTS : ServiceGroup::CONFLICT;
+  }
+  if (result) *result = zret;
+  return *group;
+}
+
+time_t
+CacheImpl::GroupData::waitTime(time_t now) const {
+  time_t zret = std::numeric_limits<time_t>::max();
+  // Active routers.
+  for ( RouterBag::const_iterator router = m_routers.begin(),
+          router_limit = m_routers.end() ;
+        router != router_limit && zret;
+        ++router
+  ) {
+    zret = std::min(zret, router->waitTime(now));
+    }
+  // Seed routers.
+  for ( std::vector<SeedRouter>::const_iterator
+          router = m_seed_routers.begin(),
+          router_limit = m_seed_routers.end() ;
+        router != router_limit && zret;
+        ++router
+  ) {
+    time_t tx = router->m_xmit + TIME_UNIT;
+    if (tx < now) zret = 0;
+    else zret = std::min(tx - now, zret);
+  }
+  // Assignment
+  if (m_assignment_pending) {
+    time_t tx = m_generation_time + ( 3 * TIME_UNIT / 2 );
+    if (tx < now) zret = 0;
+    else zret = std::min(tx - now, zret);
+  }
+
+  return zret;
+}
+
+bool
+CacheImpl::GroupData::cullRouters(time_t now) {
+  bool zret = false;
+  size_t idx = 0, n = m_routers.size();
+  while ( idx < n ) {
+    RouterData& router = m_routers[idx];
+    if (router.m_recv.m_time + TIME_UNIT * 3 < now) {
+      uint32 addr = router.m_addr;
+      // Clip the router by copying down and resizing.
+      // Must do all caches as well.
+      --n; // Decrement router counter first.
+      if (idx < n) router = m_routers[n];
+      m_routers.resize(n);
+      for ( CacheBag::iterator
+              cache = m_caches.begin(),
+              cache_limit = m_caches.end();
+            cache != cache_limit;
+            ++cache
+      ) {
+        if (idx < n) cache->m_src[idx] = cache->m_src[n];
+        cache->m_src.resize(n);
+      }
+      // Put it back in the seeds.
+      this->seedRouter(addr);
+      zret = true; // Router was culled, report it to caller.
+      logf(LVL_INFO, "Router " ATS_IP_PRINTF_CODE " timed out and was removed from the active list.", ATS_IP_OCTETS(addr));
+    } else {
+      ++idx; // move to next router.
+    }
+  }
+  if (zret) this->viewChanged(now);
+  return zret;
+}
+
+CacheImpl::GroupData&
+CacheImpl::GroupData::viewChanged(time_t now) {
+  m_generation += 1;
+  m_generation_time = now;
+  m_assign_info.setActive(false); // invalidate current assignment.
+  m_assignment_pending = m_routers.size() && m_caches.size();
+  // Cancel any pending assignment transmissions.
+  ats::for_each(m_routers, ats::assign_member(&RouterData::m_assign, false));
+  logf(LVL_DEBUG, "Service group %d view change (%d)", m_svc.getSvcId(), m_generation);
+
+  return *this;
+}
+
+Cache::Service&
+Cache::Service::setKey(char const* key) {
+  m_group->setKey(key);
+  return *this;
+}
+
+Cache::Service&
+Cache::Service::setSecurity(SecurityOption opt) {
+  m_group->setSecurity(opt);
+  return *this;
+}
+
+CacheImpl&
+CacheImpl::seedRouter(uint8 id, uint32 addr) {
+  GroupMap::iterator spot = m_groups.find(id);
+  if (spot != m_groups.end()) spot->second.seedRouter(addr);
+  return *this;
+}
+
+bool
+CacheImpl::isConfigured() const {
+  return INADDR_ANY != m_addr && m_groups.size() > 0;
+}
+
+int
+CacheImpl::open(uint32 addr) {
+  int zret = this->super::open(addr);
+  // If the socket was successfully opened, go through the
+  // services and update the local service descriptor.
+  if (0 <= zret) {
+    for ( GroupMap::iterator spot = m_groups.begin(), limit = m_groups.end();
+          spot != limit;
+          ++spot
+    ) {
+      spot->second.m_id.setAddr(m_addr);
+    }
+  }
+  return zret;
+}
+
+time_t
+CacheImpl::waitTime() const {
+  time_t now = time(0);
+  return ats::minima(m_groups, &GroupData::waitTime, now);
+}
+
+void
+CacheImpl::generateHereIAm(
+  HereIAmMsg& msg,
+  GroupData& group
+) {
+  msg.fill(group, this->setSecurity(msg, group), 0, 0);
+  msg.finalize();
+}
+
+void
+CacheImpl::generateHereIAm(
+  HereIAmMsg& msg,
+  GroupData& group,
+  RouterData& router
+) {
+  int i;
+  size_t n_routers = group.m_routers.size();
+  size_t n_caches = group.m_caches.size();
+
+  msg.fill(group, this->setSecurity(msg, group), n_routers, n_caches);
+  msg.fill_caps(router);
+
+  // Fill routers.
+  i = 0;
+  for ( RouterBag::iterator spot = group.m_routers.begin(),
+          limit = group.m_routers.end();
+        spot != limit;
+        ++spot, ++i
+  ) {
+    msg.m_cache_view.routerElt(i)
+      .setAddr(spot->m_addr)
+      .setRecvId(spot->m_recv.m_sn)
+      ;
+  }
+
+  // fill caches.
+  i = 0;
+  for ( CacheBag::iterator spot = group.m_caches.begin(),
+          limit = group.m_caches.end();
+        spot != limit;
+        ++spot, ++i
+  ) {
+    msg.m_cache_view.setCacheAddr(i, spot->idAddr());
+  }
+
+  msg.finalize();
+}
+
+void
+CacheImpl::generateRedirectAssign(
+  RedirectAssignMsg& msg,
+  GroupData& group
+) {
+  int i;
+  size_t n_caches = group.m_caches.size();
+  size_t n_routers = group.m_caches.size();
+  AssignmentKeyElt key(m_addr, group.m_generation);
+
+  msg.fill(group, this->setSecurity(msg, group), key, n_routers, n_caches);
+
+  // Fill routers.
+  i = 0;
+  for ( RouterBag::iterator spot = group.m_routers.begin(),
+          limit = group.m_routers.end();
+        spot != limit;
+        ++spot, ++i
+  ) {
+    msg.m_assign.routerElt(i)
+      .setChangeNumber(spot->m_generation)
+      .setAddr(spot->m_addr)
+      .setRecvId(spot->m_recv.m_sn)
+      ;
+  }
+
+  // fill caches.
+  i = 0;
+  for ( CacheBag::iterator spot = group.m_caches.begin(),
+          limit = group.m_caches.end();
+        spot != limit;
+        ++spot, ++i
+  ) {
+    msg.m_assign.setCacheAddr(i, spot->idAddr());
+  }
+
+  msg.finalize();
+}
+
+int
+CacheImpl::housekeeping() {
+  int zret = 0;
+  sockaddr_in dst_addr;
+  sockaddr* addr_ptr = reinterpret_cast<sockaddr*>(&dst_addr);
+  time_t now = time(0);
+  static size_t const BUFFER_SIZE = 4096;
+  MsgBuffer msg_buffer;
+  char msg_data[BUFFER_SIZE];
+  msg_buffer.set(msg_data, BUFFER_SIZE);
+
+  // Set up everything except the IP address.
+  memset(&dst_addr, 0, sizeof(dst_addr));
+  dst_addr.sin_family = AF_INET;
+  dst_addr.sin_port = htons(DEFAULT_PORT);
+
+  // Walk the service groups and do their housekeeping.
+  for ( GroupMap::iterator
+          svc_spot = m_groups.begin(),
+          svc_limit = m_groups.end();
+        svc_spot != svc_limit;
+        ++svc_spot
+  ) {
+    GroupData& group = svc_spot->second;
+
+    // Check to see if it's time for an assignment.
+    if (group.m_assignment_pending
+      && group.m_generation_time + ASSIGN_WAIT <= now
+    ) {
+      // Is a valid assignment possible?
+      if (group.m_assign_info.fill(group, m_addr))
+        ats::for_each(group.m_routers, ats::assign_member(&RouterData::m_assign, true));
+
+      // Always clear because no point in sending a assign we can't generate.
+      group.m_assignment_pending = false;
+    }
+
+    group.cullRouters(now); // TBD UPDATE VIEW!
+
+    // Check the active routers for scheduled packets.
+    for ( RouterBag::iterator rspot = group.m_routers.begin(),
+             rend = group.m_routers.end() ;
+           rspot != rend ;
+           ++rspot
+    ) {
+      dst_addr.sin_addr.s_addr = rspot->m_addr;
+      if (0 == rspot->pingTime(now)) {
+        HereIAmMsg here_i_am;
+        here_i_am.setBuffer(msg_buffer);
+        this->generateHereIAm(here_i_am, group, *rspot);
+        zret = sendto(m_fd, msg_data, here_i_am.getCount(), 0, addr_ptr, sizeof(dst_addr));
+        if (0 <= zret) {
+          rspot->m_xmit.set(now, group.m_generation);
+          rspot->m_send_caps = false;
+          logf(LVL_DEBUG, "Sent HERE_I_AM for service group %d to router %s%s[#%d,%lu].",
+            group.m_svc.getSvcId(),
+            ip_addr_to_str(rspot->m_addr),
+            rspot->m_rapid ? " [rapid] " : " ",
+            group.m_generation, now
+          );
+          if (rspot->m_rapid) --(rspot->m_rapid);
+        } else {
+          logf_errno(LVL_WARN, "Failed to send to router " ATS_IP_PRINTF_CODE " - ", ATS_IP_OCTETS(rspot->m_addr));
+        }
+      } else if (rspot->m_assign) {
+        RedirectAssignMsg redirect_assign;
+        redirect_assign.setBuffer(msg_buffer);
+        this->generateRedirectAssign(redirect_assign, group);
+        zret = sendto(m_fd, msg_data, redirect_assign.getCount(), 0, addr_ptr, sizeof(dst_addr));
+        if (0 <= zret) rspot->m_assign = false;
+      }
+    }
+
+    // Seed routers.
+    for ( std::vector<SeedRouter>::iterator
+            sspot = group.m_seed_routers.begin(),
+            slimit = group.m_seed_routers.end() ;
+           sspot != slimit ;
+           ++sspot
+    ) {
+      HereIAmMsg here_i_am;
+      here_i_am.setBuffer(msg_buffer);
+      // Is the router due for a ping?
+      if (sspot->m_xmit + TIME_UNIT > now) continue; // no
+      
+      this->generateHereIAm(here_i_am, group);
+
+      dst_addr.sin_addr.s_addr = sspot->m_addr;
+      zret = sendto(m_fd, msg_data, here_i_am.getCount(), 0,
+        addr_ptr, sizeof(dst_addr));
+      if (0 <= zret) {
+        logf(LVL_DEBUG, "Sent HERE_I_AM for SG %d to seed router %s [#%d,%lu].",
+          group.m_svc.getSvcId(),
+          ip_addr_to_str(sspot->m_addr),
+          group.m_generation, now
+        );
+        sspot->m_xmit = now;
+        sspot->m_count += 1;
+      }
+    }
+  }
+  return zret;
+}
+
+ats::Errata
+CacheImpl::handleISeeYou(IpHeader const& ip_hdr, ats::Buffer const& chunk) {
+  ats::Errata zret;
+  ISeeYouMsg msg;
+  // Set if our view of the group changes enough to bump the
+  // generation number.
+  bool view_changed = false;
+  time_t now = time(0); // don't call this over and over.
+  int parse = msg.parse(chunk);
+
+  if (PARSE_SUCCESS != parse)
+    return log(LVL_INFO, "Ignored malformed WCCP2_I_SEE_YOU message.");
+
+  ServiceGroup svc(msg.m_service);
+  GroupMap::iterator spot = m_groups.find(svc.getSvcId());
+  if (spot == m_groups.end())
+    return logf(LVL_INFO, "WCCP2_I_SEE_YOU ignored - service group %d not found.", svc.getSvcId());
+
+  GroupData& group = spot->second;
+
+  if (!this->validateSecurity(msg, group))
+    return log(LVL_INFO, "Ignored WCCP2_I_SEE_YOU with invalid security.\n");
+
+  if (svc != group.m_svc)
+    return logf(LVL_INFO, "WCCP2_I_SEE_YOU ignored - service group definition %d does not match.\n", svc.getSvcId());
+
+  if (-1 == msg.m_router_id.findFromAddr(m_addr))
+    return logf(LVL_INFO, "WCCP2_I_SEE_YOU ignored -- cache not in from list.\n");
+
+  logf(LVL_DEBUG, "Received WCCP2_I_SEE_YOU for group %d.", group.m_svc.getSvcId());
+
+  // Prefered address for router.
+  uint32 router_addr = msg.m_router_id.idElt().getAddr();
+  // Where we sent our packet.
+  uint32 to_addr = msg.m_router_id.getToAddr();
+  uint32 recv_id = msg.m_router_id.idElt().getRecvId();
+  RouterBag::iterator ar_spot; // active router
+  int router_idx; // index in active routers.
+  std::vector< SeedRouter >::iterator seed_spot;
+
+  // Handle the router that sent us this.
+  ar_spot = find_by_member(group.m_routers, &RouterData::m_addr, router_addr);
+  if (ar_spot == group.m_routers.end()) {
+    // Remove this from the seed routers and copy the last packet
+    // sent time.
+    RouterData r(router_addr); // accumulate state before we commit it.
+    r.m_xmit.m_time = group.removeSeedRouter(to_addr);
+
+    // Validate capabilities.
+    CapComp& caps = msg.m_capabilities;
+    ServiceGroup::PacketStyle ps;
+    ServiceGroup::CacheAssignmentStyle as;
+    char const* caps_tag = caps.isEmpty() ? "default" : "router";
+
+    ps = caps.isEmpty() ? ServiceGroup::GRE : caps.getPacketForwardStyle();
+    if (ServiceGroup::GRE & ps & group.m_packet_forward)
+      r.m_packet_forward = ServiceGroup::GRE;
+    else if (ServiceGroup::L2 & ps & group.m_packet_forward)
+      r.m_packet_forward = ServiceGroup::L2;
+    else
+      logf(zret, LVL_WARN, "Packet forwarding (config=%d, %s=%d) did not match.", group.m_packet_forward, caps_tag, ps);
+
+    ps = caps.isEmpty() ? ServiceGroup::GRE : caps.getPacketReturnStyle();
+    if (ServiceGroup::GRE & ps & group.m_packet_return)
+      r.m_packet_return = ServiceGroup::GRE;
+    else if (ServiceGroup::L2 & ps & group.m_packet_return)
+      r.m_packet_return = ServiceGroup::L2;
+    else
+      logf(zret, LVL_WARN, "Packet return (local=%d, %s=%d) did not match.", group.m_packet_return, caps_tag, ps);
+
+    as = caps.isEmpty() ? ServiceGroup::HASH_ONLY : caps.getCacheAssignmentStyle();
+    if (ServiceGroup::HASH_ONLY & as & group.m_cache_assign)
+      r.m_cache_assign = ServiceGroup::HASH_ONLY;
+    else if (ServiceGroup::MASK_ONLY & as & group.m_cache_assign)
+      r.m_cache_assign = ServiceGroup::MASK_ONLY;
+    else
+      logf(zret, LVL_WARN, "Cache assignment (local=%d, %s=%d) did not match.", group.m_cache_assign, caps_tag, as);
+
+    r.m_send_caps = ! caps.isEmpty();
+
+    if (!zret) {
+      // cancel out, can't use this packet because we reject the router.
+      return logf(zret, LVL_WARN, "Router %s rejected because of capabilities mismatch.", ip_addr_to_str(router_addr));
+    }
+
+    group.m_routers.push_back(r);
+    ar_spot = group.m_routers.end() - 1;
+    view_changed = true;
+    logf(LVL_INFO, "Added source router %s to view %d", ip_addr_to_str(router_addr), group.m_svc.getSvcId());
+  }
+  ar_spot->m_recv.set(now, recv_id);
+  ar_spot->m_generation = msg.m_router_view.getChangeNumber();
+  router_idx = ar_spot - group.m_routers.begin();
+
+  // For all the other listed routers, seed them if they're not
+  // already active.
+  uint32 nr = msg.m_router_view.getRouterCount();
+  for ( uint32 idx = 0; idx < nr ; ++idx ) {
+    uint32 addr = msg.m_router_view.getRouterAddr(idx);
+    if (group.m_routers.end() == find_by_member(group.m_routers, &RouterData::m_addr, addr))
+      group.seedRouter(addr);
+  }
+
+  // Update/Install the caches.
+  // TBD: Must bump view if a router fails to report a cache it reported
+  // in its last packet.
+  group.resizeCacheSources();
+  uint32 nc = msg.m_router_view.getCacheCount();
+  for ( uint32 idx = 0 ; idx < nc ; ++idx ) {
+    CacheIdElt& cache = msg.m_router_view.cacheElt(idx);
+    CacheBag::iterator ac_spot = group.findCache(cache.getAddr());
+    if (group.m_caches.end() == ac_spot) {
+      group.m_caches.push_back(CacheData());
+      ac_spot = group.m_caches.end() - 1;
+      ac_spot->m_src.resize(group.m_routers.size());
+      logf(LVL_INFO, "Added cache %s to view %d", ip_addr_to_str(cache.getAddr()), group.m_svc.getSvcId());
+      view_changed = true;
+    }
+    ac_spot->m_id = cache;
+    ac_spot->m_src[router_idx].set(now, recv_id);
+  }
+
+  if (view_changed) group.viewChanged(now);
+
+  return zret;
+}
+
+// ------------------------------------------------------
+inline uint32
+RouterImpl::CacheData::idAddr() const {
+  return m_id.getAddr();
+}
+
+RouterImpl::GroupData::GroupData() { }
+
+RouterImpl::CacheBag::iterator
+RouterImpl::GroupData::findCache(uint32 addr) {
+  return std::find_if(
+    m_caches.begin(),
+    m_caches.end(),
+    ats::predicate(&CacheData::idAddr, addr)
+  );
+}
+
+RouterImpl::GroupData&
+RouterImpl::defineServiceGroup(
+  ServiceGroup const& svc,
+  ServiceGroup::Result* result
+) {
+  uint8 svc_id = svc.getSvcId();
+  GroupMap::iterator spot = m_groups.find(svc_id);
+  GroupData* group; // service with target ID.
+  ServiceGroup::Result zret;
+  if (spot == m_groups.end()) { // not defined
+    group = &(m_groups[svc_id]);
+    group->m_svc = svc;
+    zret = ServiceGroup::DEFINED;
+  } else {
+    group = &spot->second;
+    zret = group->m_svc == svc ? ServiceGroup::EXISTS : ServiceGroup::CONFLICT;
+  }
+  if (result) *result = zret;
+  return *group;
+}
+
+void
+RouterImpl::GroupData::resizeRouterSources() {
+  ats::for_each(m_routers, &RouterData::resize, m_caches.size());
+}
+
+ats::Errata
+RouterImpl::handleHereIAm(IpHeader const& ip_hdr, ats::Buffer const& chunk) {
+  ats::Errata zret;
+  HereIAmMsg msg;
+  static GroupData nil_group; // scratch until I clean up the security.
+  // Set if our view of the group changes enough to bump the
+  // generation number.
+  bool view_changed = false;
+  int i; // scratch index var.
+  time_t now = time(0); // don't call this over and over.
+  int parse = msg.parse(chunk);
+
+  if (PARSE_SUCCESS != parse) 
+    return log(LVL_INFO, "Ignored malformed WCCP2_HERE_I_AM message.\n");
+
+  if (!this->validateSecurity(msg, nil_group))
+    return log(LVL_INFO, "Ignored WCCP2_HERE_I_AM with invalid security.\n");
+
+  ServiceGroup svc(msg.m_service);
+  ServiceGroup::Result r;
+  GroupData& group = this->defineServiceGroup(svc, &r);
+  if (ServiceGroup::CONFLICT == r)
+    return logf(LVL_INFO, "WCCP2_HERE_I_AM ignored - service group %d definition does not match.\n", svc.getSvcId());
+  else if (ServiceGroup::DEFINED == r)
+    return logf(LVL_INFO, "Service group %d defined by WCCP2_HERE_I_AM.\n", svc.getSvcId());
+
+  // Check if this cache is already known.
+  uint32 cache_addr = msg.m_cache_id.getAddr();
+  int cache_idx;
+  uint32 cache_gen;
+  CacheBag::iterator cache = group.findCache(cache_addr);
+  if (cache == group.m_caches.end()) { // not known
+    group.m_caches.push_back(CacheData());
+    // Vector modified, need clean end value.
+    cache = group.m_caches.end() - 1;
+    cache->m_recv_count = 0;
+    group.resizeRouterSources();
+    view_changed = true;
+  } else {
+    // Did the cache mention us specifically?
+    // If so, make sure the sequence # is correct.
+    RouterIdElt* me = msg.m_cache_view.findf_router_elt(m_addr);
+    if (me && me->getRecvId() != cache->m_recv_count)
+      return logf(LVL_INFO, "Discarded out of date (recv=%d, local=%ld) WCCP2_HERE_I_AM.\n", me->getRecvId(), cache->m_recv_count);
+  }
+
+  cache_gen = msg.m_cache_view.getChangeNumber();
+
+  cache_idx = cache - group.m_caches.begin();
+  cache->m_id = msg.m_cache_id.idElt();
+  cache->m_recv.set(now, cache_gen);
+  cache->m_pending = true;
+  cache->m_to_addr = ip_hdr.m_dst;
+
+  // Add any new routers
+  i = msg.m_cache_view.getRouterCount();
+  while(i-- > 0) {
+    uint32 addr = msg.m_cache_view.routerElt(i).getAddr();
+    RouterBag::iterator spot = find_by_member(group.m_routers, &RouterData::m_addr, addr);
+    if (spot == group.m_routers.end()) {
+      group.m_routers.push_back(RouterData());
+      // Can't count on previous end value, modified container.
+      spot = group.m_routers.end() - 1;
+      spot->m_addr = addr;
+      spot->m_src.resize(group.m_caches.size());
+      view_changed = true;
+    }
+    spot->m_src[cache_idx].set(now, cache_gen);
+  }
+
+  if (view_changed) ++(group.m_generation);
+  return zret;
+}
+
+void
+RouterImpl::generateISeeYou(
+  ISeeYouMsg& msg,
+  GroupData& group,
+  CacheData& cache
+) {
+  int i;
+  size_t n_routers = group.m_routers.size();
+  size_t n_caches = group.m_caches.size();
+
+  // Not handling multi-cast so target caches is hardwired to 1.
+  msg.fill(group, this->setSecurity(msg, group), group.m_assign_info, 1, n_routers, n_caches);
+
+  // Fill in ID data not done by fill.
+  msg.m_router_id
+    .setIdElt(m_addr, cache.m_recv_count + 1)
+    .setToAddr(cache.m_to_addr)
+    .setFromAddr(0, cache.idAddr());
+    ;
+
+  // Fill view routers.
+  i = 0;
+  for ( RouterBag::iterator router = group.m_routers.begin(),
+          router_limit = group.m_routers.end();
+        router != router_limit;
+        ++router, ++i
+  ) {
+    msg.m_router_view.setRouterAddr(i, router->m_addr);
+  }
+
+  // Fill view caches.
+  i = 0;
+  for ( CacheBag::iterator spot = group.m_caches.begin(),
+          limit = group.m_caches.end();
+        spot != limit;
+        ++spot, ++i
+  ) {
+    msg.m_router_view.cacheElt(i) = spot->m_id;
+  }
+
+  msg.finalize();
+}
+
+int
+RouterImpl::xmitISeeYou() {
+  int zret = 0;
+  ISeeYouMsg msg;
+  MsgBuffer buffer;
+  sockaddr_in dst_addr;
+  time_t now = time(0);
+  static size_t const BUFFER_SIZE = 4096;
+  char* data = static_cast<char*>(alloca(BUFFER_SIZE));
+
+  memset(&dst_addr, 0, sizeof(dst_addr));
+  dst_addr.sin_family = AF_INET;
+  dst_addr.sin_port = htons(DEFAULT_PORT);
+  buffer.set(data, BUFFER_SIZE);
+
+  // Send out messages for each service group.
+  for ( GroupMap::iterator svc_spot = m_groups.begin(),
+          svc_limit = m_groups.end() ;
+        svc_spot != svc_limit ;
+        ++svc_spot
+  ) {
+    GroupData& group = svc_spot->second;
+
+    // Check each active cache in the group.
+    for ( CacheBag::iterator cache = group.m_caches.begin(),
+             cache_limit = group.m_caches.end() ;
+           cache != cache_limit ;
+           ++cache
+    ) {
+      if (!cache->m_pending) continue;
+
+      msg.setBuffer(buffer);
+      this->generateISeeYou(msg, group, *cache);
+      dst_addr.sin_addr.s_addr = cache->m_id.getAddr();
+      zret = sendto(m_fd, data, msg.getCount(), 0,
+        reinterpret_cast<sockaddr*>(&dst_addr), sizeof(dst_addr));
+      if (0 <= zret) {
+        cache->m_xmit.set(now, group.m_generation);
+        cache->m_pending = false;
+        cache->m_recv_count = msg.m_router_id.getRecvId();
+        logf(LVL_DEBUG, "I_SEE_YOU -> %s\n", ip_addr_to_str(cache->m_id.getAddr()));
+      }
+      else {
+        log_errno(LVL_WARN, "Router transmit failed -");
+        return zret;
+      }
+    }
+  }
+  return zret;
+}
+
+int
+RouterImpl::housekeeping() {
+  return this->xmitISeeYou();
+}
+
+bool
+RouterImpl::isConfigured() const {
+  return false;
+}
+// ------------------------------------------------------
+EndPoint::EndPoint() {
+}
+
+EndPoint::~EndPoint() {
+}
+
+EndPoint::EndPoint(self const& that)
+  : m_ptr(that.m_ptr) {
+}
+
+inline EndPoint::ImplType*
+EndPoint::instance() {
+  return m_ptr ? m_ptr.get() : this->make();
+}
+
+EndPoint& EndPoint::setAddr(uint32 addr) {
+  this->instance()->m_addr = addr;
+  logf(LVL_DEBUG, "Endpoint address set to %s\n", ip_addr_to_str(addr));
+  return *this;
+}
+
+bool
+EndPoint::isConfigured() const {
+  return m_ptr && m_ptr->isConfigured();
+}
+
+int
+EndPoint::open(uint32 addr) {
+  return this->instance()->open(addr);
+}
+
+void
+EndPoint::useMD5Security(char const* key) {
+  this->instance()->useMD5Security(key);
+}
+
+int EndPoint::getSocket() const {
+  return m_ptr ? m_ptr->m_fd : ats::NO_FD;
+}
+
+int
+EndPoint::housekeeping() {
+  // Don't force an instance because if there isn't one,
+  // there's no socket either.
+  return m_ptr && ats::NO_FD != m_ptr->m_fd ? m_ptr->housekeeping() : -ENOTCONN;
+}
+
+ats::Rv<int>
+EndPoint::handleMessage() {
+  return m_ptr
+    ? m_ptr->handleMessage()
+    : ats::Rv<int>(-ENOTCONN, log(LVL_INFO, "EndPoint::handleMessage called on unconnected instance"));
+}
+// ------------------------------------------------------
+Cache::Cache() {
+}
+
+Cache::~Cache() {
+}
+
+EndPoint::ImplType*
+Cache::make() {
+  m_ptr.assign(new ImplType);
+  return m_ptr.get();
+}
+
+inline Cache::ImplType*
+Cache::instance() {
+  return static_cast<ImplType*>(this->super::instance());
+}
+
+inline Cache::ImplType* Cache::impl() {
+  return static_cast<ImplType*>(m_ptr.get());
+}
+
+inline Cache::ImplType const* Cache::impl() const {
+  return static_cast<ImplType*>(m_ptr.get());
+}
+
+Cache::Service
+Cache::defineServiceGroup(
+  ServiceGroup const& svc,
+  ServiceGroup::Result* result
+) {
+  return Service(*this, this->instance()->defineServiceGroup(svc, result));
+}
+
+time_t Cache::waitTime() const {
+  return m_ptr ? this->impl()->waitTime() : std::numeric_limits<time_t>::max();
+}
+
+Cache&
+Cache::addSeedRouter(uint8 id, uint32 addr) {
+  this->instance()->seedRouter(id, addr);
+  return *this;
+}
+
+ats::Errata
+Cache::loadServicesFromFile(char const* path) {
+  return this->instance()->loadServicesFromFile(path);
+}
+// ------------------------------------------------------
+Router::Router() {
+}
+
+Router::~Router() {
+}
+
+EndPoint::ImplType*
+Router::make() {
+  m_ptr.assign(new ImplType);
+  return m_ptr.get();
+}
+
+inline Router::ImplType*
+Router::instance() {
+  return static_cast<ImplType*>(this->super::instance());
+}
+
+inline Router::ImplType*
+Router::impl() {
+  return static_cast<ImplType*>(m_ptr.get());
+}
+// ------------------------------------------------------
+} // namespace Wccp