diff --git a/ovn/controller/automake.mk b/ovn/controller/automake.mk
index fec9bf1..cadfa9c 100644
--- a/ovn/controller/automake.mk
+++ b/ovn/controller/automake.mk
@@ -10,6 +10,8 @@ ovn_controller_ovn_controller_SOURCES = \
 	ovn/controller/lflow.h \
 	ovn/controller/ofctrl.c \
 	ovn/controller/ofctrl.h \
+	ovn/controller/pinctrl.c \
+	ovn/controller/pinctrl.h \
 	ovn/controller/patch.c \
 	ovn/controller/patch.h \
 	ovn/controller/ovn-controller.c \
diff --git a/ovn/controller/ovn-controller.c b/ovn/controller/ovn-controller.c
index 3f29b25..02ecb3e 100644
--- a/ovn/controller/ovn-controller.c
+++ b/ovn/controller/ovn-controller.c
@@ -41,6 +41,7 @@
 #include "util.h"
 
 #include "ofctrl.h"
+#include "pinctrl.h"
 #include "binding.h"
 #include "chassis.h"
 #include "encaps.h"
@@ -223,6 +224,7 @@ main(int argc, char *argv[])
     sbrec_init();
 
     ofctrl_init();
+    pinctrl_init();
     lflow_init();
 
     /* Connect to OVS OVSDB instance.  We do not monitor all tables by
@@ -292,6 +294,8 @@ main(int argc, char *argv[])
         if (br_int) {
             enum mf_field_id mff_ovn_geneve = ofctrl_run(br_int);
 
+            pinctrl_run(&ctx, br_int);
+
             struct hmap flow_table = HMAP_INITIALIZER(&flow_table);
             lflow_run(&ctx, &flow_table, &ct_zones);
             if (chassis_id) {
@@ -314,6 +318,7 @@ main(int argc, char *argv[])
 
         if (br_int) {
             ofctrl_wait();
+            pinctrl_wait();
         }
         poll_block();
         if (should_service_stop()) {
@@ -351,6 +356,7 @@ main(int argc, char *argv[])
     unixctl_server_destroy(unixctl);
     lflow_destroy();
     ofctrl_destroy();
+    pinctrl_destroy();
 
     simap_destroy(&ct_zones);
 
diff --git a/ovn/controller/pinctrl.c b/ovn/controller/pinctrl.c
new file mode 100644
index 0000000..7b7fe49
--- /dev/null
+++ b/ovn/controller/pinctrl.c
@@ -0,0 +1,177 @@
+
+/* Copyright (c) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <config.h>
+#include "dirs.h"
+#include "pinctrl.h"
+#include "ofp-msgs.h"
+#include "ofp-print.h"
+#include "ofp-util.h"
+#include "rconn.h"
+#include "openvswitch/vlog.h"
+#include "socket-util.h"
+#include "vswitch-idl.h"
+
+VLOG_DEFINE_THIS_MODULE(pinctrl);
+
+/* OpenFlow connection to the switch. */
+static struct rconn *swconn;
+
+/* Last seen sequence number for 'swconn'.  When this differs from
+ * rconn_get_connection_seqno(rconn), 'swconn' has reconnected. */
+static unsigned int conn_seq_no;
+
+void
+pinctrl_init(void)
+{
+    swconn = rconn_create(5, 0, DSCP_DEFAULT, 0xF);
+    conn_seq_no = 0;
+}
+
+static ovs_be32
+queue_msg(struct ofpbuf *msg)
+{
+    const struct ofp_header *oh = msg->data;
+    ovs_be32 xid = oh->xid;
+
+    rconn_send(swconn, msg, NULL);
+    return xid;
+}
+
+static void
+get_switch_config(struct rconn *swconn)
+{
+    struct ofpbuf *request;
+
+    request = ofpraw_alloc(OFPRAW_OFPT_GET_CONFIG_REQUEST,
+                           rconn_get_version(swconn), 0);
+    queue_msg(request);
+}
+
+static void
+set_switch_config(struct rconn *swconn, const struct ofp_switch_config *config)
+{
+    struct ofpbuf *request;
+
+    request =
+        ofpraw_alloc(OFPRAW_OFPT_SET_CONFIG, rconn_get_version(swconn), 0);
+    ofpbuf_put(request, config, sizeof *config);
+
+    queue_msg(request);
+}
+
+static void
+process_packet_in(struct controller_ctx *ctx OVS_UNUSED,
+                  const struct ofp_header *msg)
+{
+    struct ofputil_packet_in pin;
+
+    if (ofputil_decode_packet_in(&pin, msg) != 0) {
+        return;
+    }
+    if (pin.reason != OFPR_ACTION) {
+        return;
+    }
+
+    /* XXX : process the received packet */
+}
+
+static void
+pinctrl_recv(struct controller_ctx *ctx, const struct ofp_header *oh,
+             enum ofptype type)
+{
+    if (type == OFPTYPE_ECHO_REQUEST) {
+        queue_msg(make_echo_reply(oh));
+    } else if (type == OFPTYPE_GET_CONFIG_REPLY) {
+        struct ofpbuf rq_buf;
+        struct ofpbuf *spif;
+        struct ofp_switch_config *config_, config;
+
+        ofpbuf_use_const(&rq_buf, oh, ntohs(oh->length));
+        config_ = ofpbuf_pull(&rq_buf, sizeof *config_);
+        config = *config_;
+        config.miss_send_len = htons(UINT16_MAX);
+        set_switch_config(swconn, &config);
+        spif = ofputil_make_set_packet_in_format(rconn_get_version(swconn),
+                                                 NXPIF_NXM);
+        queue_msg(spif);
+    } else if (type == OFPTYPE_PACKET_IN) {
+        process_packet_in(ctx, oh);
+    } else if (type != OFPTYPE_ECHO_REPLY && type != OFPTYPE_BARRIER_REPLY) {
+        if (VLOG_IS_DBG_ENABLED()) {
+            static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300);
+
+            char *s = ofp_to_string(oh, ntohs(oh->length), 2);
+
+            VLOG_DBG_RL(&rl, "OpenFlow packet ignored: %s", s);
+            free(s);
+        }
+    }
+}
+
+void
+pinctrl_run(struct controller_ctx *ctx, const struct ovsrec_bridge *br_int)
+{
+    if (br_int) {
+        char *target;
+
+        target = xasprintf("unix:%s/%s.mgmt", ovs_rundir(), br_int->name);
+        if (strcmp(target, rconn_get_target(swconn))) {
+            VLOG_INFO("%s: connecting to switch", target);
+            rconn_connect(swconn, target, target);
+        }
+        free(target);
+    } else {
+        rconn_disconnect(swconn);
+    }
+
+    rconn_run(swconn);
+
+    if (!rconn_is_connected(swconn)) {
+        return;
+    }
+
+    if (conn_seq_no != rconn_get_connection_seqno(swconn)) {
+        get_switch_config(swconn);
+        conn_seq_no = rconn_get_connection_seqno(swconn);
+    }
+
+    struct ofpbuf *msg = rconn_recv(swconn);
+
+    if (!msg) {
+        return;
+    }
+
+    const struct ofp_header *oh = msg->data;
+    enum ofptype type;
+
+    ofptype_decode(&type, oh);
+    pinctrl_recv(ctx, oh, type);
+    ofpbuf_delete(msg);
+}
+
+void
+pinctrl_wait(void)
+{
+    rconn_run_wait(swconn);
+    rconn_recv_wait(swconn);
+}
+
+void
+pinctrl_destroy(void)
+{
+    rconn_destroy(swconn);
+}
diff --git a/ovn/controller/pinctrl.h b/ovn/controller/pinctrl.h
new file mode 100644
index 0000000..65d5dfe
--- /dev/null
+++ b/ovn/controller/pinctrl.h
@@ -0,0 +1,34 @@
+
+/* Copyright (c) 2015 Nicira, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at:
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H 1
+
+#include <stdint.h>
+
+#include "meta-flow.h"
+
+struct ovsrec_bridge;
+struct controller_ctx;
+
+/* Interface for OVN main loop. */
+void pinctrl_init(void);
+void pinctrl_run(struct controller_ctx *ctx,
+                 const struct ovsrec_bridge *br_int);
+void pinctrl_wait(void);
+void pinctrl_destroy(void);
+
+#endif /* ovn/dhcp.h */
