From 42e7cc0388b258ae0f391e7b0dbab9b53b067124 Mon Sep 17 00:00:00 2001
From: John Crispin <john@phrozen.org>
Date: Tue, 8 Jun 2021 09:12:38 +0200
Subject: [PATCH 12/43] dnsmasq: add relay / option82 support

Signed-off-by: John Crispin <john@phrozen.org>
---
 .../services/dnsmasq/files/dnsmasq.init       |  10 +-
 .../dnsmasq/patches/200-option82.patch        | 195 ++++++++++++++++++
 2 files changed, 201 insertions(+), 4 deletions(-)
 create mode 100644 package/network/services/dnsmasq/patches/200-option82.patch

diff --git a/package/network/services/dnsmasq/files/dnsmasq.init b/package/network/services/dnsmasq/files/dnsmasq.init
index 205bfb4cf6..dacd476cd4 100644
--- a/package/network/services/dnsmasq/files/dnsmasq.init
+++ b/package/network/services/dnsmasq/files/dnsmasq.init
@@ -777,12 +777,14 @@ dhcp_relay_add() {
 	[ -n "$server_addr" ] || return 0
 
 	config_get interface "$cfg" interface
-	if [ -z "$interface" ]; then
-		xappend "--dhcp-relay=$local_addr,$server_addr"
-	else
+	config_get suboption1 "$cfg" suboption1
+	config_get suboption2 "$cfg" suboption2
+	config_get suboption151 "$cfg" suboption151
+	config_get suboption152 "$cfg" suboption152
+	if [ -n "$interface" ]; then
 		network_get_device ifname "$interface" || return
-		xappend "--dhcp-relay=$local_addr,$server_addr,$ifname"
 	fi
+	xappend "--dhcp-relay=$local_addr,$server_addr,$ifname,$suboption1,$suboption2,$suboption151,$suboption152"
 }
 
 dnsmasq_ipset_add() {
diff --git a/package/network/services/dnsmasq/patches/200-option82.patch b/package/network/services/dnsmasq/patches/200-option82.patch
new file mode 100644
index 0000000000..7883b12922
--- /dev/null
+++ b/package/network/services/dnsmasq/patches/200-option82.patch
@@ -0,0 +1,195 @@
+diff --git a/src/dhcp.c b/src/dhcp.c
+index 97324f2..48e08b1 100644
+--- a/src/dhcp.c
++++ b/src/dhcp.c
+@@ -1057,14 +1057,83 @@ char *host_from_dns(struct in_addr addr)
+   return NULL;
+ }
+ 
++extern void insert_option82(struct dhcp_packet *mess, size_t *sz, char *opt, int len);
++
++static char *insert_suboption(char *opt, char id, char *string, struct ifreq *ifr, u8 *client)
++{
++	char *len, *o = opt;
++
++	if (!string || !*string)
++		return opt;
++
++	*o++ = id;
++	len = o++;
++
++	while (*string) {
++		if (*string == '%') {
++			string++;
++			switch(*string) {
++			case 'i':
++				memcpy(o, ifr->ifr_name, strlen(ifr->ifr_name));
++				o += strlen(ifr->ifr_name);
++				break;
++			case 'a':
++				memcpy(o, ifr->ifr_hwaddr.sa_data, 6);
++				o += 6;
++				break;
++			case 'A':
++				sprintf(o, "%02X:%02X:%02X:%02X:%02X:%02X",
++					ifr->ifr_hwaddr.sa_data[0],
++					ifr->ifr_hwaddr.sa_data[1],
++					ifr->ifr_hwaddr.sa_data[2],
++					ifr->ifr_hwaddr.sa_data[3],
++					ifr->ifr_hwaddr.sa_data[4],
++					ifr->ifr_hwaddr.sa_data[5]);
++				o += 17;
++				break;
++			case 'c':
++				memcpy(o, client, 6);
++				o += 6;
++				break;
++			case 'C':
++				sprintf(o, "%02X:%02X:%02X:%02X:%02X:%02X",
++					client[0],
++					client[1],
++					client[2],
++					client[3],
++					client[4],
++					client[5]);
++				o += 17;
++				break;
++			}
++		} else {
++			*o++ = *string;
++		}
++		string++;
++	}
++
++	*len = o - opt - 2;
++
++	return o;
++}
++
+ static int  relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess, size_t sz, int iface_index)
+ {
+   /* ->local is same value for all relays on ->current chain */
++  struct ifreq ifr;
+   union all_addr from;
+-  
++  char opt82[512] = { 82, 12 };
++  char *o = &opt82[2];
++  int len;
++
+   if (mess->op != BOOTREQUEST)
+     return 0;
+ 
++  if (!if_indextoname(iface_index, ifr.ifr_name))
++	  return -1;
++  if (ioctl(daemon->dhcpfd, SIOCGIFHWADDR, &ifr))
++	  return -1;
++
+   /* source address == relay address */
+   from.addr4 = relay->local.addr4;
+   
+@@ -1080,10 +1149,22 @@ static int  relay_upstream4(struct dhcp_relay *relay, struct dhcp_packet *mess,
+       /* plug in our address */
+       mess->giaddr.s_addr = relay->local.addr4.s_addr;
+     }
++  
++  o = insert_suboption(o, 1, relay->sub_opt1, &ifr, mess->chaddr);
++  o = insert_suboption(o, 2, relay->sub_opt2, &ifr, mess->chaddr);
++  o = insert_suboption(o, 151, relay->sub_opt151, &ifr, mess->chaddr);
++  o = insert_suboption(o, 152, relay->sub_opt152, &ifr, mess->chaddr);
++
++  len = o - opt82;
++  opt82[1] = len - 2;
++
++  insert_option82(mess, &sz, opt82, len);
+ 
+   if ((mess->hops++) > 20)
+     return 1;
+ 
++
++
+   for (; relay; relay = relay->current)
+     {
+       union mysockaddr to;
+diff --git a/src/dnsmasq.h b/src/dnsmasq.h
+index e770454..abc79f8 100644
+--- a/src/dnsmasq.h
++++ b/src/dnsmasq.h
+@@ -1005,6 +1005,10 @@ struct dhcp_relay {
+   char *interface; /* Allowable interface for replies from server, and dest for IPv6 multicast */
+   int iface_index; /* working - interface in which requests arrived, for return */
+   struct dhcp_relay *current, *next;
++  char sub_opt1[64];
++  char sub_opt2[64];
++  char sub_opt151[16];
++  char sub_opt152[16];
+ };
+ 
+ extern struct daemon {
+diff --git a/src/option.c b/src/option.c
+index 0a72406..b06d1c8 100644
+--- a/src/option.c
++++ b/src/option.c
+@@ -3957,10 +3957,25 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
+     case LOPT_RELAY: /* --dhcp-relay */
+       {
+ 	struct dhcp_relay *new = opt_malloc(sizeof(struct dhcp_relay));
+-	comma = split(arg);
+-	new->interface = opt_string_alloc(split(comma));
++	char *args[7] = {}, **arg_p = args;
++
++	while (arg) {
++		comma = split(arg);
++		*arg_p++ = arg;
++		arg = comma;
++	}
++
++	if (args[3] && *args[3])
++		memcpy(new->sub_opt1, args[3], strlen(args[3]) + 1);
++	if (args[4] && *args[4])
++		memcpy(new->sub_opt2, args[4], strlen(args[4]) + 1);
++	if (args[5] && *args[5])
++		memcpy(new->sub_opt151, args[5], strlen(args[5]) + 1);
++	if (args[6] && *args[6])
++		memcpy(new->sub_opt152, args[6], strlen(args[6]) + 1);
++	new->interface = opt_string_alloc(args[2]);
+ 	new->iface_index = 0;
+-	if (inet_pton(AF_INET, arg, &new->local) && inet_pton(AF_INET, comma, &new->server))
++	if (inet_pton(AF_INET, args[0], &new->local) && inet_pton(AF_INET, args[1], &new->server))
+ 	  {
+ 	    new->next = daemon->relay4;
+ 	    daemon->relay4 = new;
+@@ -3977,7 +3992,7 @@ static int one_opt(int option, char *arg, char *errstr, char *gen_err, int comma
+ 	    free(new->interface);
+ 	    ret_err_free(_("Bad dhcp-relay"), new);
+ 	  }
+-	
++
+ 	break;
+       }
+ 
+diff --git a/src/rfc2131.c b/src/rfc2131.c
+index 3f50755..792a274 100644
+--- a/src/rfc2131.c
++++ b/src/rfc2131.c
+@@ -2785,6 +2785,21 @@ static void apply_delay(u32 xid, time_t recvtime, struct dhcp_netid *netid)
+     }
+ }
+ 
++void insert_option82(struct dhcp_packet *mess, size_t *sz, char *opt82, int len)
++{
++  if (!option_find(mess, *sz, OPTION_AGENT_ID, 2)) {
++	unsigned char *end = option_find(mess, *sz, OPTION_END, 1);
++	int space = 312 - (end - mess->options) - 1;
++
++	if (space < len)
++		return;
++	memcpy(end, opt82, len);
++	end[len] = OPTION_END;
++	*sz += len;
++  }
++}
++
++
+ #endif
+   
+ 
-- 
2.25.1

