diff --git a/common/discover.c b/common/discover.c index 8e7f632..4c58e12 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 *); @@ -935,6 +936,8 @@ discover_interfaces(int state) { int ir; isc_result_t status; int wifcount = 0; + int up_intf = 0; + int down_intf = 0; static int setup_fallback = 0; @@ -1300,9 +1303,38 @@ discover_interfaces(int state) { switch (local_family) { #ifdef DHCPv6 case AF_INET6: + if (relay_port) { + /* + * The normal IPv6 relay only needs one + * socket as long as we find an interface. + * When user relay port is defined, and we + * have two different UDP ports. One to + * receive from DHCP client with port 547, + * and the other is user defined for sending + * to the server or upstream relay agent. + * Thus we need to register sockets for one + * upstream and one downstream interfaces. + */ + if (up_intf && + ((tmp->flags & INTERFACE_STREAMS) == + INTERFACE_UPSTREAM)) { + continue; + } + if (down_intf && + ((tmp->flags & INTERFACE_STREAMS) == + INTERFACE_DOWNSTREAM)) { + continue; + } + } status = omapi_register_io_object((omapi_object_t *)tmp, if_readsocket, 0, got_one_v6, 0, 0); + if ((tmp->flags & INTERFACE_STREAMS) == + INTERFACE_UPSTREAM) { + up_intf++; + } else { + down_intf++; + } break; #endif /* DHCPv6 */ case AF_INET: @@ -1325,7 +1357,8 @@ discover_interfaces(int state) { * we're well beyond that point in terms of mess. */ if (((state == DISCOVER_SERVER) || (state == DISCOVER_RELAY)) && - (local_family == AF_INET6)) + (local_family == AF_INET6) && + ((relay_port == 0) || (up_intf && down_intf))) break; #endif } /* for (tmp = interfaces; ... */ diff --git a/common/socket.c b/common/socket.c index e8851b4..edc4b0d 100644 --- a/common/socket.c +++ b/common/socket.c @@ -64,6 +64,8 @@ static int no_global_v6_socket = 0; static unsigned int global_v6_socket_references = 0; static int global_v6_socket = -1; +static unsigned int relay_port_v6_socket_references = 0; +static int relay_port_v6_socket = -1; static void if_register_multicast(struct interface_info *info); #endif @@ -155,7 +157,12 @@ if_register_socket(struct interface_info *info, int family, case AF_INET6: addr6 = (struct sockaddr_in6 *)&name; addr6->sin6_family = AF_INET6; - addr6->sin6_port = local_port; + if (relay_port && + ((info->flags & INTERFACE_STREAMS) == INTERFACE_UPSTREAM)) { + addr6->sin6_port = relay_port; + } else { + addr6->sin6_port = local_port; + } if (linklocal6) { memcpy(&addr6->sin6_addr, linklocal6, @@ -484,23 +491,51 @@ if_register6(struct interface_info *info, int do_multicast) { log_fatal("Impossible condition at %s:%d", MDL); } - if (global_v6_socket_references == 0) { - global_v6_socket = if_register_socket(info, AF_INET6, - &req_multi, NULL); - if (global_v6_socket < 0) { - /* - * if_register_socket() fatally logs if it fails to - * create a socket, this is just a sanity check. - */ - log_fatal("Impossible condition at %s:%d", MDL); - } else { - log_info("Bound to *:%d", ntohs(local_port)); + if (!relay_port || + ((info->flags & INTERFACE_STREAMS) == INTERFACE_DOWNSTREAM)) { + if (global_v6_socket_references == 0) { + global_v6_socket = + if_register_socket(info, AF_INET6, + &req_multi, NULL); + if (global_v6_socket < 0) { + /* + * if_register_socket() fatally logs if it + * fails to create a socket, this is just a + * sanity check. + */ + log_fatal("Impossible condition at %s:%d", + MDL); + } else { + log_info("Bound to *:%d", ntohs(local_port)); + } } + + info->rfdesc = global_v6_socket; + info->wfdesc = global_v6_socket; + global_v6_socket_references++; + } else { + /* + * If relay port is defined, we need to register one + * IPv6 UPD socket to handle upstream server or relay agent + * with a non-547 UDP local port. + */ + if ((relay_port_v6_socket_references == 0) && + ((info->flags & INTERFACE_STREAMS) == INTERFACE_UPSTREAM)) { + relay_port_v6_socket = + if_register_socket(info, AF_INET6, + &req_multi, NULL); + if (relay_port_v6_socket < 0) { + log_fatal("Impossible condition at %s:%d", + MDL); + } else { + log_info("Bound to relay port *:%d", + ntohs(relay_port)); + } + } + info->rfdesc = relay_port_v6_socket; + info->wfdesc = relay_port_v6_socket; + relay_port_v6_socket_references++; } - - info->rfdesc = global_v6_socket; - info->wfdesc = global_v6_socket; - global_v6_socket_references++; if (req_multi) if_register_multicast(info); @@ -592,6 +627,14 @@ if_deregister6(struct interface_info *info) { global_v6_socket_references--; info->rfdesc = -1; info->wfdesc = -1; + } else if (relay_port && + (info->rfdesc == relay_port_v6_socket) && + (info->wfdesc == relay_port_v6_socket) && + (relay_port_v6_socket_references > 0)) { + /* Dereference the global v6 socket. */ + relay_port_v6_socket_references--; + info->rfdesc = -1; + info->wfdesc = -1; } else { log_fatal("Impossible condition at %s:%d", MDL); } @@ -608,12 +651,20 @@ if_deregister6(struct interface_info *info) { } } - if (!no_global_v6_socket && - (global_v6_socket_references == 0)) { - close(global_v6_socket); - global_v6_socket = -1; + if (!no_global_v6_socket) { + if ((global_v6_socket_references == 0)) { + close(global_v6_socket); + global_v6_socket = -1; - log_info("Unbound from *:%d", ntohs(local_port)); + log_info("Unbound from *:%d", ntohs(local_port)); + } + if (relay_port && (relay_port_v6_socket_references == 0)) { + close(relay_port_v6_socket); + relay_port_v6_socket = -1; + + log_info("Unbound from relay port *:%d", + ntohs(relay_port)); + } } } #endif /* DHCPv6 */ diff --git a/common/tables.c b/common/tables.c index 96dc6a3..1cdd035 100644 --- a/common/tables.c +++ b/common/tables.c @@ -570,6 +570,8 @@ static struct option dhcpv6_options[] = { { "dhcp4-o-dhcp6-server", "6A", &dhcpv6_universe, 88, 1 }, #endif + { "ipv6-relay-port", "S", &dhcpv6_universe, 90, 1 }, + { NULL, NULL, NULL, 0, 0 } }; diff --git a/includes/dhcp6.h b/includes/dhcp6.h index bf8205c..e2258f1 100644 --- a/includes/dhcp6.h +++ b/includes/dhcp6.h @@ -115,6 +115,7 @@ #define D6O_V6_PCP_SERVER 86 /* RFC7291 */ #define D6O_DHCPV4_MSG 87 /* RFC7341 */ #define D6O_DHCP4_O_DHCP6_SERVER 88 /* RFC7341 */ +#define D6O_RELAY_PORT 90 /* XXX draft relay-port */ /* * Status Codes, from RFC 3315 section 24.4, and RFC 3633, 5007, 5460. diff --git a/includes/dhcpd.h b/includes/dhcpd.h index 261714d..05a5c12 100644 --- a/includes/dhcpd.h +++ b/includes/dhcpd.h @@ -472,6 +472,9 @@ struct packet { /* Propogates server value SV_ECHO_CLIENT_ID so it is available * in cons_options() */ int sv_echo_client_id; + + /* Relay port check */ + isc_boolean_t relay_source_port; }; /* @@ -2788,6 +2791,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..4224a73 100644 --- a/relay/dhcrelay.c +++ b/relay/dhcrelay.c @@ -159,7 +159,8 @@ char *progname; " [-id interface0 [ ... -id interfaceN]\n" \ " [-U interface]\n" \ " server0 [ ... serverN]\n\n" \ -" %s -6 [-d] [-q] [-I] [-c ] [-p ]\n" \ +" %s -6 [-d] [-q] [-I] [-c ]\n" \ +" [-p | -rp \n" \ " [-pf ] [--no-pid]\n" \ " [-s ]\n" \ " -l lower0 [ ... -l lowerN]\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,7 @@ 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"; #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 +229,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 +299,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) @@ -1501,6 +1518,7 @@ setup_streams(void) { static const int required_forw_opts[] = { D6O_INTERFACE_ID, D6O_SUBSCRIBER_ID, + D6O_RELAY_PORT, D6O_RELAY_MSG, 0 }; @@ -1515,6 +1533,7 @@ process_up6(struct packet *packet, struct stream_list *dp) { struct dhcpv6_relay_packet *relay; struct option_state *opts; struct stream_list *up; + u_int16_t relay_client_port = 0; /* Check if the message should be relayed to the server. */ switch (packet->dhcpv6_msg_type) { @@ -1575,6 +1594,10 @@ process_up6(struct packet *packet, struct stream_list *dp) { } memset(&relay->link_address, 0, 16); } + + if (packet->client_port != 547) { + relay_client_port = packet->client_port; + } } else { relay->hop_count = 0; if (!dp) @@ -1626,6 +1649,23 @@ process_up6(struct packet *packet, struct stream_list *dp) { } } + /* + * If we use a non-547 UDP source port or if we have received + * from a downstream relay agent uses a non-547 port, we need to + * include the RELAY-PORT option. The "Downstream UDP Port" field + * value in the option allow us to send relay-reply message back + * to the downstream relay agent with the correct UDP source port. + */ + if (relay_port || relay_client_port) { + if (!save_option_buffer(&dhcpv6_universe, opts, NULL, + (unsigned char *) &relay_client_port, + sizeof(u_int16_t), + D6O_RELAY_PORT, 0)) { + log_error("Can't save relay-port."); + option_state_dereference(&opts, MDL); + return; + } + } /* Add the relay-msg carrying the packet. */ if (!save_option_buffer(&dhcpv6_universe, opts, @@ -1752,6 +1792,34 @@ process_down6(struct packet *packet) { /* Relay-Reply of for another relay, not a client. */ case DHCPV6_RELAY_REPL: to.sin6_port = local_port; + + oc = lookup_option(&dhcpv6_universe, packet->options, + D6O_RELAY_PORT); + if (oc != NULL) { + u_int16_t down_relay_port; + + memset(&if_id, 0, sizeof(if_id)); + if (!evaluate_option_cache(&if_id, packet, NULL, + NULL, packet->options, NULL, + &global_scope, oc, MDL) || + (if_id.len != sizeof(u_int16_t))) { + log_info("Can't evaluate down relay-port."); + goto cleanup; + } + memcpy(&down_relay_port, if_id.data, + sizeof(u_int16_t)); + /* + * If the down_relay_port value is non-zero, that + * means our downstream relay agent uses a non-547 + * UDP source port sending relay-forw message to + * us. We need to use the same UDP port sending + * reply back. + */ + if (down_relay_port) { + to.sin6_port = down_relay_port; + } + } + /* Fall into: */ case DHCPV6_ADVERTISE: diff --git a/server/dhcpv6.c b/server/dhcpv6.c index 18d8bb8..4b8a41c 100644 --- a/server/dhcpv6.c +++ b/server/dhcpv6.c @@ -844,6 +844,7 @@ static const int required_opts_solicit[] = { }; static const int required_opts_agent[] = { D6O_INTERFACE_ID, + D6O_RELAY_PORT, D6O_RELAY_MSG, 0 }; @@ -6545,6 +6546,33 @@ dhcpv6_relay_forw(struct data_string *reply_ret, struct packet *packet) { } /* + * Append the relay_port option if present. + */ + oc = lookup_option(&dhcpv6_universe, packet->options, + D6O_RELAY_PORT); + if (oc != NULL) { + if (!evaluate_option_cache(&a_opt, packet, + NULL, NULL, + packet->options, NULL, + &global_scope, oc, MDL)) { + log_error("dhcpv6_relay_forw: error evaluating " + "Relay Port."); + goto exit; + } + if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, + (unsigned char *)a_opt.data, + a_opt.len, + D6O_RELAY_PORT, 0)) { + log_error("dhcpv6_relay_forw: error saving " + "Relay Port."); + goto exit; + } + data_string_forget(&a_opt, MDL); + + packet->relay_source_port = ISC_TRUE; + } + + /* * Append our encapsulated stuff for caller. */ if (!save_option_buffer(&dhcpv6_universe, opt_state, NULL, @@ -7379,6 +7407,10 @@ dhcpv6(struct packet *packet) { */ to_addr.sin6_port = packet->client_port; #endif + /* Check relay source port */ + if (packet->relay_source_port && packet->client_port != 547) { + to_addr.sin6_port = packet->client_port; + } memcpy(&to_addr.sin6_addr, packet->client_addr.iabuf, sizeof(to_addr.sin6_addr));