]> git.ipfire.org Git - thirdparty/dhcp.git/commitdiff
Add Relay Agent Information Option support.
authorTed Lemon <source@isc.org>
Fri, 6 Nov 1998 00:15:17 +0000 (00:15 +0000)
committerTed Lemon <source@isc.org>
Fri, 6 Nov 1998 00:15:17 +0000 (00:15 +0000)
relay/dhcrelay.8
relay/dhcrelay.c

index c2d53533d6eed6671fe8cf9f796ad3d2386825c8..91b2f69980e0b4521471e6d62fabed1b95b5aa03 100644 (file)
@@ -1,6 +1,6 @@
 .\"    dhcrelay.8
 .\"
-.\" Copyright (c) 1997 The Internet Software Consortium.
+.\" Copyright (c) 1997, 1998 The Internet Software Consortium.
 .\" All rights reserved.
 .\"
 .\" Redistribution and use in source and binary forms, with or without
@@ -59,6 +59,26 @@ dhcrelay - Dynamic Host Configuration Protocol Relay Agent
 .I ifN
 ]
 ]
+[
+.B -a
+]
+[
+.B -A
+.I length
+]
+[
+.B -D
+]
+[
+.B -m
+.I append
+|
+.I replace
+|
+.I forward
+|
+.I discard
+]
 .I server0
 [
 .I ...serverN
@@ -70,33 +90,40 @@ no DHCP server is directly to one or more DHCP servers on other
 subnets.
 .SH OPERATION
 .PP
-The DHCP Relay Agent listens for DHCP requests on all interfaces
-attached to a host, unless one or more interfaces are specified on the
-command line with the
-.I -i
-flag.
-.PP
-When a query is received, dhcrelay forwards it to the list of DHCP
-servers specified on the command line.   When a reply is received, it
-is broadcast or unicast on the network from whence the original
-request came.
-.PP
-It is possible to specify a set of interfaces on which dhcrelay will
-listen, so that if dhcrelay is connected through one interface to a
-network on which there is no DHCP server, but is connected on another
-interface to a network on which there is a DHCP server, it will not
-relay DHCP and BOOTP requests from the network on which the server
-exists to that server.   This is an imperfect solution.
+The DHCP Relay Agent listens for DHCP and BOOTP queries and responses.
+When a query is received from a client, dhcrelay forwards it to the
+list of DHCP servers specified on the command line.  When a reply is
+received from a server, it is broadcast or unicast (according to the
+relay agent's ability or the client's request) on the network from
+which the original request came.
 .SH COMMAND LINE
 .PP
 The names of the network interfaces that dhcrelay should attempt to
 configure may be specified on the command line using the
-.I -i
+.B -i
 option.  If no interface names
 are specified on the command line dhcrelay will identify all network
 interfaces, elimininating non-broadcast interfaces if possible, and
 attempt to configure each interface.
 .PP
+If a relay agent is running on a system that is connected to one or
+more networks on which no DHCP servers are present, and is also
+connected to one or more networks on which DHCP servers
+.I are
+connected, it is may not be helpful for the relay agent to relay
+requests from those networks on which a DHCP server already exists.
+To avoid such a situation, the interfaces on which the relay agent
+should listen should be specified with the
+.B -i
+flag.
+.PP
+Note that in some cases it
+.I is
+helpful for the relay agent to forward requests from networks on which
+a DHCP server is running to other DHCP servers.   This would be the
+case if two DHCP servers on different networks were being used to
+provide backup service for each other's networks.
+.PP
 If dhcrelay should listen and transmit on a port other than the
 standard (port 67), the
 .B -p
@@ -113,14 +140,101 @@ a debugger, or when running it out of inittab on System V systems.
 Dhcrelay will normally print its network configuration on startup.
 This can be annoying in a system startup script - to disable this
 behaviour, specify the
-.I -q
+.B -q
 flag.
+.SH RELAY AGENT INFORMATION OPTIONS
+If the
+.B -a
+flag is set the relay agent will append an agent option field to each
+request before forwarding it to the server.   Agent option fields in
+responses sent from servers to clients will be stripped before
+forwarding such responses back to the client.
+.PP
+The agent option field will contain two agent options: the Circuit ID
+suboption and the Agent ID suboption.  Currently, the Circuit ID will
+be the printable name of the interface on which the client request was
+received.   The Agent ID will be the value that the relay agent stores
+in the DHCP packet's giaddr field.   The client supports inclusion of
+a Remote ID suboption as well, but this is not used by default.
+.PP
+.I Note:
+The Agent ID suboption is not defined in the current Relay Agent
+Information Option draft (draft-ietf-dhc-agent-options-03.txt), but
+has been proposed for inclusion in the next draft.
+.PP
+Relay Agent options are added to a DHCP packet without the knowledge
+of the DHCP client.   The client may have filled the DHCP packet
+option buffer completely, in which case there theoretically isn't any
+space to add Agent options.   However, the DHCP server may be able to
+handle a much larger packet than most DHCP clients would send.   The
+current Agent Options draft requires that the relay agent use a
+maximum packet size of 576 bytes.   
 .PP
-The name of at least one DHCP server to which DHCP and BOOTP requests
-should be relayed must be specified on the command line.
+It is recommended that with the Internet Software Consortium DHCP
+server, the maximum packet size be set to about 1400, allowing plenty
+of extra space in which the relay agent can put the agent option
+field, while still fitting into the Ethernet MTU size.  This can be
+done by specifying the
+.B -A
+flag, followed by the desired maximum packet size (e.g., 1400).
 .PP
+Note that this is reasonably safe to do even if the MTU between the
+server and the client is less than 1500, as long as the hosts on which
+the server and client are running support IP fragmentation (and they
+should).  With some knowledge as to how large the agent options might
+get in a particular configuration, this parameter can be tuned as
+finely as necessary.
+.PP
+It is possible for a relay agent to receive a packet which already
+contains an agent option field.  If this packet does not have a giaddr
+set, the standard requires that the packet be discarded.
+.PP
+If giaddr is set, the server may handle the situation in one of four
+ways: it may
+.I append
+its own set of relay options to the packet, leaving the
+supplied option field intact.   It may
+.I replace
+the existing agent option field.
+It may
+.I forward
+the packet unchanged.   Or, it may
+.I discard
+it.
+.PP
+Which of these behaviours is followed by the Internet Software
+Consortium DHCP Relay Agent may be configured with the
+.B -m
+flag, followed by one of the four keywords specified in
+.I italics
+above.
+.PP
+When the relay agent receives a reply from a server that it's supposed
+to forward to a client, and Relay Agent Information option processing
+is enabled, the relay agent scans the packet for Relay Agent
+Information options and removes them.   As it's scanning, if it finds
+a Relay Agent Information option field containing an Agent ID
+suboption that matches one of its IP addresses, that option is
+recognized as its own.   If no such option is found, the relay agent
+can either drop the packet, or relay it anyway.   If the
+.B -D
+option is specified, all packets that don't contain a match will be
+dropped.
+.SH SPECIFYING DHCP SERVERS
+The name or IP address of at least one DHCP server to which DHCP and
+BOOTP requests should be relayed must be specified on the command
+line.
 .SH SEE ALSO
-dhclient(8), dhcpd(8), RFC2132, RFC2131.
+dhclient(8), dhcpd(8), RFC2132, RFC2131, draft-ietf-dhc-agent-options-03.txt.
+.SH BUGS
+It should be possible for the user to define the Circuit ID and Remote
+ID values on a per-interface basis.
+.PP
+The relay agent should not relay packets received on a physical
+network to DHCP servers on the same physical network - if they do, the
+server will receive duplicate packets.   In order to fix this,
+however, the relay agent needs to be able to learn about the network
+topology, which requires that it have a configuration file.
 .SH AUTHOR
 .B dhcrelay(8)
 has been written for the Internet Software Consortium
@@ -131,4 +245,3 @@ see
 To learn more about Vixie
 Enterprises, see
 .B http://www.vix.com.
-.PP
index e1ab0a176c2ed0fa1f321bf1ced9143d39f0857f..3ffb959b41bf217919b2cf63ce0b09b7df85f9e0 100644 (file)
@@ -42,7 +42,7 @@
 
 #ifndef lint
 static char copyright[] =
-"$Id: dhcrelay.c,v 1.15 1998/11/05 18:51:16 mellon Exp $ Copyright (c) 1997 The Internet Software Consortium.  All rights reserved.\n";
+"$Id: dhcrelay.c,v 1.16 1998/11/06 00:15:13 mellon Exp $ Copyright (c) 1997 The Internet Software Consortium.  All rights reserved.\n";
 #endif /* not lint */
 
 #include "dhcpd.h"
@@ -64,12 +64,45 @@ char *tlname;
 
 char *path_dhcrelay_pid = _PATH_DHCRELAY_PID;
 
+int bogus_agent_drops = 0;     /* Packets dropped because agent option
+                                  field was specified and we're not relaying
+                                  packets that already have an agent option
+                                  specified. */
+int bogus_giaddr_drops = 0;    /* Packets sent to us to relay back to a
+                                  client, but with a bogus giaddr. */
+int client_packets_relayed = 0;        /* Packets relayed from client to server. */
+int server_packet_errors = 0;  /* Errors sending packets to servers. */
+int server_packets_relayed = 0;        /* Packets relayed from server to client. */
+int client_packet_errors = 0;  /* Errors sending packets to clients. */
+
+int add_agent_options = 0;     /* If nonzero, add relay agent options. */
+int drop_agent_mismatches = 0; /* If nonzero, drop server replies that
+                                  don't contain a Relay Agent Information
+                                  option whose Agent ID suboption matches
+                                  our giaddr. */
+int corrupt_agent_options = 0; /* Number of packets dropped because
+                                  relay agent information option was bad. */
+int missing_agent_option = 0;  /* Number of packets dropped because no
+                                  RAI option matching our ID was found. */
+int bad_circuit_id = 0;                /* Circuit ID option in matching RAI option
+                                  did not match any known circuit ID. */
+int missing_circuit_id = 0;    /* Circuit ID option in matching RAI option
+                                  was missing. */
+
+       /* Maximum size of a packet with agent options added. */
+int dhcp_max_agent_option_packet_length = 576;
+
+       /* What to do about packets we're asked to relay that
+          already have a relay option: */
+enum { forward_and_append,     /* Forward and append our own relay option. */
+       forward_and_replace,    /* Forward, but replace theirs with ours. */
+       forward_untouched,      /* Forward without changes. */
+       discard } agent_relay_mode = forward_and_replace;
+
 #ifdef USE_FALLBACK
 struct interface_info fallback_interface;
 #endif
 
-int dhcp_max_agent_option_packet_length = 0;
-
 u_int16_t local_port;
 u_int16_t remote_port;
 int log_priority;
@@ -127,6 +160,27 @@ int main (argc, argv, envp)
                } else if (!strcmp (argv [i], "-q")) {
                        quiet = 1;
                        quiet_interface_discovery = 1;
+               } else if (!strcmp (argv [i], "-a")) {
+                       add_agent_options = 1;
+               } else if (!strcmp (argv [i], "-A")) {
+                       if (++i == argc)
+                               usage ();
+                       dhcp_max_agent_option_packet_length = atoi (argv [i]);
+               } else if (!strcmp (argv [i], "-m")) {
+                       if (++i == argc)
+                               usage ();
+                       if (!strcasecmp (argv [i], "append")) {
+                               agent_relay_mode = forward_and_append;
+                       } else if (!strcasecmp (argv [i], "replace")) {
+                               agent_relay_mode = forward_and_replace;
+                       } else if (!strcasecmp (argv [i], "forward")) {
+                               agent_relay_mode = forward_untouched;
+                       } else if (!strcasecmp (argv [i], "discard")) {
+                               agent_relay_mode = discard;
+                       } else
+                               usage ();
+               } else if (!strcmp (argv [i], "-D")) {
+                       drop_agent_mismatches = 1;
                } else if (argv [i][0] == '-') {
                    usage ();
                } else {
@@ -249,6 +303,24 @@ void relay (ip, packet, length, from_port, from, hfrom)
                return;
        }
 
+       /* XXX Dave:
+          If you're using the circuit ID to figure out where to
+          send the reply, you can delete the following code,
+          but you still need to validate the giaddr and drop the
+          packet if it's bogus. */
+       /* Find the interface that corresponds to the giaddr
+          in the packet. */
+       if (packet -> giaddr.s_addr) {
+               for (out = interfaces; out; out = out -> next) {
+                       if (!memcmp (&out -> primary_address,
+                                    &packet -> giaddr,
+                                    sizeof packet -> giaddr))
+                               break;
+               }
+       } else {
+               out = (struct interface_info *)0;
+       }
+
        /* If it's a bootreply, forward it to the client. */
        if (packet -> op == BOOTREPLY) {
 #ifdef USE_FALLBACK
@@ -272,46 +344,56 @@ void relay (ip, packet, length, from_port, from, hfrom)
                         : packet -> hlen));
                hto.htype = packet -> htype;
 
-               /* Find the interface that corresponds to the giaddr
-                  in the packet. */
-               for (out = interfaces; out; out = out -> next) {
-                       if (!memcmp (&out -> primary_address,
-                                    &packet -> giaddr,
-                                    sizeof packet -> giaddr))
-                               break;
-               }
+               /* Wipe out the agent relay options and, if possible, figure
+                  out which interface to use based on the contents of the
+                  option that we put on the request to which the server is
+                  replying. */
+               if (!(length =
+                     strip_relay_agent_options (ip, &out, packet, length)))
+                       return;
+
                if (!out) {
                        warn ("packet to bogus giaddr %s.\n",
                              inet_ntoa (packet -> giaddr));
+                       ++bogus_giaddr_drops;
                        return;
                }
 
                if (send_packet (out,
                                 (struct packet *)0,
                                 packet, length, out -> primary_address,
-                                &to, &hto) < 0)
+                                &to, &hto) < 0) {
                        debug ("sendpkt: %m");
-               else
+                       ++server_packet_errors;
+               } else {
                        debug ("forwarded BOOTREPLY for %s to %s",
                               print_hw_addr (packet -> htype, packet -> hlen,
                                              packet -> chaddr),
                               inet_ntoa (to.sin_addr));
 
+                       ++server_packets_relayed;
+               }
                return;
        }
 
-       /* If giaddr is set on a BOOTREQUEST, ignore it - it's already
-          been gatewayed. */
-       if (packet -> giaddr.s_addr) {
-               note ("ignoring BOOTREQUEST with giaddr of %s\n",
-                     inet_ntoa (packet -> giaddr));
+       /* If giaddr matches one of our addresses, ignore the packet -
+          we just sent it. */
+       if (out)
+               return;
+
+       /* Add relay agent options if indicated.   If something goes wrong,
+          drop the packet. */
+       if (!(length = add_relay_agent_options (ip, packet, length,
+                                               ip -> primary_address)))
                return;
-       }
 
-       /* Set the giaddr so the server can figure out what net it's
-          from and so that we can later forward the response to the
-          correct net. */
-       packet -> giaddr = ip -> primary_address;
+       /* If giaddr is not already set, Set it so the server can
+          figure out what net it's from and so that we can later
+          forward the response to the correct net.    If it's already
+          set, the response will be sent directly to the relay agent
+          that set giaddr, so we won't see it. */
+       if (!packet -> giaddr.s_addr)
+               packet -> giaddr = ip -> primary_address;
 
        /* Otherwise, it's a BOOTREQUEST, so forward it to all the
           servers. */
@@ -330,11 +412,13 @@ void relay (ip, packet, length, from_port, from, hfrom)
 #endif
                    < 0) {
                        debug ("send_packet: %m");
+                       ++client_packet_errors;
                } else {
                        debug ("forwarded BOOTREQUEST for %s to %s",
                               print_hw_addr (packet -> htype, packet -> hlen,
                                              packet -> chaddr),
                               inet_ntoa (sp -> to.sin_addr));
+                       ++client_packets_relayed;
                }
        }
                                 
@@ -342,7 +426,10 @@ void relay (ip, packet, length, from_port, from, hfrom)
 
 static void usage ()
 {
-       error ("Usage: dhcrelay [-c] [-p <port>] [server1 [... serverN]]");
+       error ("Usage: dhcrelay [-p <port>] [-d] [-D] [-i interface]\n%s%s%s",
+              "                ",
+              "[-q] [-a] [-A length] [-m append|replace|forward|discard]\n",
+              "                [server1 [... serverN]]");
 }
 
 void cleanup ()
@@ -375,3 +462,337 @@ struct subnet *find_subnet (addr)
 {
        return (struct subnet *)0;
 }
+
+/* Strip any Relay Agent Information options from the DHCP packet
+   option buffer.   If an RAI option is found whose Agent ID matches
+   the giaddr (i.e., ours), try to look up the outgoing interface
+   based on the circuit ID suboption. */
+
+int strip_relay_agent_options (in, out, packet, length)
+       struct interface_info *in, **out;
+       struct dhcp_packet *packet;
+       int length;
+{
+       int is_dhcp = 0;
+       u_int8_t *op, *sp, *max;
+       int good_agent_option = 0;
+       int status;
+
+       /* If we're not adding agent options to packets, we're not taking
+          them out either. */
+       if (!add_agent_options)
+               return length;
+
+       /* If there's no cookie, it's a bootp packet, so we should just
+          forward it unchanged. */
+       if (memcmp (packet -> options, DHCP_OPTIONS_COOKIE, 4))
+               return length;
+
+       max = ((u_int8_t *)packet) + length;
+       sp = op = &packet -> options [4];
+
+       while (op < max) {
+               switch (*op) {
+                       /* Skip padding... */
+                     case DHO_PAD:
+                       if (sp != op)
+                               *sp = *op;
+                       ++op;
+                       ++sp;
+                       continue;
+
+                       /* If we see a message type, it's a DHCP packet. */
+                     case DHO_DHCP_MESSAGE_TYPE:
+                       is_dhcp = 1;
+                       goto skip;
+                       break;
+
+                       /* Quit immediately if we hit an End option. */
+                     case DHO_END:
+                       if (sp != op)
+                               *sp++ = *op++;
+                       goto out;
+
+                     case DHO_DHCP_AGENT_OPTIONS:
+                       /* We shouldn't see a relay agent option in a
+                          packet before we've seen the DHCP packet type,
+                          but if we do, we have to leave it alone. */
+                       if (!is_dhcp)
+                               goto skip;
+
+                       status = find_interface_by_agent_option (packet,
+                                                                out, op + 2,
+                                                                op [1]);
+                       if (status == -1 && drop_agent_mismatches)
+                               return 0;
+                       if (status)
+                               good_agent_option = 1;
+                       op += op [1] + 2;
+                       break;
+
+                     skip:
+                       /* Skip over other options. */
+                     default:
+                       if (sp != op)
+                               memcpy (sp, op, op [1] + 2);
+                       sp += op [1] + 2;
+                       op += op [1] + 2;
+                       break;
+               }
+       }
+      out:
+
+       /* If it's not a DHCP packet, we're not supposed to touch it. */
+       if (!is_dhcp)
+               return length;
+
+       /* If none of the agent options we found matched, or if we didn't
+          find any agent options, count this packet as not having any
+          matching agent options, and if we're relying on agent options
+          to determine the outgoing interface, drop the packet. */
+
+       if (!good_agent_option) {
+               ++missing_agent_option;
+               if (drop_agent_mismatches)
+                       return 0;
+       }
+
+       /* Adjust the length... */
+       if (sp != op) {
+               length = sp - ((u_int8_t *)packet);
+
+               /* Make sure the packet isn't short (this is unlikely,
+                   but WTH) */
+               if (length < BOOTP_MIN_LEN) {
+                       memset (sp, 0, BOOTP_MIN_LEN - length);
+                       length = BOOTP_MIN_LEN;
+               }
+       }
+       return length;
+}
+
+
+/* Find an interface that matches the circuit ID specified in the
+   Relay Agent Information option.   If one is found, store it through
+   the pointer given; otherwise, leave the existing pointer alone.
+
+   We actually deviate somewhat from the current specification here:
+   if the option buffer is corrupt, we suggest that the caller not
+   respond to this packet.  If the circuit ID doesn't match any known
+   interface, we suggest that the caller to drop the packet.  Only if
+   we find a circuit ID that matches an existing interface do we tell
+   the caller to go ahead and process the packet. */
+
+int find_interface_by_agent_option (packet, out, buf, len)
+       struct dhcp_packet *packet;
+       struct interface_info **out;
+       u_int8_t *buf;
+       int len;
+{
+       int i;
+       u_int8_t *circuit_id = 0;
+       int circuit_id_len;
+       struct interface_info *ip;
+
+       while (i < len) {
+               /* If the next agent option overflows the end of the
+                  packet, the agent option buffer is corrupt. */
+               if (i + 1 == len ||
+                   i + buf [i + 1] + 2 > len) {
+                       ++corrupt_agent_options;
+                       return -1;
+               }
+               switch (buf [i]) {
+                       /* Remember where the circuit ID is... */
+                     case RAI_CIRCUIT_ID:
+                       circuit_id = &buf [i + 2];
+                       circuit_id_len = buf [i + 1];
+                       i += circuit_id_len + 2;
+                       continue;
+
+                     default:
+                       i += buf [i + 1] + 2;
+                       break;
+               }
+       }
+
+       /* If there's no circuit ID, it's not really ours, tell the caller
+          it's no good. */
+       if (!circuit_id) {
+               ++missing_circuit_id;
+               return -1;
+       }
+
+       /* Scan the interface list looking for an interface whose
+          name matches the one specified in circuit_id. */
+
+       for (ip = interfaces; ip; ip = ip -> next) {
+               if (ip -> circuit_id &&
+                   ip -> circuit_id_len == circuit_id_len &&
+                   !memcmp (ip -> circuit_id, circuit_id, circuit_id_len))
+                       break;
+       }
+
+       /* If we got a match, use it. */
+       if (ip) {
+               *out = ip;
+               return 1;
+       }
+
+       /* If we didn't get a match, the circuit ID was bogus. */
+       ++bad_circuit_id;
+       return -1;
+}
+
+/* Examine a packet to see if it's a candidate to have a Relay
+   Agent Information option tacked onto its tail.   If it is, tack
+   the option on.  */
+
+int add_relay_agent_options (ip, packet, length, giaddr)
+       struct interface_info *ip;
+       struct dhcp_packet *packet;
+       struct in_addr giaddr;
+{
+       int is_dhcp = 0, agent_options_present = 0;
+       u_int8_t *op, *sp, *max, *end_pad = 0;
+
+       /* If we're not adding agent options to packets, we can skip
+          this. */
+       if (!add_agent_options)
+               return length;
+
+       /* If there's no cookie, it's a bootp packet, so we should just
+          forward it unchanged. */
+       if (memcmp (packet -> options, DHCP_OPTIONS_COOKIE, 4))
+               return length;
+
+       max = ((u_int8_t *)packet) + length;
+       sp = op = &packet -> options [4];
+
+       while (op < max) {
+               switch (*op) {
+                       /* Skip padding... */
+                     case DHO_PAD:
+                       end_pad = sp;
+                       if (sp != op)
+                               *sp = *op;
+                       ++op;
+                       ++sp;
+                       continue;
+
+                       /* If we see a message type, it's a DHCP packet. */
+                     case DHO_DHCP_MESSAGE_TYPE:
+                       is_dhcp = 1;
+                       goto skip;
+                       break;
+
+                       /* Quit immediately if we hit an End option. */
+                     case DHO_END:
+                       goto out;
+
+                     case DHO_DHCP_AGENT_OPTIONS:
+                       /* We shouldn't see a relay agent option in a
+                          packet before we've seen the DHCP packet type,
+                          but if we do, we have to leave it alone. */
+                       if (!is_dhcp)
+                               goto skip;
+                       end_pad = 0;
+
+                       /* There's already a Relay Agent Information option
+                          in this packet.   How embarrassing.   Decide what
+                          to do based on the mode the user specified. */
+
+                       switch (agent_relay_mode) {
+                             case forward_and_append:
+                               goto skip;
+                             case forward_untouched:
+                               return length;
+                             case discard:
+                               return 0;
+                             case forward_and_replace:
+                             default:
+                               break;
+                       }
+
+                       /* Skip over the agent option and start copying
+                          if we aren't copying already. */
+                       op += op [1] + 2;
+                       break;
+
+                     skip:
+                       /* Skip over other options. */
+                     default:
+                       end_pad = 0;
+                       if (sp != op)
+                               memcpy (sp, op, op [1] + 2);
+                       sp += op [1] + 2;
+                       op += op [1] + 2;
+                       break;
+               }
+       }
+      out:
+
+       /* If it's not a DHCP packet, we're not supposed to touch it. */
+       if (!is_dhcp)
+               return length;
+
+       /* If the packet was padded out, we can store the agent option
+          at the beginning of the padding. */
+
+       if (end_pad)
+               sp = end_pad;
+
+       /* Remember where the end of the packet was after parsing
+          it. */
+       op = sp;
+
+       /* XXX Is there room? */
+
+       /* Okay, cons up *our* Relay Agent Information option. */
+       *sp++ = DHO_DHCP_AGENT_OPTIONS;
+       *sp++ = 0;      /* Dunno... */
+
+       /* Copy in the circuit id... */
+       *sp++ = RAI_CIRCUIT_ID;
+       /* Sanity check.   Had better not every happen. */
+       if (ip -> circuit_id_len > 255 || ip -> circuit_id_len < 1)
+               error ("completely bogus circuit id length %d on %s\n",
+                      ip -> circuit_id_len, ip -> name);
+       *sp++ = ip -> circuit_id_len;
+       memcpy (sp, ip -> circuit_id, ip -> circuit_id_len);
+       sp += ip -> circuit_id_len;
+
+       /* Copy in remote ID... */
+       if (ip -> remote_id) {
+               *sp++ = RAI_REMOTE_ID;
+               if (ip -> remote_id_len > 255 || ip -> remote_id_len < 1)
+                       error ("completely bogus remote id length %d on %s\n",
+                              ip -> circuit_id_len, ip -> name);
+               *sp++ = ip -> remote_id_len;
+               memcpy (sp, ip -> remote_id, ip -> remote_id_len);
+               sp += ip -> remote_id_len;
+       }
+
+       /* Relay option's total length shouldn't ever get to be more than
+          257 bytes. */
+       if (sp - op > 257)
+               error ("total agent option length exceeds 257 (%d) on %s\n",
+                      sp - op, ip -> name);
+
+       /* Calculate length of RAI option. */
+       op [1] = sp - op - 2;
+
+       /* Deposit an END token. */
+       *sp++ = DHO_END;
+
+       /* Recalculate total packet length. */
+       length = sp - ((u_int8_t *)packet);
+
+       /* Make sure the packet isn't short (this is unlikely, but WTH) */
+       if (length < BOOTP_MIN_LEN) {
+               memset (sp, 0, BOOTP_MIN_LEN - length);
+               length = BOOTP_MIN_LEN;
+       }
+
+       return length;
+}