wireguard: Regular reresolve endpoint address
[platform/upstream/connman.git] / src / peer.c
old mode 100644 (file)
new mode 100755 (executable)
index ce3b582..2102f11
 #endif
 
 #include <errno.h>
+#include <ctype.h>
 #include <gdbus.h>
+#include <gdhcp/gdhcp.h>
+#include <netinet/if_ether.h>
+
+#include <connman/agent.h>
 
 #include "connman.h"
 
@@ -32,21 +37,333 @@ static DBusConnection *connection = NULL;
 
 static GHashTable *peers_table = NULL;
 
+static struct connman_peer_driver *peer_driver;
+
+struct _peers_notify {
+       int id;
+       GHashTable *add;
+       GHashTable *remove;
+} *peers_notify;
+
+struct _peer_service {
+       enum connman_peer_service_type type;
+       unsigned char *data;
+       int length;
+};
+
 struct connman_peer {
+       int refcount;
+       struct connman_device *device;
+       struct connman_device *sub_device;
+       unsigned char *iface_address[ETH_ALEN];
        char *identifier;
        char *name;
        char *path;
+       enum connman_peer_state state;
+       struct connman_ipconfig *ipconfig;
+       DBusMessage *pending;
+       bool registered;
+       bool connection_master;
+       struct connman_ippool *ip_pool;
+       GDHCPServer *dhcp_server;
+       uint32_t lease_ip;
+       GSList *services;
 };
 
+static void settings_changed(struct connman_peer *peer);
+
+static void stop_dhcp_server(struct connman_peer *peer)
+{
+       DBG("");
+
+       if (peer->dhcp_server)
+               g_dhcp_server_unref(peer->dhcp_server);
+
+       peer->dhcp_server = NULL;
+
+       if (peer->ip_pool)
+               __connman_ippool_free(peer->ip_pool);
+       peer->ip_pool = NULL;
+       peer->lease_ip = 0;
+}
+
+static void dhcp_server_debug(const char *str, void *data)
+{
+       connman_info("%s: %s\n", (const char *) data, str);
+}
+
+static void lease_added(unsigned char *mac, uint32_t ip)
+{
+       GList *list, *start;
+
+       start = list = g_hash_table_get_values(peers_table);
+       for (; list; list = list->next) {
+               struct connman_peer *temp = list->data;
+
+               if (!memcmp(temp->iface_address, mac, ETH_ALEN)) {
+                       temp->lease_ip = ip;
+                       settings_changed(temp);
+                       break;
+               }
+       }
+
+       g_list_free(start);
+}
+
+static gboolean dhcp_server_started(gpointer data)
+{
+       struct connman_peer *peer = data;
+
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_READY);
+       connman_peer_unref(peer);
+
+       return FALSE;
+}
+
+static int start_dhcp_server(struct connman_peer *peer)
+{
+       const char *start_ip, *end_ip;
+       GDHCPServerError dhcp_error;
+       const char *broadcast;
+       const char *gateway;
+       const char *subnet;
+       int prefixlen;
+       int index;
+       int err;
+
+       DBG("");
+
+       err = -ENOMEM;
+
+       if (peer->sub_device)
+               index = connman_device_get_index(peer->sub_device);
+       else
+               index = connman_device_get_index(peer->device);
+
+       peer->ip_pool = __connman_ippool_create(index, 2, 1, NULL, NULL);
+       if (!peer->ip_pool)
+               goto error;
+
+       gateway = __connman_ippool_get_gateway(peer->ip_pool);
+       subnet = __connman_ippool_get_subnet_mask(peer->ip_pool);
+       broadcast = __connman_ippool_get_broadcast(peer->ip_pool);
+       start_ip = __connman_ippool_get_start_ip(peer->ip_pool);
+       end_ip = __connman_ippool_get_end_ip(peer->ip_pool);
+
+       prefixlen = connman_ipaddress_calc_netmask_len(subnet);
+
+       err = __connman_inet_modify_address(RTM_NEWADDR,
+                               NLM_F_REPLACE | NLM_F_ACK, index, AF_INET,
+                               gateway, NULL, prefixlen, broadcast);
+       if (err < 0)
+               goto error;
+
+       peer->dhcp_server = g_dhcp_server_new(G_DHCP_IPV4, index, &dhcp_error);
+       if (!peer->dhcp_server)
+               goto error;
+
+       g_dhcp_server_set_debug(peer->dhcp_server,
+                                       dhcp_server_debug, "Peer DHCP server");
+       g_dhcp_server_set_lease_time(peer->dhcp_server, 3600);
+       g_dhcp_server_set_option(peer->dhcp_server, G_DHCP_SUBNET, subnet);
+       g_dhcp_server_set_option(peer->dhcp_server, G_DHCP_ROUTER, gateway);
+       g_dhcp_server_set_option(peer->dhcp_server, G_DHCP_DNS_SERVER, NULL);
+       g_dhcp_server_set_ip_range(peer->dhcp_server, start_ip, end_ip);
+
+       g_dhcp_server_set_lease_added_cb(peer->dhcp_server, lease_added);
+
+       err = g_dhcp_server_start(peer->dhcp_server);
+       if (err < 0)
+               goto error;
+
+       g_idle_add(dhcp_server_started, connman_peer_ref(peer));
+
+       return 0;
+
+error:
+       stop_dhcp_server(peer);
+       return err;
+}
+
+static void reply_pending(struct connman_peer *peer, int error)
+{
+       if (!peer->pending)
+               return;
+
+       connman_dbus_reply_pending(peer->pending, error, NULL);
+       peer->pending = NULL;
+}
+
 static void peer_free(gpointer data)
 {
        struct connman_peer *peer = data;
-       connman_peer_destroy(peer);
+
+       reply_pending(peer, ENOENT);
+
+       connman_peer_unregister(peer);
+
+       if (peer->path) {
+               g_free(peer->path);
+               peer->path = NULL;
+       }
+
+       if (peer->ipconfig) {
+               __connman_ipconfig_set_ops(peer->ipconfig, NULL);
+               __connman_ipconfig_set_data(peer->ipconfig, NULL);
+               __connman_ipconfig_unref(peer->ipconfig);
+               peer->ipconfig = NULL;
+       }
+
+       stop_dhcp_server(peer);
+
+       if (peer->device) {
+               connman_device_unref(peer->device);
+               peer->device = NULL;
+       }
+
+       if (peer->services)
+               connman_peer_reset_services(peer);
+
+       g_free(peer->identifier);
+       g_free(peer->name);
+
+       g_free(peer);
+}
+
+static const char *state2string(enum connman_peer_state state)
+{
+       switch (state) {
+       case CONNMAN_PEER_STATE_UNKNOWN:
+               break;
+       case CONNMAN_PEER_STATE_IDLE:
+               return "idle";
+       case CONNMAN_PEER_STATE_ASSOCIATION:
+               return "association";
+       case CONNMAN_PEER_STATE_CONFIGURATION:
+               return "configuration";
+       case CONNMAN_PEER_STATE_READY:
+               return "ready";
+       case CONNMAN_PEER_STATE_DISCONNECT:
+               return "disconnect";
+       case CONNMAN_PEER_STATE_FAILURE:
+               return "failure";
+       }
+
+       return NULL;
+}
+
+static bool is_connecting(struct connman_peer *peer)
+{
+       if (peer->state == CONNMAN_PEER_STATE_ASSOCIATION ||
+                       peer->state == CONNMAN_PEER_STATE_CONFIGURATION ||
+                       peer->pending)
+               return true;
+
+       return false;
+}
+
+static bool is_connected(struct connman_peer *peer)
+{
+       if (peer->state == CONNMAN_PEER_STATE_READY)
+               return true;
+
+       return false;
+}
+
+static bool allow_property_changed(struct connman_peer *peer)
+{
+       if (g_hash_table_lookup_extended(peers_notify->add, peer->path,
+                                                               NULL, NULL))
+               return false;
+
+       return true;
+}
+
+static void append_ipv4(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_peer *peer = user_data;
+       char trans[INET_ADDRSTRLEN+1] = {};
+       const char *local = "";
+       const char *remote = "";
+       char *dhcp = NULL;
+
+       if (!is_connected(peer))
+               return;
+
+       if (peer->connection_master) {
+               struct in_addr addr;
+
+               addr.s_addr = peer->lease_ip;
+               inet_ntop(AF_INET, &addr, trans, INET_ADDRSTRLEN);
+
+               local = __connman_ippool_get_gateway(peer->ip_pool);
+               remote = trans;
+       } else if (peer->ipconfig) {
+               local = __connman_ipconfig_get_local(peer->ipconfig);
+
+               remote = __connman_ipconfig_get_gateway(peer->ipconfig);
+               if (!remote) {
+                       remote = dhcp = __connman_dhcp_get_server_address(
+                                                       peer->ipconfig);
+                       if (!dhcp)
+                               remote = "";
+               }
+       }
+
+       connman_dbus_dict_append_basic(iter, "Local",
+                                               DBUS_TYPE_STRING, &local);
+       connman_dbus_dict_append_basic(iter, "Remote",
+                                               DBUS_TYPE_STRING, &remote);
+       if (dhcp)
+               g_free(dhcp);
+}
+
+static void append_peer_service(DBusMessageIter *iter,
+                                       struct _peer_service *service)
+{
+       DBusMessageIter dict;
+
+       connman_dbus_dict_open(iter, &dict);
+
+       switch (service->type) {
+       case CONNMAN_PEER_SERVICE_UNKNOWN:
+               /* Should never happen */
+               break;
+       case CONNMAN_PEER_SERVICE_WIFI_DISPLAY:
+               connman_dbus_dict_append_fixed_array(&dict,
+                               "WiFiDisplayIEs", DBUS_TYPE_BYTE,
+                               &service->data, service->length);
+               break;
+       }
+
+       connman_dbus_dict_close(iter, &dict);
+}
+
+static void append_peer_services(DBusMessageIter *iter, void *user_data)
+{
+       struct connman_peer *peer = user_data;
+       DBusMessageIter container;
+       GSList *list;
+
+       dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT,
+                                                       NULL, &container);
+
+       if (!peer->services) {
+               DBusMessageIter dict;
+
+               connman_dbus_dict_open(&container, &dict);
+               connman_dbus_dict_close(&container, &dict);
+       } else {
+               for (list = peer->services; list; list = list->next)
+                       append_peer_service(&container, list->data);
+       }
+
+       dbus_message_iter_close_container(iter, &container);
 }
 
 static void append_properties(DBusMessageIter *iter, struct connman_peer *peer)
 {
-       const char *state = "disconnected";
+       const char *state = state2string(peer->state);
        DBusMessageIter dict;
 
        connman_dbus_dict_open(iter, &dict);
@@ -55,11 +372,23 @@ static void append_properties(DBusMessageIter *iter, struct connman_peer *peer)
                                        DBUS_TYPE_STRING, &state);
        connman_dbus_dict_append_basic(&dict, "Name",
                                        DBUS_TYPE_STRING, &peer->name);
-       connman_dbus_dict_append_dict(&dict, "IPv4", NULL, NULL);
-
+       connman_dbus_dict_append_dict(&dict, "IPv4", append_ipv4, peer);
+       connman_dbus_dict_append_array(&dict, "Services",
+                                       DBUS_TYPE_DICT_ENTRY,
+                                       append_peer_services, peer);
        connman_dbus_dict_close(iter, &dict);
 }
 
+static void settings_changed(struct connman_peer *peer)
+{
+       if (!allow_property_changed(peer))
+               return;
+
+       connman_dbus_property_changed_dict(peer->path,
+                                       CONNMAN_PEER_INTERFACE, "IPv4",
+                                       append_ipv4, peer);
+}
+
 static DBusMessage *get_peer_properties(DBusConnection *conn,
                                                DBusMessage *msg, void *data)
 {
@@ -92,31 +421,44 @@ static void append_peer_struct(gpointer key, gpointer value,
        dbus_message_iter_close_container(array, &entry);
 }
 
-struct _peers_notify {
-       int id;
-       GHashTable *add;
-       GHashTable *remove;
-} *peers_notify;
+static void state_changed(struct connman_peer *peer)
+{
+       const char *state;
+
+       state = state2string(peer->state);
+       if (!state || !allow_property_changed(peer))
+               return;
+
+       connman_dbus_property_changed_basic(peer->path,
+                                        CONNMAN_PEER_INTERFACE, "State",
+                                        DBUS_TYPE_STRING, &state);
+}
 
 static void append_existing_and_new_peers(gpointer key,
                                        gpointer value, gpointer user_data)
 {
        struct connman_peer *peer = value;
        DBusMessageIter *iter = user_data;
-       DBusMessageIter entry;
+       DBusMessageIter entry, dict;
+
+       if (!peer || !peer->registered)
+               return;
 
        if (g_hash_table_lookup(peers_notify->add, peer->path)) {
                DBG("new %s", peer->path);
 
-               append_peer_struct(key, value, user_data);
+               append_peer_struct(key, peer, iter);
                g_hash_table_remove(peers_notify->add, peer->path);
-       } else {
+       } else if (!g_hash_table_lookup(peers_notify->remove, peer->path)) {
                DBG("existing %s", peer->path);
 
                dbus_message_iter_open_container(iter, DBUS_TYPE_STRUCT,
                                                                NULL, &entry);
                dbus_message_iter_append_basic(&entry, DBUS_TYPE_OBJECT_PATH,
                                                                &peer->path);
+               connman_dbus_dict_open(&entry, &dict);
+               connman_dbus_dict_close(&entry, &dict);
+
                dbus_message_iter_close_container(iter, &entry);
        }
 }
@@ -195,32 +537,219 @@ static void peer_removed(struct connman_peer *peer)
        peer_schedule_changed();
 }
 
+static const char *get_dbus_sender(struct connman_peer *peer)
+{
+       if (!peer->pending)
+               return NULL;
+
+       return dbus_message_get_sender(peer->pending);
+}
+
+static enum connman_peer_wps_method check_wpspin(struct connman_peer *peer,
+                                                       const char *wpspin)
+{
+       int len, i;
+
+       if (!wpspin)
+               return CONNMAN_PEER_WPS_PBC;
+
+       len = strlen(wpspin);
+       if (len == 0)
+               return CONNMAN_PEER_WPS_PBC;
+
+       if (len != 8)
+               return CONNMAN_PEER_WPS_UNKNOWN;
+       for (i = 0; i < 8; i++) {
+               if (!isdigit((unsigned char) wpspin[i]))
+                       return CONNMAN_PEER_WPS_UNKNOWN;
+       }
+
+       return CONNMAN_PEER_WPS_PIN;
+}
+
+static void request_authorization_cb(struct connman_peer *peer,
+                                       bool choice_done, const char *wpspin,
+                                       const char *error, void *user_data)
+{
+       enum connman_peer_wps_method wps_method;
+       int err;
+
+       DBG("RequestInput return, %p", peer);
+
+       if (error) {
+               if (g_strcmp0(error,
+                               "net.connman.Agent.Error.Canceled") == 0 ||
+                       g_strcmp0(error,
+                               "net.connman.Agent.Error.Rejected") == 0) {
+                       err = -EINVAL;
+                       goto out;
+               }
+       }
+
+       if (!choice_done || !peer_driver->connect) {
+               err = -EINVAL;
+               goto out;
+       }
+
+       wps_method = check_wpspin(peer, wpspin);
+
+       err = peer_driver->connect(peer, wps_method, wpspin);
+       if (err == -EINPROGRESS)
+               return;
+
+out:
+       reply_pending(peer, EIO);
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_IDLE);
+}
+
+static int peer_connect(struct connman_peer *peer)
+{
+       int err = -ENOTSUP;
+
+       if (is_connected(peer))
+               return -EISCONN;
+
+       if (peer_driver->connect)
+               err = peer_driver->connect(peer,
+                                       CONNMAN_PEER_WPS_UNKNOWN, NULL);
+
+       if (err == -ENOKEY) {
+               err = __connman_agent_request_peer_authorization(peer,
+                                               request_authorization_cb, true,
+                                               get_dbus_sender(peer), NULL);
+       }
+
+       return err;
+}
+
+static int peer_disconnect(struct connman_peer *peer)
+{
+       int err = -ENOTSUP;
+
+       connman_agent_cancel(peer);
+       reply_pending(peer, ECONNABORTED);
+
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_DISCONNECT);
+
+       if (peer->connection_master)
+               stop_dhcp_server(peer);
+       else
+               __connman_dhcp_stop(peer->ipconfig);
+
+       if (peer_driver->disconnect)
+               err = peer_driver->disconnect(peer);
+
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_IDLE);
+
+       return err;
+}
+
+static DBusMessage *connect_peer(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct connman_peer *peer = user_data;
+       GList *list, *start;
+       int err;
+
+       DBG("peer %p", peer);
+
+       if (peer->pending)
+               return __connman_error_in_progress(msg);
+
+       list = g_hash_table_get_values(peers_table);
+       start = list;
+       for (; list; list = list->next) {
+               struct connman_peer *temp = list->data;
+
+               if (temp == peer || temp->device != peer->device)
+                       continue;
+
+               if (is_connecting(temp) || is_connected(temp)) {
+                       if (peer_disconnect(temp) == -EINPROGRESS) {
+                               g_list_free(start);
+                               return __connman_error_in_progress(msg);
+                       }
+               }
+       }
+
+       g_list_free(start);
+
+       peer->pending = dbus_message_ref(msg);
+
+       err = peer_connect(peer);
+       if (err == -EINPROGRESS)
+               return NULL;
+
+       if (err < 0) {
+               dbus_message_unref(peer->pending);
+               peer->pending = NULL;
+
+               return __connman_error_failed(msg, -err);
+       }
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *disconnect_peer(DBusConnection *conn,
+                                       DBusMessage *msg, void *user_data)
+{
+       struct connman_peer *peer = user_data;
+       int err;
+
+       DBG("peer %p", peer);
+
+       err = peer_disconnect(peer);
+       if (err < 0 && err != -EINPROGRESS)
+               return __connman_error_failed(msg, -err);
+
+       return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
 struct connman_peer *connman_peer_create(const char *identifier)
 {
        struct connman_peer *peer;
 
        peer = g_malloc0(sizeof(struct connman_peer));
-       peer->identifier = g_strdup_printf("peer_%s", identifier);
+       peer->identifier = g_strdup(identifier);
+       peer->state = CONNMAN_PEER_STATE_IDLE;
+
+       peer->refcount = 1;
 
        return peer;
 }
 
-void connman_peer_destroy(struct connman_peer *peer)
+struct connman_peer *connman_peer_ref_debug(struct connman_peer *peer,
+                               const char *file, int line, const char *caller)
 {
-       if (!peer)
+       DBG("%p ref %d by %s:%d:%s()", peer, peer->refcount + 1,
+                                               file, line, caller);
+
+       __sync_fetch_and_add(&peer->refcount, 1);
+
+       return peer;
+}
+
+void connman_peer_unref_debug(struct connman_peer *peer,
+                               const char *file, int line, const char *caller)
+{
+       DBG("%p ref %d by %s:%d:%s()", peer, peer->refcount - 1,
+                                               file, line, caller);
+
+       if (__sync_fetch_and_sub(&peer->refcount, 1) != 1)
                return;
 
-       if (peer->path) {
-               peer_removed(peer);
-               g_dbus_unregister_interface(connection, peer->path,
-                                               CONNMAN_PEER_INTERFACE);
-               g_free(peer->path);
-       }
+       if (!peer->registered && !peer->path)
+               return peer_free(peer);
 
-       g_free(peer->identifier);
-       g_free(peer->name);
+       g_hash_table_remove(peers_table, peer->path);
+}
 
-       g_free(peer);
+const char *connman_peer_get_identifier(struct connman_peer *peer)
+{
+       if (!peer)
+               return NULL;
+
+       return peer->identifier;
 }
 
 void connman_peer_set_name(struct connman_peer *peer, const char *name)
@@ -229,12 +758,320 @@ void connman_peer_set_name(struct connman_peer *peer, const char *name)
        peer->name = g_strdup(name);
 }
 
+void connman_peer_set_iface_address(struct connman_peer *peer,
+                                       const unsigned char *iface_address)
+{
+       memset(peer->iface_address, 0, sizeof(peer->iface_address));
+       memcpy(peer->iface_address, iface_address, ETH_ALEN);
+}
+
+void connman_peer_set_device(struct connman_peer *peer,
+                               struct connman_device *device)
+{
+       if (!peer || !device)
+               return;
+
+       peer->device = device;
+       connman_device_ref(device);
+}
+
+struct connman_device *connman_peer_get_device(struct connman_peer *peer)
+{
+       if (!peer)
+               return NULL;
+
+       return peer->device;
+}
+
+void connman_peer_set_sub_device(struct connman_peer *peer,
+                                       struct connman_device *device)
+{
+       if (!peer || !device || peer->sub_device)
+               return;
+
+       peer->sub_device = device;
+}
+
+void connman_peer_set_as_master(struct connman_peer *peer, bool master)
+{
+       if (!peer || !is_connecting(peer))
+               return;
+
+       peer->connection_master = master;
+}
+
+static void dhcp_callback(struct connman_ipconfig *ipconfig,
+                       struct connman_network *network,
+                       bool success, gpointer data)
+{
+       struct connman_peer *peer = data;
+       int err;
+
+       if (!success)
+               goto error;
+
+       DBG("lease acquired for ipconfig %p", ipconfig);
+
+       err = __connman_ipconfig_address_add(ipconfig);
+       if (err < 0)
+               goto error;
+
+       return;
+
+error:
+       __connman_ipconfig_address_remove(ipconfig);
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_FAILURE);
+}
+
+static int start_dhcp_client(struct connman_peer *peer)
+{
+       if (peer->sub_device)
+               __connman_ipconfig_set_index(peer->ipconfig,
+                               connman_device_get_index(peer->sub_device));
+
+       __connman_ipconfig_enable(peer->ipconfig);
+
+       return __connman_dhcp_start(peer->ipconfig, NULL, dhcp_callback, peer);
+}
+
+static void report_error_cb(void *user_context, bool retry, void *user_data)
+{
+       struct connman_peer *peer = user_context;
+
+       if (retry) {
+               int err;
+               err = peer_connect(peer);
+
+               if (err == 0 || err == -EINPROGRESS)
+                       return;
+       }
+
+       reply_pending(peer, ENOTCONN);
+
+       peer_disconnect(peer);
+
+       if (!peer->connection_master) {
+               __connman_dhcp_stop(peer->ipconfig);
+               __connman_ipconfig_disable(peer->ipconfig);
+       } else
+               stop_dhcp_server(peer);
+
+       peer->connection_master = false;
+       peer->sub_device = NULL;
+}
+
+static int manage_peer_error(struct connman_peer *peer)
+{
+       int err;
+
+       err = __connman_agent_report_peer_error(peer, peer->path,
+                                       "connect-failed", report_error_cb,
+                                       get_dbus_sender(peer), NULL);
+       if (err != -EINPROGRESS) {
+               report_error_cb(peer, false, NULL);
+               return err;
+       }
+
+       return 0;
+}
+
+int connman_peer_set_state(struct connman_peer *peer,
+                                       enum connman_peer_state new_state)
+{
+       enum connman_peer_state old_state = peer->state;
+       int err;
+
+       DBG("peer (%s) old state %d new state %d", peer->name,
+                               old_state, new_state);
+
+       if (old_state == new_state)
+               return -EALREADY;
+
+       switch (new_state) {
+       case CONNMAN_PEER_STATE_UNKNOWN:
+               return -EINVAL;
+       case CONNMAN_PEER_STATE_IDLE:
+               if (is_connecting(peer) || is_connected(peer))
+                       return peer_disconnect(peer);
+               peer->sub_device = NULL;
+               break;
+       case CONNMAN_PEER_STATE_ASSOCIATION:
+               break;
+       case CONNMAN_PEER_STATE_CONFIGURATION:
+               if (peer->connection_master)
+                       err = start_dhcp_server(peer);
+               else
+                       err = start_dhcp_client(peer);
+               if (err < 0)
+                       return connman_peer_set_state(peer,
+                                               CONNMAN_PEER_STATE_FAILURE);
+               break;
+       case CONNMAN_PEER_STATE_READY:
+               reply_pending(peer, 0);
+               __connman_technology_set_connected(CONNMAN_SERVICE_TYPE_P2P, true);
+               break;
+       case CONNMAN_PEER_STATE_DISCONNECT:
+               if (peer->connection_master)
+                       stop_dhcp_server(peer);
+               else
+                       __connman_dhcp_stop(peer->ipconfig);
+               peer->connection_master = false;
+               peer->sub_device = NULL;
+               __connman_technology_set_connected(CONNMAN_SERVICE_TYPE_P2P, false);
+               break;
+       case CONNMAN_PEER_STATE_FAILURE:
+               if (manage_peer_error(peer) == 0)
+                       return 0;
+               break;
+       };
+
+       peer->state = new_state;
+       state_changed(peer);
+
+       if (peer->state == CONNMAN_PEER_STATE_READY ||
+                               peer->state == CONNMAN_PEER_STATE_DISCONNECT)
+               settings_changed(peer);
+
+       return 0;
+}
+
+int connman_peer_request_connection(struct connman_peer *peer)
+{
+       return __connman_agent_request_peer_authorization(peer,
+                                       request_authorization_cb, false,
+                                       NULL, NULL);
+}
+
+static void peer_service_free(gpointer data)
+{
+       struct _peer_service *service = data;
+
+       if (!service)
+               return;
+
+       g_free(service->data);
+       g_free(service);
+}
+
+void connman_peer_reset_services(struct connman_peer *peer)
+{
+       if (!peer)
+               return;
+
+       g_slist_free_full(peer->services, peer_service_free);
+       peer->services = NULL;
+}
+
+void connman_peer_services_changed(struct connman_peer *peer)
+{
+       if (!peer || !peer->registered || !allow_property_changed(peer))
+               return;
+
+       connman_dbus_property_changed_array(peer->path,
+                       CONNMAN_PEER_INTERFACE, "Services",
+                       DBUS_TYPE_DICT_ENTRY, append_peer_services, peer);
+}
+
+void connman_peer_add_service(struct connman_peer *peer,
+                               enum connman_peer_service_type type,
+                               const unsigned char *data, int data_length)
+{
+       struct _peer_service *service;
+
+       if (!peer || !data || type == CONNMAN_PEER_SERVICE_UNKNOWN)
+               return;
+
+       service = g_malloc0(sizeof(struct _peer_service));
+       service->type = type;
+       service->data = g_memdup(data, data_length * sizeof(unsigned char));
+       service->length = data_length;
+
+       peer->services = g_slist_prepend(peer->services, service);
+}
+
+static void peer_up(struct connman_ipconfig *ipconfig, const char *ifname)
+{
+       DBG("%s up", ifname);
+}
+
+static void peer_down(struct connman_ipconfig *ipconfig, const char *ifname)
+{
+       DBG("%s down", ifname);
+}
+
+static void peer_lower_up(struct connman_ipconfig *ipconfig,
+                                                       const char *ifname)
+{
+       DBG("%s lower up", ifname);
+}
+
+static void peer_lower_down(struct connman_ipconfig *ipconfig,
+                                                       const char *ifname)
+{
+       struct connman_peer *peer = __connman_ipconfig_get_data(ipconfig);
+
+       DBG("%s lower down", ifname);
+
+       __connman_ipconfig_disable(ipconfig);
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_DISCONNECT);
+}
+
+static void peer_ip_bound(struct connman_ipconfig *ipconfig,
+                                                       const char *ifname)
+{
+       struct connman_peer *peer = __connman_ipconfig_get_data(ipconfig);
+
+       DBG("%s ip bound", ifname);
+
+       if (peer->state == CONNMAN_PEER_STATE_READY)
+               settings_changed(peer);
+       connman_peer_set_state(peer, CONNMAN_PEER_STATE_READY);
+}
+
+static void peer_ip_release(struct connman_ipconfig *ipconfig,
+                                                       const char *ifname)
+{
+       struct connman_peer *peer = __connman_ipconfig_get_data(ipconfig);
+
+       DBG("%s ip release", ifname);
+
+       if (peer->state == CONNMAN_PEER_STATE_READY)
+               settings_changed(peer);
+}
+
+static const struct connman_ipconfig_ops peer_ip_ops = {
+       .up             = peer_up,
+       .down           = peer_down,
+       .lower_up       = peer_lower_up,
+       .lower_down     = peer_lower_down,
+       .ip_bound       = peer_ip_bound,
+       .ip_release     = peer_ip_release,
+       .route_set      = NULL,
+       .route_unset    = NULL,
+};
+
+static struct connman_ipconfig *create_ipconfig(int index, void *user_data)
+{
+       struct connman_ipconfig *ipconfig;
+
+       ipconfig = __connman_ipconfig_create(index,
+                                               CONNMAN_IPCONFIG_TYPE_IPV4);
+       if (!ipconfig)
+               return NULL;
+
+       __connman_ipconfig_set_method(ipconfig, CONNMAN_IPCONFIG_METHOD_DHCP);
+       __connman_ipconfig_set_data(ipconfig, user_data);
+       __connman_ipconfig_set_ops(ipconfig, &peer_ip_ops);
+
+       return ipconfig;
+}
+
 static const GDBusMethodTable peer_methods[] = {
        { GDBUS_METHOD("GetProperties",
                        NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
                        get_peer_properties) },
-       { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, NULL) },
-       { GDBUS_METHOD("Disconnect", NULL, NULL, NULL) },
+       { GDBUS_ASYNC_METHOD("Connect", NULL, NULL, connect_peer) },
+       { GDBUS_METHOD("Disconnect", NULL, NULL, disconnect_peer) },
        { },
 };
 
@@ -244,23 +1081,37 @@ static const GDBusSignalTable peer_signals[] = {
        { },
 };
 
+static char *get_peer_path(struct connman_device *device,
+                                       const char *identifier)
+{
+       return g_strdup_printf("%s/peer/peer_%s_%s", CONNMAN_PATH,
+                               connman_device_get_ident(device), identifier);
+}
+
 int connman_peer_register(struct connman_peer *peer)
 {
+       int index;
+
        DBG("peer %p", peer);
 
-       if (peer->path)
+       if (peer->path && peer->registered)
                return -EALREADY;
 
-       peer->path = g_strdup_printf("%s/peer/%s", CONNMAN_PATH,
-                                               peer->identifier);
+       index = connman_device_get_index(peer->device);
+       peer->ipconfig = create_ipconfig(index, peer);
+       if (!peer->ipconfig)
+               return -ENOMEM;
+
+       peer->path = get_peer_path(peer->device, peer->identifier);
        DBG("path %s", peer->path);
 
-       g_hash_table_insert(peers_table, peer->identifier, peer);
+       g_hash_table_insert(peers_table, peer->path, peer);
 
        g_dbus_register_interface(connection, peer->path,
                                        CONNMAN_PEER_INTERFACE,
                                        peer_methods, peer_signals,
                                        NULL, peer, NULL);
+       peer->registered = true;
        peer_added(peer);
 
        return 0;
@@ -270,15 +1121,22 @@ void connman_peer_unregister(struct connman_peer *peer)
 {
        DBG("peer %p", peer);
 
-       if (peer->path)
-               g_hash_table_remove(peers_table, peer->identifier);
-       else
-               connman_peer_destroy(peer);
+       if (!peer->path || !peer->registered)
+               return;
+
+       connman_agent_cancel(peer);
+       reply_pending(peer, EIO);
+
+       g_dbus_unregister_interface(connection, peer->path,
+                                       CONNMAN_PEER_INTERFACE);
+       peer->registered = false;
+       peer_removed(peer);
 }
 
-struct connman_peer *connman_peer_get(const char *identifier)
+struct connman_peer *connman_peer_get(struct connman_device *device,
+                                               const char *identifier)
 {
-       char *ident = g_strdup_printf("peer_%s", identifier);
+       char *ident = get_peer_path(device, identifier);
        struct connman_peer *peer;
 
        peer = g_hash_table_lookup(peers_table, ident);
@@ -287,11 +1145,53 @@ struct connman_peer *connman_peer_get(const char *identifier)
        return peer;
 }
 
+int connman_peer_driver_register(struct connman_peer_driver *driver)
+{
+       if (peer_driver && peer_driver != driver)
+               return -EINVAL;
+
+       peer_driver = driver;
+
+       __connman_peer_service_set_driver(driver);
+
+       return 0;
+}
+
+void connman_peer_driver_unregister(struct connman_peer_driver *driver)
+{
+       if (peer_driver != driver)
+               return;
+
+       peer_driver = NULL;
+
+       __connman_peer_service_set_driver(NULL);
+}
+
 void __connman_peer_list_struct(DBusMessageIter *array)
 {
        g_hash_table_foreach(peers_table, append_peer_struct, array);
 }
 
+const char *__connman_peer_get_path(struct connman_peer *peer)
+{
+       if (!peer || !peer->registered)
+               return NULL;
+
+       return peer->path;
+}
+
+static void disconnect_peer_hash_table(gpointer key,
+                                       gpointer value, gpointer user_data)
+{
+       struct connman_peer *peer = value;
+       peer_disconnect(peer);
+}
+
+void __connman_peer_disconnect_all(void)
+{
+       g_hash_table_foreach(peers_table, disconnect_peer_hash_table, NULL);
+}
+
 int __connman_peer_init(void)
 {
        DBG("");
@@ -312,6 +1212,12 @@ void __connman_peer_cleanup(void)
 {
        DBG("");
 
+       g_hash_table_destroy(peers_notify->remove);
+       g_hash_table_destroy(peers_notify->add);
+       g_free(peers_notify);
+
        g_hash_table_destroy(peers_table);
+       peers_table = NULL;
        dbus_connection_unref(connection);
+       connection = NULL;
 }