diff --git a/common/bpf.c b/common/bpf.c index 1816496..91b12d1 100644 --- a/common/bpf.c +++ b/common/bpf.c @@ -198,11 +198,48 @@ struct bpf_insn dhcp_bpf_filter [] = { BPF_STMT(BPF_RET+BPF_K, 0), }; +/* + * For relay port extension + */ +struct bpf_insn dhcp_bpf_relay_filter [] = { + /* Make sure this is an IP packet... */ + BPF_STMT (BPF_LD + BPF_H + BPF_ABS, 12), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 0, 10), + + /* Make sure it's a UDP packet... */ + BPF_STMT (BPF_LD + BPF_B + BPF_ABS, 23), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, IPPROTO_UDP, 0, 8), + + /* Make sure this isn't a fragment... */ + BPF_STMT(BPF_LD + BPF_H + BPF_ABS, 20), + BPF_JUMP(BPF_JMP + BPF_JSET + BPF_K, 0x1fff, 6, 0), + + /* Get the IP header length... */ + BPF_STMT (BPF_LDX + BPF_B + BPF_MSH, 14), + + /* Make sure it's to the right port... */ + BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 2, 0), /* patch */ + + /* relay can have an alternative port... */ + BPF_STMT (BPF_LD + BPF_H + BPF_IND, 16), + BPF_JUMP (BPF_JMP + BPF_JEQ + BPF_K, 67, 0, 1), /* patch */ + + /* If we passed all the tests, ask for the whole packet. */ + BPF_STMT(BPF_RET+BPF_K, (u_int)-1), + + /* Otherwise, drop it. */ + BPF_STMT(BPF_RET+BPF_K, 0), +}; + #if defined (DEC_FDDI) struct bpf_insn *bpf_fddi_filter = NULL; #endif int dhcp_bpf_filter_len = sizeof dhcp_bpf_filter / sizeof (struct bpf_insn); + +int dhcp_bpf_relay_filter_len = sizeof dhcp_bpf_relay_filter / sizeof (struct bpf_insn); + #if defined (HAVE_TR_SUPPORT) struct bpf_insn dhcp_bpf_tr_filter [] = { /* accept all token ring packets due to variable length header */ @@ -306,10 +343,22 @@ void if_register_receive (info) #endif /* DEC_FDDI */ p.bf_insns = dhcp_bpf_filter; + if (relay_port) { + /* + * If user defined relay UDP port, we need to filter + * on both port 67 and user UDP port. + */ + p.bf_len = dhcp_bpf_relay_filter_len; + p.bf_insns = dhcp_bpf_relay_filter; + + dhcp_bpf_relay_filter [10].k = ntohs ((short)relay_port); + } else { + /* Patch the server port into the BPF program... XXX changes to filter program may require changes to the insn number(s) used below! XXX */ - dhcp_bpf_filter [8].k = ntohs (local_port); + dhcp_bpf_filter [8].k = ntohs (local_port); + } if (ioctl (info -> rfdesc, BIOCSETF, &p) < 0) log_fatal ("Can't install packet filter program: %m"); diff --git a/common/discover.c b/common/discover.c index 8e7f632..6c58190 100644 --- a/common/discover.c +++ b/common/discover.c @@ -44,6 +44,7 @@ int interfaces_invalidated; int quiet_interface_discovery; u_int16_t local_port; u_int16_t remote_port; +u_int16_t relay_port = 0; int dhcpv4_over_dhcpv6 = 0; int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *); int (*dhcp_interface_discovery_hook) (struct interface_info *); diff --git a/common/lpf.c b/common/lpf.c index ee3820b..168b475 100644 --- a/common/lpf.c +++ b/common/lpf.c @@ -179,6 +179,9 @@ void if_deregister_send (info) extern struct sock_filter dhcp_bpf_filter []; extern int dhcp_bpf_filter_len; +extern struct sock_filter dhcp_bpf_relay_filter []; +extern int dhcp_bpf_relay_filter_len; + #if defined (HAVE_TR_SUPPORT) extern struct sock_filter dhcp_bpf_tr_filter []; extern int dhcp_bpf_tr_filter_len; @@ -252,13 +255,27 @@ static void lpf_gen_filter_setup (info) /* Set up the bpf filter program structure. This is defined in bpf.c */ - p.len = dhcp_bpf_filter_len; - p.filter = dhcp_bpf_filter; + if (relay_port) { + /* + * If user defined relay UDP port, we need to filter + * on both port 67 and user UDP port. + */ + p.len = dhcp_bpf_relay_filter_len; + p.filter = dhcp_bpf_relay_filter; + } else { + p.len = dhcp_bpf_filter_len; + p.filter = dhcp_bpf_filter; + } /* Patch the server port into the LPF program... XXX changes to filter program may require changes to the insn number(s) used below! XXX */ - dhcp_bpf_filter [8].k = ntohs ((short)local_port); + + if (relay_port) { /* DHCP relay port check */ + dhcp_bpf_relay_filter [10].k = ntohs ((short)relay_port); + } else { + dhcp_bpf_filter [8].k = ntohs ((short)local_port); + } if (setsockopt (info -> rfdesc, SOL_SOCKET, SO_ATTACH_FILTER, &p, sizeof p) < 0) { diff --git a/common/packet.c b/common/packet.c index e600e37..e278edc 100644 --- a/common/packet.c +++ b/common/packet.c @@ -168,6 +168,11 @@ void assemble_udp_ip_header (interface, buf, bufix, /* Fill out the UDP header */ udp.uh_sport = local_port; /* XXX */ udp.uh_dport = port; /* XXX */ + + /* Change to relay port defined if sending to server */ + if (relay_port && (port == htons(67))) { + udp.uh_sport = relay_port; + } udp.uh_ulen = htons(sizeof(udp) + len); memset (&udp.uh_sum, 0, sizeof udp.uh_sum); @@ -297,7 +302,8 @@ decode_udp_ip_header(struct interface_info *interface, return -1; /* Is it to the port we're serving? */ - if (udp.uh_dport != local_port) + if ((udp.uh_dport != local_port) && + ((relay_port == 0) || (udp.uh_dport != relay_port))) return -1; #endif /* USERLAND_FILTER */ diff --git a/common/raw.c b/common/raw.c index a15f8ee..69a6af2 100644 --- a/common/raw.c +++ b/common/raw.c @@ -55,14 +55,14 @@ void if_register_send (info) /* Set up the address we're going to connect to. */ name.sin_family = AF_INET; - name.sin_port = local_port; + name.sin_port = (relay_port) ? relay_port: local_port; name.sin_addr.s_addr = htonl (INADDR_BROADCAST); memset (name.sin_zero, 0, sizeof (name.sin_zero)); /* List addresses on which we're listening. */ if (!quiet_interface_discovery) log_info ("Sending on %s, port %d", - piaddr (info -> address), htons (local_port)); + piaddr (info -> address), htons (name.sin_port)); if ((sock = socket (AF_INET, SOCK_RAW, IPPROTO_RAW)) < 0) log_fatal ("Can't create dhcp socket: %m"); diff --git a/common/socket.c b/common/socket.c index e8851b4..3ab4391 100644 --- a/common/socket.c +++ b/common/socket.c @@ -177,7 +177,7 @@ if_register_socket(struct interface_info *info, int family, default: addr = (struct sockaddr_in *)&name; addr->sin_family = AF_INET; - addr->sin_port = local_port; + addr->sin_port = (relay_port) ? relay_port : local_port; memcpy(&addr->sin_addr, &local_address, sizeof(addr->sin_addr)); diff --git a/includes/dhcp.h b/includes/dhcp.h index f916468..60f4d96 100644 --- a/includes/dhcp.h +++ b/includes/dhcp.h @@ -184,6 +184,7 @@ struct dhcp_packet { #define RAI_REMOTE_ID 2 #define RAI_AGENT_ID 3 #define RAI_LINK_SELECT 5 +#define RAI_RELAY_PORT 19 /* FQDN suboptions: */ #define FQDN_NO_CLIENT_UPDATE 1 diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 261714d..38fd61a 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -2788,6 +2788,7 @@ extern struct in_addr local_address; extern u_int16_t local_port; extern u_int16_t remote_port; +extern u_int16_t relay_port; extern int dhcpv4_over_dhcpv6; extern int (*dhcp_interface_setup_hook) (struct interface_info *, struct iaddr *); diff --git a/relay/dhcrelay.c b/relay/dhcrelay.c index 344cee7..11c7696 100644 --- a/relay/dhcrelay.c +++ b/relay/dhcrelay.c @@ -151,7 +151,8 @@ char *progname; #ifdef DHCPv6 #define DHCRELAY_USAGE \ "Usage: %s [-4] [-d] [-q] [-a] [-D]\n"\ -" [-A ] [-c ] [-p ]\n" \ +" [-A ] [-c ]\n" \ +" [-p | -rp \n" \ " [-pf ] [--no-pid]\n"\ " [-m append|replace|forward|discard]\n" \ " [-i interface0 [ ... -i interfaceN]\n" \ @@ -168,7 +169,8 @@ char *progname; " upper (server link): [address%%]interface" #else #define DHCRELAY_USAGE \ -"Usage: %s [-d] [-q] [-a] [-D] [-A ] [-c ] [-p ]\n" \ +"Usage: %s [-d] [-q] [-a] [-D] [-A ] [-c ]\n" \ +" [-p | -rp \n" \ " [-pf ] [--no-pid]\n" \ " [-m append|replace|forward|discard]\n" \ " [-i interface0 [ ... -i interfaceN]\n" \ @@ -194,6 +196,8 @@ char *progname; * \return Nothing */ static const char use_noarg[] = "No argument for command: %s"; +static const char use_port_defined[] = "Port already set, %s inappropriate"; +static const char lpf_sock_support[] = "Only LPF and BPF are supported: %s"; #ifdef DHCPv6 static const char use_badproto[] = "Protocol already set, %s inappropriate"; static const char use_v4command[] = "Command not used for DHCPv6: %s"; @@ -226,6 +230,7 @@ main(int argc, char **argv) { int no_daemon = 0, quiet = 0; int fd; int i; + int port_defined = 0; #ifdef DHCPv6 struct stream_list *sl = NULL; int local_family_set = 0; @@ -295,9 +300,22 @@ main(int argc, char **argv) { } else if (!strcmp(argv[i], "-p")) { if (++i == argc) usage(use_noarg, argv[i-1]); + if (port_defined) + usage(use_port_defined, argv[i-1]); local_port = validate_port(argv[i]); log_debug("binding to user-specified port %d", ntohs(local_port)); + port_defined = ISC_TRUE; + } else if (!strcmp(argv[i], "-rp")) { + if (++i == argc) + usage(use_noarg, argv[i-1]); + if (port_defined) + usage(use_port_defined, argv[i-1]); + relay_port = validate_port(argv[i]); + log_debug("binding to user-defined relay port %d", + ntohs(relay_port)); + add_agent_options = 1; + port_defined = ISC_TRUE; } else if (!strcmp(argv[i], "-c")) { int hcount; if (++i == argc) @@ -529,6 +547,11 @@ main(int argc, char **argv) { } } +#if !defined (USE_BPF_RECEIVE) && !defined (USE_LPF_RECEIVE) + if (relay_port && (local_family == AF_INET)) + usage(lpf_sock_support, "-rp"); +# endif + /* * If the user didn't specify a pid file directly * find one from environment variables or defaults @@ -1212,6 +1235,10 @@ add_relay_agent_options(struct interface_info *ip, struct dhcp_packet *packet, optlen += 6; } + if (relay_port) { + optlen += 2; + } + /* We do not support relay option fragmenting(multiple options to * support an option data exceeding 255 bytes). */ @@ -1256,6 +1283,16 @@ add_relay_agent_options(struct interface_info *ip, struct dhcp_packet *packet, log_debug ("Adding link selection suboption" " with addr: %s", inet_ntoa(giaddr)); } + + /* + * Draft-ietf-dhc-relay-port, the relay operation select + * to use a different relay UDP source port when sending + * and receiving to/from DHCP server. + */ + if (relay_port) { + *sp++ = RAI_RELAY_PORT; + *sp++ = 0u; + } } else { ++agent_option_errors; log_error("No room in packet (used %d of %d) "