.\" 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
.I ifN
]
]
+[
+.B -a
+]
+[
+.B -A
+.I length
+]
+[
+.B -D
+]
+[
+.B -m
+.I append
+|
+.I replace
+|
+.I forward
+|
+.I discard
+]
.I server0
[
.I ...serverN
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
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
To learn more about Vixie
Enterprises, see
.B http://www.vix.com.
-.PP
#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"
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;
} 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 {
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
: 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. */
#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;
}
}
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 ()
{
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;
+}