Allow passing in a mark to use for listen sockets.

The mark is applied to the DNS and DHCP listen sockets.  Client
sockets used to talk to DNS forwarders were already marked.
Both bound and wildcard sockets are marked, even though we only
use bound sockets.

The mark is passed in at startup instead of on a per-interface
basis because there is only one DHCP socket and it is created
before any interfaces are added.

Bug: 37778642
Test: "adb shell ss -tneiopan" shows expected mark
Change-Id: I3b99aa5b77963745715d52065b2119f54df17e8b
diff --git a/src/dhcp.c b/src/dhcp.c
index 335fc04..16a22ce 100755
--- a/src/dhcp.c
+++ b/src/dhcp.c
@@ -76,6 +76,11 @@
   if (bind(fd, (struct sockaddr *)&saddr, sizeof(struct sockaddr_in)))
     die(_("failed to bind DHCP server socket: %s"), NULL, EC_BADNET);
 
+#ifdef __ANDROID__
+  if (setsockopt(fd, SOL_SOCKET, SO_MARK, &daemon->listen_mark, sizeof(daemon->listen_mark)) == -1)
+    die(_("failed to set DHCP socket mark: %s"), NULL, EC_BADNET);
+#endif /* __ANDROID__ */
+
   daemon->dhcpfd = fd;
 
 #if defined(HAVE_BSD_NETWORK)
diff --git a/src/dnsmasq.h b/src/dnsmasq.h
index 2887546..10b8521 100755
--- a/src/dnsmasq.h
+++ b/src/dnsmasq.h
@@ -644,6 +644,7 @@
   struct doctor *doctors;
   unsigned short edns_pktsz;
   char *tftp_prefix; 
+  uint32_t listen_mark;
 
   /* globally used stuff for DNS */
   char *packet; /* packet buffer */
diff --git a/src/network.c b/src/network.c
index f2c3073..1588288 100755
--- a/src/network.c
+++ b/src/network.c
@@ -369,7 +369,24 @@
 #endif 
 	  bind(fd, (struct sockaddr *)&addr, sa_len(&addr)) == -1)
 	return NULL;
+
+#ifdef __ANDROID__
+      uint32_t mark = daemon->listen_mark;
+      if (mark != 0 &&
+	  (setsockopt(fd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) == -1 ||
+	   setsockopt(tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) == -1 ||
+	   setsockopt(l6->fd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) == -1 ||
+	   setsockopt(l6->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) == -1))
+      {
+	my_syslog(LOG_WARNING, _("setsockopt(SO_MARK, 0x%x: %s"), mark, strerror(errno));
+	close(fd);
+	close(tcpfd);
+	close(l6->fd);
+	close(l6->tcpfd);
+	return NULL;
+      }
     }
+#endif /* __ANDROID__ */
   
 #ifdef HAVE_TFTP
   if (daemon->options & OPT_TFTP)
@@ -476,6 +493,12 @@
       die(_("failed to bind listening socket for %s: %s"), daemon->namebuff, EC_BADNET);
     }
 
+    uint32_t mark = daemon->listen_mark;
+    if (mark != 0 &&
+	(setsockopt(new->fd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) == -1 ||
+	 setsockopt(new->tcpfd, SOL_SOCKET, SO_MARK, &mark, sizeof(mark)) == -1))
+      die(_("failed to set SO_MARK on listen socket: %s"), NULL, EC_BADNET);
+
     if (listen(new->tcpfd, 5) == -1)
       die(_("failed to listen on socket: %s"), NULL, EC_BADNET);
   }
diff --git a/src/option.c b/src/option.c
index 9e7ac2f..8d5a39e 100755
--- a/src/option.c
+++ b/src/option.c
@@ -102,6 +102,7 @@
 #define LOPT_PXE_PROMT 291
 #define LOPT_PXE_SERV  292
 #define LOPT_TEST      293
+#define LOPT_LISTNMARK 294
 
 #ifdef HAVE_GETOPT_LONG
 static const struct option opts[] =  
@@ -208,6 +209,9 @@
     { "cname", 1, 0, LOPT_CNAME },
     { "pxe-prompt", 1, 0, LOPT_PXE_PROMT },
     { "pxe-service", 1, 0, LOPT_PXE_SERV },
+#ifdef __ANDROID__
+    { "listen-mark", 1, 0, LOPT_LISTNMARK },
+#endif /* __ANDROID__ */
     { "test", 0, 0, LOPT_TEST },
     { NULL, 0, 0, 0 }
   };
@@ -322,6 +326,7 @@
   { LOPT_CNAME, ARG_DUP, "<alias>,<target>", gettext_noop("Specify alias name for LOCAL DNS name."), NULL },
   { LOPT_PXE_PROMT, ARG_DUP, "<prompt>,[<timeout>]", gettext_noop("Prompt to send to PXE clients."), NULL },
   { LOPT_PXE_SERV, ARG_DUP, "<service>", gettext_noop("Boot service for PXE menu."), NULL },
+  { LOPT_LISTNMARK, ARG_ONE, NULL, gettext_noop("Socket mark to use for listen sockets."), NULL },
   { LOPT_TEST, 0, NULL, gettext_noop("Check configuration syntax."), NULL },
   { 0, 0, NULL, NULL, NULL }
 }; 
@@ -2497,6 +2502,19 @@
 	break;
       }
       
+    case LOPT_LISTNMARK: /* --listen-mark */
+      {
+	char *endptr;
+	uint32_t mark = strtoul(arg, &endptr, 0);
+my_syslog(LOG_WARNING, "passed-in mark: %s", arg);
+	if (!*endptr)
+	  daemon->listen_mark = mark;
+	else
+	  problem = _("invalid mark");
+my_syslog(LOG_WARNING, "daemon->listen_mark: 0x%x, *endptr=%d", daemon->listen_mark, *endptr);
+	break;
+      }
+
     default:
       return _("unsupported option (check that dnsmasq was compiled with DHCP/TFTP/DBus support)");