Hi,

This patch adds the possibility to enable a captive portal while connman is in tethering mode by sending its own IP via the DNS proxy (which acts as a DNS server more than a proxy in this case).

I needed this usage for an IoT device, in which the tethering mode is in fact a hotspot mode. With a light http server and patched connman, it offers the possibility to have a light and easy-to-use captive portal for example to diagnostic device.

The captive portal is enabled in settings file, via the key
 Tethering.Captive=true (defaults to false)

It has been used for 1 month on our devices without failures.

Thanks for your reviews,

--
Alexandre Chataignon


>From b3d28e448b43c84b5fb4dd56a6384790ad4e498c Mon Sep 17 00:00:00 2001
From: Alexandre Chataignon <alexandre.chataig...@mobirider.com>
Date: Tue, 28 Jul 2015 12:05:16 +0200
Subject: [PATCH] Added captive portal possibility

---
 src/connman.h    |    4 ++-
 src/dnsproxy.c   |   79 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 src/technology.c |    7 ++++-
 src/tethering.c  |   22 +++++++++++++--
 4 files changed, 108 insertions(+), 4 deletions(-)

diff --git a/src/connman.h b/src/connman.h
index 35eb3f5..7b3265f 100644
--- a/src/connman.h
+++ b/src/connman.h
@@ -619,7 +619,7 @@ int __connman_tethering_init(void);
 void __connman_tethering_cleanup(void);
 
 const char *__connman_tethering_get_bridge(void);
-void __connman_tethering_set_enabled(void);
+void __connman_tethering_set_enabled(bool captive_dns);
 void __connman_tethering_set_disabled(void);
 
 int __connman_private_network_request(DBusMessage *msg, const char *owner);
@@ -930,6 +930,8 @@ int __connman_dnsproxy_add_listener(int index);
 void __connman_dnsproxy_remove_listener(int index);
 int __connman_dnsproxy_append(int index, const char *domain, const char *server);
 int __connman_dnsproxy_remove(int index, const char *domain, const char *server);
+int __connman_dnsproxy_enable_captive(uint32_t ip) ;
+int __connman_dnsproxy_disable_captive(void) ;
 
 int __connman_6to4_probe(struct connman_service *service);
 void __connman_6to4_remove(struct connman_ipconfig *ipconfig);
diff --git a/src/dnsproxy.c b/src/dnsproxy.c
index c37eee9..fff91a8 100644
--- a/src/dnsproxy.c
+++ b/src/dnsproxy.c
@@ -76,6 +76,14 @@ struct domain_hdr {
 #else
 #error "Unknown byte order"
 #endif
+struct a_dns_answer {
+	uint16_t ptr;
+	uint16_t type;
+	uint16_t class;
+	uint32_t ttl;
+	uint16_t rdlength;
+	uint32_t ip;
+} __attribute__ ((packed)) ;
 
 struct partial_reply {
 	uint16_t len;
@@ -216,6 +224,7 @@ static GHashTable *listener_table = NULL;
 static time_t next_refresh;
 static GHashTable *partial_tcp_req_table;
 static guint cache_timer = 0;
+static uint32_t captive_ip = 0;
 
 static guint16 get_id(void)
 {
@@ -502,6 +511,58 @@ static void send_response(int sk, unsigned char *buf, int len,
 	}
 }
 
+static void send_response_A(int sk, unsigned char *req, int len,
+				uint32_t ip,
+				const struct sockaddr *to, socklen_t tolen,
+				int protocol)
+{
+	struct domain_hdr *hdr;
+	struct a_dns_answer* ans;
+	int err, offset = protocol_offset(protocol);
+
+	DBG("sk %d", sk);
+
+	if (offset < 0)
+		return;
+
+	if (len < 12)
+		return;
+	
+	size_t new_len = len+sizeof(struct a_dns_answer) ;
+	char* new_buf ;
+	new_buf = g_malloc(new_len) ;
+	memcpy(new_buf, req, len) ;
+
+	hdr = (void *) (new_buf + offset);
+
+	DBG("id 0x%04x qr %d opcode %d", hdr->id, hdr->qr, hdr->opcode);
+
+	hdr->qr = 1;
+	hdr->rcode = ns_r_noerror;
+
+	hdr->ancount = htons(1);
+	hdr->nscount = 0;
+	hdr->arcount = 0;
+
+	/* Create A answer */
+	/* Other attributes */
+	ans = (void *) (new_buf + len);
+	ans->ptr   = htons(0xc00c) ;/* 11....1100 -> ptr to 12th octet = query */
+	ans->type  = htons(ns_t_a);
+	ans->class = htons(ns_c_in);
+	ans->ttl   = 0;
+	ans->rdlength = htons(4);
+	ans->ip    = htonl(ip);
+
+	err = sendto(sk, new_buf, new_len, MSG_NOSIGNAL, to, tolen);
+	g_free(new_buf) ;
+	if (err < 0) {
+		connman_error("Failed to send DNS response to %d: %s",
+				sk, strerror(errno));
+		return;
+	}
+}
+
 static int get_req_udp_socket(struct request_data *req)
 {
 	GIOChannel *channel;
@@ -3429,6 +3490,14 @@ static bool udp_listener_event(GIOChannel *channel, GIOCondition condition,
 
 	DBG("Received %d bytes (id 0x%04x)", len, buf[0] | buf[1] << 8);
 
+	/* If captive mode, all is redirected to tether ip */
+	if (captive_ip) {
+		send_response_A(sk, buf, len, 
+						 captive_ip,
+						 client_addr, *client_addr_len, IPPROTO_UDP);
+		return true ;
+	}
+
 	err = parse_request(buf, len, query, sizeof(query));
 	if (err < 0 || (g_slist_length(server_list) == 0)) {
 		send_response(sk, buf, len, client_addr,
@@ -3885,3 +3954,13 @@ void __connman_dnsproxy_cleanup(void)
 
 	g_hash_table_destroy(partial_tcp_req_table);
 }
+
+int __connman_dnsproxy_enable_captive(uint32_t ip) {
+	captive_ip = ip ;
+	return 0;
+}
+
+int __connman_dnsproxy_disable_captive(void) {
+	captive_ip = 0 ;
+	return 0;
+}
diff --git a/src/technology.c b/src/technology.c
index 55303a0..c6475b8 100644
--- a/src/technology.c
+++ b/src/technology.c
@@ -66,6 +66,7 @@ struct connman_technology {
 					      */
 	char *tethering_ident;
 	char *tethering_passphrase;
+	bool tethering_captive;
 
 	bool enable_persistent; /* Save the tech state */
 
@@ -224,7 +225,7 @@ void connman_technology_tethering_notify(struct connman_technology *technology,
 	tethering_changed(technology);
 
 	if (enabled)
-		__connman_tethering_set_enabled();
+		__connman_tethering_set_enabled(technology->tethering_captive);
 	else
 		__connman_tethering_set_disabled();
 }
@@ -428,6 +429,10 @@ static void technology_load(struct connman_technology *technology)
 
 	technology->tethering_passphrase = g_key_file_get_string(keyfile,
 				identifier, "Tethering.Passphrase", NULL);
+
+	technology->tethering_captive    = g_key_file_get_boolean(keyfile,
+				identifier, "Tethering.Captive", false);
+
 done:
 	g_free(identifier);
 
diff --git a/src/tethering.c b/src/tethering.c
index ceeec74..9187178 100644
--- a/src/tethering.c
+++ b/src/tethering.c
@@ -56,6 +56,7 @@ static char *private_network_primary_dns = NULL;
 static char *private_network_secondary_dns = NULL;
 
 static volatile int tethering_enabled;
+static volatile bool captive_enabled;
 static GDHCPServer *tethering_dhcp_server = NULL;
 static struct connman_ippool *dhcp_ippool = NULL;
 static DBusConnection *connection;
@@ -178,10 +179,10 @@ static void tethering_restart(struct connman_ippool *pool, void *user_data)
 {
 	DBG("pool %p", pool);
 	__connman_tethering_set_disabled();
-	__connman_tethering_set_enabled();
+	__connman_tethering_set_enabled(captive_enabled);
 }
 
-void __connman_tethering_set_enabled(void)
+void __connman_tethering_set_enabled(bool captive_dns)
 {
 	int index;
 	int err;
@@ -193,6 +194,7 @@ void __connman_tethering_set_enabled(void)
 	const char *dns;
 	unsigned char prefixlen;
 	char **ns;
+	captive_enabled = captive_dns;
 
 	DBG("enabled %d", tethering_enabled + 1);
 
@@ -279,6 +281,19 @@ void __connman_tethering_set_enabled(void)
 		return;
 	}
 
+	if (captive_dns) {
+		err = __connman_dnsproxy_enable_captive(htonl(inet_addr(gateway)));
+		if (err < 0) {
+			connman_error("Cannot enable captive DNS");
+			dhcp_server_stop(tethering_dhcp_server);
+			__connman_bridge_disable(BRIDGE_NAME);
+			__connman_ippool_unref(dhcp_ippool);
+			__connman_bridge_remove(BRIDGE_NAME);
+			__sync_fetch_and_sub(&tethering_enabled, 1);
+			return;
+		}
+	}
+
 	err = __connman_ipv6pd_setup(BRIDGE_NAME);
 	if (err < 0 && err != -EINPROGRESS)
 		DBG("Cannot setup IPv6 prefix delegation %d/%s", err,
@@ -302,6 +317,7 @@ void __connman_tethering_set_disabled(void)
 	__connman_dnsproxy_remove_listener(index);
 
 	__connman_nat_disable(BRIDGE_NAME);
+	__connman_dnsproxy_disable_captive();
 
 	dhcp_server_stop(tethering_dhcp_server);
 
@@ -398,6 +414,7 @@ static void remove_private_network(gpointer user_data)
 	__connman_nat_disable(BRIDGE_NAME);
 	connman_rtnl_remove_watch(pn->iface_watch);
 	__connman_ippool_unref(pn->pool);
+	__connman_dnsproxy_disable_captive();
 
 	if (pn->watch > 0) {
 		g_dbus_remove_watch(connection, pn->watch);
@@ -548,6 +565,7 @@ void __connman_tethering_cleanup(void)
 		__connman_bridge_disable(BRIDGE_NAME);
 		__connman_bridge_remove(BRIDGE_NAME);
 		__connman_nat_disable(BRIDGE_NAME);
+		__connman_dnsproxy_disable_captive();
 	}
 
 	if (!connection)
-- 
1.7.10.4

_______________________________________________
connman mailing list
connman@connman.net
https://lists.connman.net/mailman/listinfo/connman

Reply via email to