Updating moe_db.txt with the latest equivalence since the ruby import changed the exported structure.
	Change on 2014/12/01 by nnoble <nnoble@google.com>
-------------
new [] file for grpc testing.
	Change on 2014/12/02 by donnadionne <donnadionne@google.com>
-------------
Fix unfinished calls in thread_stress_test.

Previously we had an early return if we cancelled a stream part way through a
message. Correct this, so that half close and full close signals are propagated
up the stack correctly so that higher level state machines can see the
termination.
	Change on 2014/12/02 by ctiller <ctiller@google.com>
-------------
Remove dependency on internal C code.
	Change on 2014/12/02 by ctiller <ctiller@google.com>
-------------
Turn off the flaky bit from thread_stress_test.
	Change on 2014/12/02 by ctiller <ctiller@google.com>
-------------
Add test cases of empty request/response, request streaming, response streaming, and half duplex streaming.

Bring up the GFE/ESF for mannual test:
[] build java/com/google/net/[]/testing/integration/hexa:server_components_env
[]-bin/java/com/google/net/[]/testing/integration/hexa/server_components_env --manual --rpc_port=25000 --use_autobahn
	Change on 2014/12/02 by chenw <chenw@google.com>
-------------
Make echo/server.c and fling/server.c shutdown cleanly on SIGINT, and update
the relevant tests to exercise this mechanism.

Now "[] coverage" and the memory leak detector are able to see into the
server processes.
	Change on 2014/12/02 by pmarks <pmarks@google.com>
-------------
Allow the # of channels to be configurable in this performance test. The threads will use the channels in statically-defined round-robin order (not based on when RPCs complete on any channel). The interesting cases are #channels=1 or #channels=#threads (we previously only had the latter case)
	Change on 2014/12/02 by vpai <vpai@google.com>
-------------
Fixed a typo and reworded a comment.
	Change on 2014/12/02 by gnezdo <gnezdo@google.com>
-------------
Require the grpc_call in this ClientContext to be NULL before allowing set_call to be invoked. Otherwise, it's an indication of a leak somewhere.
	Change on 2014/12/02 by vpai <vpai@google.com>
-------------
Correctly return status other than ok and add a test for it.
	Change on 2014/12/02 by yangg <yangg@google.com>
-------------
Better C++ guards for grpc_security.h
	Change on 2014/12/02 by nnoble <nnoble@google.com>
-------------
Use nullptr instead of NULL for consistency.
	Change on 2014/12/02 by vpai <vpai@google.com>
-------------
Updates the ruby gRPC service class to require the serialization method to be
a static method

- this brings it inline with the proto3 ruby API
- it adds a monkey patch to allow existing proto (beefcake) to continue working.
	Change on 2014/12/02 by temiola <temiola@google.com>
-------------
Adding a buildable unit to the blue print file.

Added the buildable unit as its name will be usesd as tap project id.

This test will fail right away in tap until tests are actually added.
	Change on 2014/12/02 by donnadionne <donnadionne@google.com>
-------------
Move interop ESF C++ server from Java to grpc directory.

Tests passed:
[] test javatests/com/google/net/[]/testing/integration/hexa/...
[] test net/grpc/testing/interop/esf_server/...
	Change on 2014/12/02 by chenw <chenw@google.com>
-------------
Return a lame channel as opposed to NULL when secure channel creation fails.

- Looks like we're going to need something similar server-side.
- I changed the prototype of the lame client channel factory to take an
explicit void as I think this is better practice in C. Let me know if you
disagree and I will revert these changes.
	Change on 2014/12/02 by jboeuf <jboeuf@google.com>
-------------
Putting ALPN support where it belongs.
	Change on 2014/12/02 by jboeuf <jboeuf@google.com>
-------------
GOAWAY send path.

Sends a GOAWAY frame when shutting down.
This is not read and understood yet.
	Change on 2014/12/03 by ctiller <ctiller@google.com>
-------------
Adds support for secure channels and servers.

- wraps new C apis (credentials, server_credentials) and Server#add_secure_http_port
- adds tests to ensure credentials and server credentials can be created
- updates client_server_spec to run the client_server wrapper layer end-to-end tests using a secure channel
	Change on 2014/12/03 by temiola <temiola@google.com>
-------------
Fix existing issues regarding out of order events.

At the client side, using pluck as the client_metadata_read can happen anytime after invoke.

At the server side, allow halfclose_ok and rpc_end to come in reverse order.
	Change on 2014/12/03 by yangg <yangg@google.com>
-------------
Don't track coverage of tests.
	Change on 2014/12/03 by ctiller <ctiller@google.com>
-------------
Change UnaryCall to conform standard test requirement of LargeUnaryCall.
	Change on 2014/12/03 by yangg <yangg@google.com>
-------------
updating alpn version to h2-15 ensure all interop are on the same version and working.

Java and go are not ready for h2-16 yet.
	Change on 2014/12/03 by donnadionne <donnadionne@google.com>
-------------
Add config to bring echo server in [].

This is used to test production GFE as its bckend.
	Change on 2014/12/03 by chenw <chenw@google.com>
-------------
In preparation for fixing shutdown race issues, change em to take ownership of
the file descriptor. Add an API to grpc_tcp to take an already created
grpc_em_fd object, and change tcp_client to use that API.

This is needed because otherwise an em user's close() of the file descriptor
may race with libevent internals. That's not an issue yet because destroy()
frees the events inline, but that can't be done safely if there is a concurrent
poller.
	Change on 2014/12/03 by klempner <klempner@google.com>
-------------
Fixing TAP opensource build

We don't want to compile and run C++ tests in the C target.
	Change on 2014/12/03 by nnoble <nnoble@google.com>
-------------
Move and separate interop tests by languages.

Small fixes to the test runner.

Improving logging.
	Change on 2014/12/03 by donnadionne <donnadionne@google.com>
-------------
Fixing the opensource build:

-) The C/C++ split wasn't done up to the 'dep' target level
-) The alpn.c file was missing from build.json
	Change on 2014/12/03 by nnoble <nnoble@google.com>
-------------
Adding blue print files after projects exist
	Change on 2014/12/03 by donnadionne <donnadionne@google.com>
-------------
Refactor StreamContext using the new completion_queue_pluck API.

The dedicated the poller thread has been removed.
This CL keeps the current behavior to make it short. There is one following to
make it usable for both client and server.

The tags for pluck is based on the address of this StreamContext object for potential debug use.

The Read/Write and Wait cannot be called concurrently now and this might need to be fixed.
	Change on 2014/12/03 by yangg <yangg@google.com>
-------------
Binary encoding utilities.

Support base64 encoding, HPACK static huffman encoding, and doing both at once.
	Change on 2014/12/03 by ctiller <ctiller@google.com>
-------------
Enforce Makefile regeneration in presubmits.
	Change on 2014/12/03 by ctiller <ctiller@google.com>
-------------
Make CloseSend() send a real zero-length control message to indicate EOS.
	Change on 2014/12/03 by zhaoq <zhaoq@google.com>
-------------
Prefer to create dualstack sockets for TCP clients and servers, with automatic
fallback for environments where IPV6_V6ONLY can't be turned off.
	Change on 2014/12/03 by pmarks <pmarks@google.com>
-------------
Add opensource path to build targets.

Ensure that MOE is going to run.
	Change on 2014/12/03 by ctiller <ctiller@google.com>
-------------
Add PingPong test case. Delete FullDuplex test case. The latter is not specified for client in

https://docs.google.com/document/d/1dwrPpIu5EqiKVsquZfoOqTj7vP8fa1i49gornJo50Qw/edit#
	Change on 2014/12/03 by chenw <chenw@google.com>
-------------
Make generate_projects.sh check out the generated targets.
	Change on 2014/12/03 by ctiller <ctiller@google.com>
-------------
rspec cleanup

- stops declaring specs within the GRPC module
- splits Bidi streaming specs into a separate test suite

adding tests in the GRPC module was a mistake, it pollutes the module and can
affect other tests that run later by the test runner

the bidi tests are currently flaky, having them run in their own test suite
allows having two separate continuous builds (once ruby gRPC is on GitHub),
one that includes bidi where we tolerate flakiness, and another that does not,
where there should be no flakiness at all
	Change on 2014/12/03 by temiola <temiola@google.com>
-------------
Adding support for composite and IAM credentials.

- For now, we don't do any checks on credentials compatibility in the
composite credentials. Maybe we'll add that later.
- Refactored the end to end security tests so that we always use the public API
(except for the fake security context which is not exposed).
	Change on 2014/12/03 by jboeuf <jboeuf@google.com>
-------------
Make GPR library buildable in Visual Studio 2013.
	Change on 2014/12/04 by jtattermusch <jtattermusch@google.com>
-------------
Adds codegen for ruby

This is being added now that ruby's proto and grpc apis are defined and stable
	Change on 2014/12/04 by temiola <temiola@google.com>
-------------
Prevent NewStream() from sending negative or 0 timeout.
	Change on 2014/12/04 by zhaoq <zhaoq@google.com>
-------------
Add a grpc_sockaddr_to_string() function, and use it when logging bind
failures.  Also improve const-correctness in some earlier code.

I'm not certain whether inet_ntop() will need any platform-specific
implementations, but for now the compiler offers no complaints.

Demo:
$ []-bin/net/grpc/c/echo_server 1.2.3.4:80
... tcp_server.c:139] bind addr=[::ffff:1.2.3.4]:80: Permission denied
	Change on 2014/12/04 by pmarks <pmarks@google.com>
-------------
Refactoring - moves c wrapped classes to a submodule Google::RPC::Core

- this allows for an explicit rule when reading through gRPC ruby code for telling
when an object is pure ruby or wrapped C
	Change on 2014/12/04 by temiola <temiola@google.com>
-------------
Fixes the bidi_call

[]
	Change on 2014/12/04 by temiola <temiola@google.com>
-------------
Fixing dev build when activating surface traces.
	Change on 2014/12/04 by nnoble <nnoble@google.com>
-------------
Updates the tests to reflect that fact that some Credentials compose works.
	Change on 2014/12/04 by temiola <temiola@google.com>
-------------
Making the generate_project_test actually do something.
	Change on 2014/12/04 by nnoble <nnoble@google.com>
-------------
Rename "esf_server" to "[]4_server". Delete "test_sever" from Java directory.
	Change on 2014/12/04 by chenw <chenw@google.com>
-------------
Added PHP client interop tests. Tested large_unary against the C++ server.
	Change on 2014/12/04 by mlumish <mlumish@google.com>
-------------
Refactor grpc_create_dualstack_socket() by pulling the setsockopt into its own
function.  This separates the magic test flag from the real fallback logic.
	Change on 2014/12/04 by pmarks <pmarks@google.com>
-------------
Fixes the type of the constant used for test cert hostname
	Change on 2014/12/04 by temiola <temiola@google.com>
-------------
Disabling these tests as they're causing flakiness.
	Change on 2014/12/04 by ctiller <ctiller@google.com>
-------------
Change intptr --> uintptr.

Handles the case where a void* turns into a negative number, which then gets
hashed into a negative bucket and segfaults.
	Change on 2014/12/04 by ctiller <ctiller@google.com>
-------------
Add a test fixture to force parsers to handle one byte at a time.

This should expand coverage and hopefully prevent errors at some point (it
seems to pass out of the box for now though).
	Change on 2014/12/04 by ctiller <ctiller@google.com>
-------------
The code generator isn't +x.
	Change on 2014/12/04 by ctiller <ctiller@google.com>
-------------
Updates math_client and math_server to allow construction using crednetials

By:
- Extending rpc_server constructor so that it takes a credentials keyword param
- Extending client_stub constructor so that it takes a credentials keyword param
	Change on 2014/12/04 by temiola <temiola@google.com>
-------------
Format output a little more nicely.

Print each line of output separately - previously logging.info was truncating this at some maximum length, and logs were getting lost.
	Change on 2014/12/04 by ctiller <ctiller@google.com>
-------------
Up timeout for this test.

Under TSAN, if we process one byte at a time, this timeout can be reached - and I think this is the cause of the following flake:
[]
	Change on 2014/12/05 by ctiller <ctiller@google.com>
-------------
Adding more error logging for ssl.
	Change on 2014/12/05 by jboeuf <jboeuf@google.com>
-------------
Read path for goaway.

Still need to add hooks to deprecate a channel on the client side when goaway
is received.
	Change on 2014/12/05 by ctiller <ctiller@google.com>
-------------
Separate accept() into server_accept() and server_end_of_initial_metadata().

This allows servers to initiate reads before finishing writing metadata.
	Change on 2014/12/05 by ctiller <ctiller@google.com>
-------------
Fix for breakage 11512317 - adding missing test files.
	Change on 2014/12/05 by nnoble <nnoble@google.com>
-------------
grpc c++ server side streaming support.

This is based on [] There is a lot of room to clean up the internal implementation which may require refactoring of CompletionQueue. The current cl serves as a working implementation with the missing interfaces.

The sample generated files are included and will be removed before submitting.
	Change on 2014/12/05 by yangg <yangg@google.com>
-------------
Changed to the latest timeout format again (search "grpc-timeout" in [] for the spec).
	Change on 2014/12/05 by zhaoq <zhaoq@google.com>
-------------
Fixing opensource build.
	Change on 2014/12/05 by nnoble <nnoble@google.com>
-------------
Making absolutely sure we can do the moe export by adding a sh_test for it.
	Change on 2014/12/05 by nnoble <nnoble@google.com>
-------------
Change :scheme psuedo-header from "grpc" to "http" or "https".
	Change on 2014/12/05 by zhaoq <zhaoq@google.com>
-------------
Add server credential wrapping for c++ server. It only wraps ssl and []2 for now.

The ServerCredentials class and the factory class are in a similar fashion as
client side wrapping. The difference is the factory method returns shared_ptr
instead of unique_ptr as the server builder needs to keep a reference to it for
actually creating the server later.

The integration will happen in a following cl.
	Change on 2014/12/05 by yangg <yangg@google.com>
-------------
Fixed bugs in new_grpc_docker_builder.sh
	Change on 2014/12/05 by mlumish <mlumish@google.com>
-------------
In secure endpoint, hold a refcount for the life of a write callback if the
write does not complete immediately.
	Change on 2014/12/05 by klempner <klempner@google.com>
-------------
Add migration support to MOE and have TAP verify it doesn't break.

Migration support allows mirroring commits from [] into the git repo, instead of just a dump of the current source.
	Change on 2014/12/05 by ejona <ejona@google.com>
-------------
Change initial window size to 65535 according http2 draft 15.
	Change on 2014/12/05 by zhaoq <zhaoq@google.com>
-------------
Re-enable the flaky cases in dualstack_socket_test, with additional logging to
help track down the problem if it surfaces again.

This also seems like a good opportunity to make grpc_socket_utils a separate
library, as it's not really specific to TCP.

Example output:
logspam: [], 26570) resolved 2 addrs in 37ms:
logspam:   [0] [::1]:26570
logspam:   [1] 127.0.0.1:26570
	Change on 2014/12/05 by pmarks <pmarks@google.com>
-------------
Opensource build fixes.

-) A function that has a return type should actually return something.
-) Don't pass unsigned chars to strlen and strncmp.
	Change on 2014/12/05 by nnoble <nnoble@google.com>
-------------
Created by MOE: http://code.google.com/p/moe-java
MOE_MIGRATED_REVID=81458281
diff --git a/src/core/channel/channel_stack.h b/src/core/channel/channel_stack.h
index 0ae1005..67cd356 100644
--- a/src/core/channel/channel_stack.h
+++ b/src/core/channel/channel_stack.h
@@ -124,9 +124,17 @@
 char *grpc_call_op_string(grpc_call_op *op);
 
 typedef enum {
-  GRPC_CHANNEL_SHUTDOWN,
+  /* send a goaway message to remote channels indicating that we are going
+     to disconnect in the future */
+  GRPC_CHANNEL_GOAWAY,
+  /* disconnect any underlying transports */
+  GRPC_CHANNEL_DISCONNECT,
+  /* transport received a new call */
   GRPC_ACCEPT_CALL,
-  GRPC_TRANSPORT_CLOSED
+  /* an underlying transport was closed */
+  GRPC_TRANSPORT_CLOSED,
+  /* an underlying transport is about to be closed */
+  GRPC_TRANSPORT_GOAWAY
 } grpc_channel_op_type;
 
 /* A single filterable operation to be performed on a channel */
@@ -142,6 +150,10 @@
       grpc_transport *transport;
       const void *transport_server_data;
     } accept_call;
+    struct {
+      grpc_status_code status;
+      gpr_slice message;
+    } goaway;
   } data;
 } grpc_channel_op;
 
diff --git a/src/core/channel/client_channel.c b/src/core/channel/client_channel.c
index 9056368..0ceffba 100644
--- a/src/core/channel/client_channel.c
+++ b/src/core/channel/client_channel.c
@@ -392,8 +392,14 @@
   /* send the message down */
   for (i = 0; i < child_count; i++) {
     child_elem = grpc_channel_stack_element(children[i], 0);
+    if (op->type == GRPC_CHANNEL_GOAWAY) {
+      gpr_slice_ref(op->data.goaway.message);
+    }
     child_elem->filter->channel_op(child_elem, op);
   }
+  if (op->type == GRPC_CHANNEL_GOAWAY) {
+    gpr_slice_unref(op->data.goaway.message);
+  }
 
   /* unmark the inflight requests */
   gpr_mu_lock(&chand->mu);
diff --git a/src/core/channel/connected_channel.c b/src/core/channel/connected_channel.c
index 336472e..fb2d5ad 100644
--- a/src/core/channel/connected_channel.c
+++ b/src/core/channel/connected_channel.c
@@ -169,7 +169,11 @@
   GPR_ASSERT(elem->filter == &grpc_connected_channel_filter);
 
   switch (op->type) {
-    case GRPC_CHANNEL_SHUTDOWN:
+    case GRPC_CHANNEL_GOAWAY:
+      grpc_transport_goaway(chand->transport, op->data.goaway.status,
+                            op->data.goaway.message);
+      break;
+    case GRPC_CHANNEL_DISCONNECT:
       grpc_transport_close(chand->transport);
       break;
     default:
@@ -439,7 +443,6 @@
                  "Last message truncated; read %d bytes, expected %d",
                  calld->incoming_message.length,
                  calld->incoming_message_length);
-      return;
     }
     call_op.type = GRPC_RECV_HALF_CLOSE;
     call_op.dir = GRPC_CALL_UP;
@@ -458,6 +461,29 @@
   }
 }
 
+static void transport_goaway(void *user_data, grpc_transport *transport,
+                             grpc_status_code status, gpr_slice debug) {
+  /* transport got goaway ==> call up and handle it */
+  grpc_channel_element *elem = user_data;
+  channel_data *chand = elem->channel_data;
+  char *msg;
+  grpc_channel_op op;
+
+  GPR_ASSERT(elem->filter == &grpc_connected_channel_filter);
+  GPR_ASSERT(chand->transport == transport);
+
+  msg = gpr_hexdump((const char *)GPR_SLICE_START_PTR(debug),
+                    GPR_SLICE_LENGTH(debug), GPR_HEXDUMP_PLAINTEXT);
+  gpr_log(GPR_DEBUG, "got goaway: status=%d, message=%s", status, msg);
+  gpr_free(msg);
+
+  op.type = GRPC_TRANSPORT_GOAWAY;
+  op.dir = GRPC_CALL_UP;
+  op.data.goaway.status = status;
+  op.data.goaway.message = debug;
+  channel_op(elem, &op);
+}
+
 static void transport_closed(void *user_data, grpc_transport *transport) {
   /* transport was closed ==> call up and handle it */
   grpc_channel_element *elem = user_data;
@@ -473,7 +499,8 @@
 }
 
 const grpc_transport_callbacks connected_channel_transport_callbacks = {
-    alloc_recv_buffer, accept_stream, recv_batch, transport_closed,
+    alloc_recv_buffer, accept_stream,    recv_batch,
+    transport_goaway,  transport_closed,
 };
 
 grpc_transport_setup_result grpc_connected_channel_bind_transport(
diff --git a/src/core/endpoint/resolve_address.c b/src/core/endpoint/resolve_address.c
index aa21954..1993b9b 100644
--- a/src/core/endpoint/resolve_address.c
+++ b/src/core/endpoint/resolve_address.c
@@ -41,10 +41,12 @@
 #include <unistd.h>
 #include <string.h>
 
+#include "src/core/endpoint/socket_utils.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/string.h>
 #include <grpc/support/log.h>
 #include <grpc/support/thd.h>
+#include <grpc/support/time.h>
 
 typedef struct {
   char *name;
@@ -119,6 +121,7 @@
   int s;
   size_t i;
   grpc_resolved_addresses *addrs = NULL;
+  const gpr_timespec start_time = gpr_now();
 
   /* parse name, splitting it into host and port parts */
   split_host_port(name, &host, &port);
@@ -160,6 +163,22 @@
     i++;
   }
 
+  /* Temporary logging, to help identify flakiness in dualstack_socket_test. */
+  {
+    const gpr_timespec delay = gpr_time_sub(gpr_now(), start_time);
+    const int delay_ms =
+        delay.tv_sec * GPR_MS_PER_SEC + delay.tv_nsec / GPR_NS_PER_MS;
+    gpr_log(GPR_INFO, "logspam: getaddrinfo(%s, %s) resolved %d addrs in %dms:",
+            host, port, addrs->naddrs, delay_ms);
+    for (i = 0; i < addrs->naddrs; i++) {
+      char *buf;
+      grpc_sockaddr_to_string(&buf, (struct sockaddr *)&addrs->addrs[i].addr,
+                              0);
+      gpr_log(GPR_INFO, "logspam:   [%d] %s", i, buf);
+      gpr_free(buf);
+    }
+  }
+
 done:
   gpr_free(host);
   gpr_free(port);
diff --git a/src/core/endpoint/secure_endpoint.c b/src/core/endpoint/secure_endpoint.c
index 4fab0fa..ecf41d7 100644
--- a/src/core/endpoint/secure_endpoint.c
+++ b/src/core/endpoint/secure_endpoint.c
@@ -50,6 +50,8 @@
   /* saved upper level callbacks and user_data. */
   grpc_endpoint_read_cb read_cb;
   void *read_user_data;
+  grpc_endpoint_write_cb write_cb;
+  void *write_user_data;
   /* saved handshaker leftover data to unprotect. */
   gpr_slice_buffer leftover_bytes;
   /* buffers for read and write */
@@ -208,6 +210,12 @@
   *end = GPR_SLICE_END_PTR(ep->write_staging_buffer);
 }
 
+static void on_write(void *data, grpc_endpoint_cb_status error) {
+  secure_endpoint *ep = data;
+  ep->write_cb(ep->write_user_data, error);
+  secure_endpoint_unref(ep);
+}
+
 static grpc_endpoint_write_status write(grpc_endpoint *secure_ep,
                                         gpr_slice *slices, size_t nslices,
                                         grpc_endpoint_write_cb cb,
@@ -219,6 +227,7 @@
   secure_endpoint *ep = (secure_endpoint *)secure_ep;
   gpr_uint8 *cur = GPR_SLICE_START_PTR(ep->write_staging_buffer);
   gpr_uint8 *end = GPR_SLICE_END_PTR(ep->write_staging_buffer);
+  grpc_endpoint_write_status status;
   GPR_ASSERT(ep->output_buffer.count == 0);
 
 #ifdef GRPC_TRACE_SECURE_TRANSPORT
@@ -295,8 +304,16 @@
   /* clear output_buffer and let the lower level handle its slices. */
   output_buffer_count = ep->output_buffer.count;
   ep->output_buffer.count = 0;
-  return grpc_endpoint_write(ep->wrapped_ep, ep->output_buffer.slices,
-                             output_buffer_count, cb, user_data, deadline);
+  ep->write_cb = cb;
+  ep->write_user_data = user_data;
+  /* Need to keep the endpoint alive across a transport */
+  secure_endpoint_ref(ep);
+  status = grpc_endpoint_write(ep->wrapped_ep, ep->output_buffer.slices,
+                               output_buffer_count, on_write, ep, deadline);
+  if (status != GRPC_ENDPOINT_WRITE_PENDING) {
+    secure_endpoint_unref(ep);
+  }
+  return status;
 }
 
 static void shutdown(grpc_endpoint *secure_ep) {
diff --git a/src/core/endpoint/socket_utils.c b/src/core/endpoint/socket_utils.c
index 9c2540b..ef160d7 100644
--- a/src/core/endpoint/socket_utils.c
+++ b/src/core/endpoint/socket_utils.c
@@ -33,6 +33,7 @@
 
 #include "src/core/endpoint/socket_utils.h"
 
+#include <arpa/inet.h>
 #include <limits.h>
 #include <fcntl.h>
 #include <netinet/in.h>
@@ -44,6 +45,11 @@
 #include <string.h>
 #include <errno.h>
 
+#include <grpc/support/host_port.h>
+#include <grpc/support/string.h>
+#include <grpc/support/log.h>
+#include <grpc/support/port_platform.h>
+
 /* set a socket to non blocking mode */
 int grpc_set_socket_nonblocking(int fd, int non_blocking) {
   int oldflags = fcntl(fd, F_GETFL, 0);
@@ -103,3 +109,157 @@
          0 == getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &newval, &intlen) &&
          newval == val;
 }
+
+/* This should be 0 in production, but it may be enabled for testing or
+   debugging purposes, to simulate an environment where IPv6 sockets can't
+   also speak IPv4. */
+int grpc_forbid_dualstack_sockets_for_testing = 0;
+
+static int set_socket_dualstack(int fd) {
+  if (!grpc_forbid_dualstack_sockets_for_testing) {
+    const int off = 0;
+    return 0 == setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &off, sizeof(off));
+  } else {
+    /* Force an IPv6-only socket, for testing purposes. */
+    const int on = 1;
+    setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on));
+    return 0;
+  }
+}
+
+int grpc_create_dualstack_socket(const struct sockaddr *addr, int type,
+                                 int protocol, grpc_dualstack_mode *dsmode) {
+  int family = addr->sa_family;
+  if (family == AF_INET6) {
+    int fd = socket(family, type, protocol);
+    /* Check if we've got a valid dualstack socket. */
+    if (fd >= 0 && set_socket_dualstack(fd)) {
+      *dsmode = GRPC_DSMODE_DUALSTACK;
+      return fd;
+    }
+    /* If this isn't an IPv4 address, then return whatever we've got. */
+    if (!grpc_sockaddr_is_v4mapped(addr, NULL)) {
+      *dsmode = GRPC_DSMODE_IPV6;
+      return fd;
+    }
+    /* Fall back to AF_INET. */
+    if (fd >= 0) {
+      close(fd);
+    }
+    family = AF_INET;
+  }
+  *dsmode = family == AF_INET ? GRPC_DSMODE_IPV4 : GRPC_DSMODE_NONE;
+  return socket(family, type, protocol);
+}
+
+static const gpr_uint8 kV4MappedPrefix[] = {0, 0, 0, 0, 0,    0,
+                                            0, 0, 0, 0, 0xff, 0xff};
+
+int grpc_sockaddr_is_v4mapped(const struct sockaddr *addr,
+                              struct sockaddr_in *addr4_out) {
+  GPR_ASSERT(addr != (struct sockaddr *)addr4_out);
+  if (addr->sa_family == AF_INET6) {
+    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
+    if (memcmp(addr6->sin6_addr.s6_addr, kV4MappedPrefix,
+               sizeof(kV4MappedPrefix)) == 0) {
+      if (addr4_out != NULL) {
+        /* Normalize ::ffff:0.0.0.0/96 to IPv4. */
+        memset(addr4_out, 0, sizeof(*addr4_out));
+        addr4_out->sin_family = AF_INET;
+        /* s6_addr32 would be nice, but it's non-standard. */
+        memcpy(&addr4_out->sin_addr, &addr6->sin6_addr.s6_addr[12], 4);
+        addr4_out->sin_port = addr6->sin6_port;
+      }
+      return 1;
+    }
+  }
+  return 0;
+}
+
+int grpc_sockaddr_to_v4mapped(const struct sockaddr *addr,
+                              struct sockaddr_in6 *addr6_out) {
+  GPR_ASSERT(addr != (struct sockaddr *)addr6_out);
+  if (addr->sa_family == AF_INET) {
+    const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
+    memset(addr6_out, 0, sizeof(*addr6_out));
+    addr6_out->sin6_family = AF_INET6;
+    memcpy(&addr6_out->sin6_addr.s6_addr[0], kV4MappedPrefix, 12);
+    memcpy(&addr6_out->sin6_addr.s6_addr[12], &addr4->sin_addr, 4);
+    addr6_out->sin6_port = addr4->sin_port;
+    return 1;
+  }
+  return 0;
+}
+
+int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out) {
+  struct sockaddr_in addr4_normalized;
+  if (grpc_sockaddr_is_v4mapped(addr, &addr4_normalized)) {
+    addr = (struct sockaddr *)&addr4_normalized;
+  }
+  if (addr->sa_family == AF_INET) {
+    /* Check for 0.0.0.0 */
+    const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
+    if (addr4->sin_addr.s_addr != 0) {
+      return 0;
+    }
+    *port_out = ntohs(addr4->sin_port);
+    return 1;
+  } else if (addr->sa_family == AF_INET6) {
+    /* Check for :: */
+    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
+    int i;
+    for (i = 0; i < 16; i++) {
+      if (addr6->sin6_addr.s6_addr[i] != 0) {
+        return 0;
+      }
+    }
+    *port_out = ntohs(addr6->sin6_port);
+    return 1;
+  } else {
+    return 0;
+  }
+}
+
+void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out,
+                                  struct sockaddr_in6 *wild6_out) {
+  memset(wild4_out, 0, sizeof(*wild4_out));
+  wild4_out->sin_family = AF_INET;
+  wild4_out->sin_port = htons(port);
+
+  memset(wild6_out, 0, sizeof(*wild6_out));
+  wild6_out->sin6_family = AF_INET6;
+  wild6_out->sin6_port = htons(port);
+}
+
+int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr,
+                            int normalize) {
+  const int save_errno = errno;
+  struct sockaddr_in addr_normalized;
+  char ntop_buf[INET6_ADDRSTRLEN];
+  const void *ip = NULL;
+  int port;
+  int ret;
+
+  *out = NULL;
+  if (normalize && grpc_sockaddr_is_v4mapped(addr, &addr_normalized)) {
+    addr = (const struct sockaddr *)&addr_normalized;
+  }
+  if (addr->sa_family == AF_INET) {
+    const struct sockaddr_in *addr4 = (const struct sockaddr_in *)addr;
+    ip = &addr4->sin_addr;
+    port = ntohs(addr4->sin_port);
+  } else if (addr->sa_family == AF_INET6) {
+    const struct sockaddr_in6 *addr6 = (const struct sockaddr_in6 *)addr;
+    ip = &addr6->sin6_addr;
+    port = ntohs(addr6->sin6_port);
+  }
+  if (ip != NULL &&
+      inet_ntop(addr->sa_family, ip, ntop_buf, sizeof(ntop_buf)) != NULL) {
+    ret = gpr_join_host_port(out, ntop_buf, port);
+  } else {
+    ret = gpr_asprintf(out, "(sockaddr family=%d)", addr->sa_family);
+  }
+  /* This is probably redundant, but we wouldn't want to log the wrong error. */
+  errno = save_errno;
+  return ret;
+}
diff --git a/src/core/endpoint/socket_utils.h b/src/core/endpoint/socket_utils.h
index 545d678..23fa192 100644
--- a/src/core/endpoint/socket_utils.h
+++ b/src/core/endpoint/socket_utils.h
@@ -38,6 +38,8 @@
 #include <sys/socket.h>
 
 struct sockaddr;
+struct sockaddr_in;
+struct sockaddr_in6;
 
 /* a wrapper for accept or accept4 */
 int grpc_accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen,
@@ -55,4 +57,82 @@
 /* disable nagle */
 int grpc_set_socket_low_latency(int fd, int low_latency);
 
+/* An enum to keep track of IPv4/IPv6 socket modes.
+
+   Currently, this information is only used when a socket is first created, but
+   in the future we may wish to store it alongside the fd.  This would let calls
+   like sendto() know which family to use without asking the kernel first. */
+typedef enum grpc_dualstack_mode {
+  /* Uninitialized, or a non-IP socket. */
+  GRPC_DSMODE_NONE,
+  /* AF_INET only. */
+  GRPC_DSMODE_IPV4,
+  /* AF_INET6 only, because IPV6_V6ONLY could not be cleared. */
+  GRPC_DSMODE_IPV6,
+  /* AF_INET6, which also supports ::ffff-mapped IPv4 addresses. */
+  GRPC_DSMODE_DUALSTACK
+} grpc_dualstack_mode;
+
+/* Only tests should use this flag. */
+extern int grpc_forbid_dualstack_sockets_for_testing;
+
+/* Creates a new socket for connecting to (or listening on) an address.
+
+   If addr is AF_INET6, this creates an IPv6 socket first.  If that fails,
+   and addr is within ::ffff:0.0.0.0/96, then it automatically falls back to
+   an IPv4 socket.
+
+   If addr is AF_INET, AF_UNIX, or anything else, then this is similar to
+   calling socket() directly.
+
+   Returns an fd on success, otherwise returns -1 with errno set to the result
+   of a failed socket() call.
+
+   The *dsmode output indicates which address family was actually created.
+   The recommended way to use this is:
+   - First convert to IPv6 using grpc_sockaddr_to_v4mapped().
+   - Create the socket.
+   - If *dsmode is IPV4, use grpc_sockaddr_is_v4mapped() to convert back to
+     IPv4, so that bind() or connect() see the correct family.
+   Also, it's important to distinguish between DUALSTACK and IPV6 when
+   listening on the [::] wildcard address. */
+int grpc_create_dualstack_socket(const struct sockaddr *addr, int type,
+                                 int protocol, grpc_dualstack_mode *dsmode);
+
+/* Returns true if addr is an IPv4-mapped IPv6 address within the
+   ::ffff:0.0.0.0/96 range, or false otherwise.
+
+   If addr4_out is non-NULL, the inner IPv4 address will be copied here when
+   returning true. */
+int grpc_sockaddr_is_v4mapped(const struct sockaddr *addr,
+                              struct sockaddr_in *addr4_out);
+
+/* If addr is an AF_INET address, writes the corresponding ::ffff:0.0.0.0/96
+   address to addr6_out and returns true.  Otherwise returns false. */
+int grpc_sockaddr_to_v4mapped(const struct sockaddr *addr,
+                              struct sockaddr_in6 *addr6_out);
+
+/* If addr is ::, 0.0.0.0, or ::ffff:0.0.0.0, writes the port number to
+   *port_out (if not NULL) and returns true, otherwise returns false. */
+int grpc_sockaddr_is_wildcard(const struct sockaddr *addr, int *port_out);
+
+/* Writes 0.0.0.0:port and [::]:port to separate sockaddrs. */
+void grpc_sockaddr_make_wildcards(int port, struct sockaddr_in *wild4_out,
+                                  struct sockaddr_in6 *wild6_out);
+
+/* Converts a sockaddr into a newly-allocated human-readable string.
+
+   Currently, only the AF_INET and AF_INET6 families are recognized.
+   If the normalize flag is enabled, ::ffff:0.0.0.0/96 IPv6 addresses are
+   displayed as plain IPv4.
+
+   Usage is similar to gpr_asprintf: returns the number of bytes written
+   (excluding the final '\0'), and *out points to a string which must later be
+   destroyed using gpr_free().
+
+   In the unlikely event of an error, returns -1 and sets *out to NULL.
+   The existing value of errno is always preserved. */
+int grpc_sockaddr_to_string(char **out, const struct sockaddr *addr,
+                            int normalize);
+
 #endif  /* __GRPC_INTERNAL_ENDPOINT_SOCKET_UTILS_H__ */
diff --git a/src/core/endpoint/tcp.c b/src/core/endpoint/tcp.c
index 39367e8..482344d 100644
--- a/src/core/endpoint/tcp.c
+++ b/src/core/endpoint/tcp.c
@@ -250,7 +250,7 @@
 typedef struct {
   grpc_endpoint base;
   grpc_em *em;
-  grpc_em_fd em_fd;
+  grpc_em_fd *em_fd;
   int fd;
   size_t slice_size;
   gpr_refcount refcount;
@@ -277,14 +277,14 @@
 
 static void grpc_tcp_shutdown(grpc_endpoint *ep) {
   grpc_tcp *tcp = (grpc_tcp *)ep;
-  grpc_em_fd_shutdown(&tcp->em_fd);
+  grpc_em_fd_shutdown(tcp->em_fd);
 }
 
 static void grpc_tcp_unref(grpc_tcp *tcp) {
   int refcount_zero = gpr_unref(&tcp->refcount);
   if (refcount_zero) {
-    grpc_em_fd_destroy(&tcp->em_fd);
-    close(tcp->fd);
+    grpc_em_fd_destroy(tcp->em_fd);
+    gpr_free(tcp->em_fd);
     gpr_free(tcp);
   }
 }
@@ -385,7 +385,7 @@
         } else {
           /* Spurious read event, consume it here */
           slice_state_destroy(&read_state);
-          grpc_em_fd_notify_on_read(&tcp->em_fd, grpc_tcp_handle_read, tcp,
+          grpc_em_fd_notify_on_read(tcp->em_fd, grpc_tcp_handle_read, tcp,
                                     tcp->read_deadline);
         }
       } else {
@@ -422,7 +422,7 @@
   tcp->read_user_data = user_data;
   tcp->read_deadline = deadline;
   gpr_ref(&tcp->refcount);
-  grpc_em_fd_notify_on_read(&tcp->em_fd, grpc_tcp_handle_read, tcp, deadline);
+  grpc_em_fd_notify_on_read(tcp->em_fd, grpc_tcp_handle_read, tcp, deadline);
 }
 
 #define MAX_WRITE_IOVEC 16
@@ -494,7 +494,7 @@
 
   write_status = grpc_tcp_flush(tcp);
   if (write_status == GRPC_ENDPOINT_WRITE_PENDING) {
-    grpc_em_fd_notify_on_write(&tcp->em_fd, grpc_tcp_handle_write, tcp,
+    grpc_em_fd_notify_on_write(tcp->em_fd, grpc_tcp_handle_write, tcp,
                                tcp->write_deadline);
   } else {
     slice_state_destroy(&tcp->write_state);
@@ -539,7 +539,7 @@
     tcp->write_cb = cb;
     tcp->write_user_data = user_data;
     tcp->write_deadline = deadline;
-    grpc_em_fd_notify_on_write(&tcp->em_fd, grpc_tcp_handle_write, tcp,
+    grpc_em_fd_notify_on_write(tcp->em_fd, grpc_tcp_handle_write, tcp,
                                tcp->write_deadline);
   }
 
@@ -550,11 +550,12 @@
                                             grpc_tcp_write, grpc_tcp_shutdown,
                                             grpc_tcp_destroy};
 
-grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size) {
+static grpc_endpoint *grpc_tcp_create_generic(grpc_em_fd *em_fd,
+                                              size_t slice_size) {
   grpc_tcp *tcp = (grpc_tcp *)gpr_malloc(sizeof(grpc_tcp));
   tcp->base.vtable = &vtable;
-  tcp->fd = fd;
-  tcp->em = em;
+  tcp->fd = grpc_em_fd_get(em_fd);
+  tcp->em = grpc_em_fd_get_em(em_fd);
   tcp->read_cb = NULL;
   tcp->write_cb = NULL;
   tcp->read_user_data = NULL;
@@ -565,6 +566,16 @@
   slice_state_init(&tcp->write_state, NULL, 0, 0);
   /* paired with unref in grpc_tcp_destroy */
   gpr_ref_init(&tcp->refcount, 1);
-  grpc_em_fd_init(&tcp->em_fd, tcp->em, fd);
+  tcp->em_fd = em_fd;
   return &tcp->base;
 }
+
+grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size) {
+  grpc_em_fd *em_fd = gpr_malloc(sizeof(grpc_em_fd));
+  grpc_em_fd_init(em_fd, em, fd);
+  return grpc_tcp_create_generic(em_fd, slice_size);
+}
+
+grpc_endpoint *grpc_tcp_create_emfd(grpc_em_fd *em_fd) {
+  return grpc_tcp_create_generic(em_fd, DEFAULT_SLICE_SIZE);
+}
diff --git a/src/core/endpoint/tcp.h b/src/core/endpoint/tcp.h
index 6507b2f..f6a2a19 100644
--- a/src/core/endpoint/tcp.h
+++ b/src/core/endpoint/tcp.h
@@ -52,4 +52,8 @@
 /* Special version for debugging slice changes */
 grpc_endpoint *grpc_tcp_create_dbg(int fd, grpc_em *em, size_t slice_size);
 
+/* Special version for handing off ownership of an existing already created
+   eventmanager fd. Must not have any outstanding callbacks. */
+grpc_endpoint *grpc_tcp_create_emfd(grpc_em_fd *em_fd);
+
 #endif  /* __GRPC_INTERNAL_ENDPOINT_TCP_H__ */
diff --git a/src/core/endpoint/tcp_client.c b/src/core/endpoint/tcp_client.c
index 01a0c3f..c6f470b 100644
--- a/src/core/endpoint/tcp_client.c
+++ b/src/core/endpoint/tcp_client.c
@@ -34,6 +34,7 @@
 #include "src/core/endpoint/tcp_client.h"
 
 #include <errno.h>
+#include <netinet/in.h>
 #include <string.h>
 #include <unistd.h>
 
@@ -45,14 +46,12 @@
 typedef struct {
   void (*cb)(void *arg, grpc_endpoint *tcp);
   void *cb_arg;
-  grpc_em_fd fd;
+  grpc_em_fd *fd;
   gpr_timespec deadline;
 } async_connect;
 
-static int create_fd(int address_family) {
-  int fd = socket(address_family, SOCK_STREAM, 0);
+static int prepare_socket(int fd) {
   if (fd < 0) {
-    gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno));
     goto error;
   }
 
@@ -63,13 +62,13 @@
     goto error;
   }
 
-  return fd;
+  return 1;
 
 error:
   if (fd >= 0) {
     close(fd);
   }
-  return -1;
+  return 0;
 }
 
 static void on_writable(void *acp, grpc_em_cb_status status) {
@@ -77,8 +76,7 @@
   int so_error = 0;
   socklen_t so_error_size;
   int err;
-  int fd = grpc_em_fd_get(&ac->fd);
-  grpc_em *em = grpc_em_fd_get_em(&ac->fd);
+  int fd = grpc_em_fd_get(ac->fd);
 
   if (status == GRPC_CALLBACK_SUCCESS) {
     do {
@@ -105,7 +103,7 @@
            opened too many network connections.  The "easy" fix:
            don't do that! */
         gpr_log(GPR_ERROR, "kernel out of buffers");
-        grpc_em_fd_notify_on_write(&ac->fd, on_writable, ac, ac->deadline);
+        grpc_em_fd_notify_on_write(ac->fd, on_writable, ac, ac->deadline);
         return;
       } else {
         goto error;
@@ -122,31 +120,50 @@
 
 error:
   ac->cb(ac->cb_arg, NULL);
-  grpc_em_fd_destroy(&ac->fd);
+  grpc_em_fd_destroy(ac->fd);
+  gpr_free(ac->fd);
   gpr_free(ac);
-  close(fd);
   return;
 
 great_success:
-  grpc_em_fd_destroy(&ac->fd);
-  ac->cb(ac->cb_arg, grpc_tcp_create(fd, em));
+  ac->cb(ac->cb_arg, grpc_tcp_create_emfd(ac->fd));
   gpr_free(ac);
 }
 
 void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *ep),
-                             void *arg, grpc_em *em, struct sockaddr *addr,
-                             int len, gpr_timespec deadline) {
-  int fd = create_fd(addr->sa_family);
+                             void *arg, grpc_em *em,
+                             const struct sockaddr *addr, int addr_len,
+                             gpr_timespec deadline) {
+  int fd;
+  grpc_dualstack_mode dsmode;
   int err;
   async_connect *ac;
+  struct sockaddr_in6 addr6_v4mapped;
+  struct sockaddr_in addr4_copy;
 
+  /* Use dualstack sockets where available. */
+  if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) {
+    addr = (const struct sockaddr *)&addr6_v4mapped;
+    addr_len = sizeof(addr6_v4mapped);
+  }
+
+  fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode);
   if (fd < 0) {
+    gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno));
+  }
+  if (dsmode == GRPC_DSMODE_IPV4) {
+    /* If we got an AF_INET socket, map the address back to IPv4. */
+    GPR_ASSERT(grpc_sockaddr_is_v4mapped(addr, &addr4_copy));
+    addr = (struct sockaddr *)&addr4_copy;
+    addr_len = sizeof(addr4_copy);
+  }
+  if (!prepare_socket(fd)) {
     cb(arg, NULL);
     return;
   }
 
   do {
-    err = connect(fd, addr, len);
+    err = connect(fd, addr, addr_len);
   } while (err < 0 && errno == EINTR);
 
   if (err >= 0) {
@@ -165,6 +182,7 @@
   ac->cb = cb;
   ac->cb_arg = arg;
   ac->deadline = deadline;
-  grpc_em_fd_init(&ac->fd, em, fd);
-  grpc_em_fd_notify_on_write(&ac->fd, on_writable, ac, deadline);
+  ac->fd = gpr_malloc(sizeof(grpc_em_fd));
+  grpc_em_fd_init(ac->fd, em, fd);
+  grpc_em_fd_notify_on_write(ac->fd, on_writable, ac, deadline);
 }
diff --git a/src/core/endpoint/tcp_client.h b/src/core/endpoint/tcp_client.h
index 2a8b8ee..69b1b62 100644
--- a/src/core/endpoint/tcp_client.h
+++ b/src/core/endpoint/tcp_client.h
@@ -44,7 +44,8 @@
    cb with arg and the completed connection when done (or call cb with arg and
    NULL on failure) */
 void grpc_tcp_client_connect(void (*cb)(void *arg, grpc_endpoint *tcp),
-                             void *arg, grpc_em *em, struct sockaddr *addr,
-                             int len, gpr_timespec deadline);
+                             void *arg, grpc_em *em,
+                             const struct sockaddr *addr, int addr_len,
+                             gpr_timespec deadline);
 
 #endif  /* __GRPC_INTERNAL_ENDPOINT_TCP_CLIENT_H__ */
diff --git a/src/core/endpoint/tcp_server.c b/src/core/endpoint/tcp_server.c
index 2f386ce..efd3ded 100644
--- a/src/core/endpoint/tcp_server.c
+++ b/src/core/endpoint/tcp_server.c
@@ -114,7 +114,6 @@
     server_port *sp = &s->ports[i];
     grpc_em_fd_destroy(sp->emfd);
     gpr_free(sp->emfd);
-    close(sp->fd);
   }
   gpr_free(s->ports);
   gpr_free(s);
@@ -153,11 +152,9 @@
   return s_max_accept_queue_size;
 }
 
-/* create a socket to listen with */
-static int create_listening_socket(struct sockaddr *port, int len) {
-  int fd = socket(port->sa_family, SOCK_STREAM, 0);
+/* Prepare a recently-created socket for listening. */
+static int prepare_socket(int fd, const struct sockaddr *addr, int addr_len) {
   if (fd < 0) {
-    gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno));
     goto error;
   }
 
@@ -169,8 +166,11 @@
     goto error;
   }
 
-  if (bind(fd, port, len) < 0) {
-    gpr_log(GPR_ERROR, "bind: %s", strerror(errno));
+  if (bind(fd, addr, addr_len) < 0) {
+    char *addr_str;
+    grpc_sockaddr_to_string(&addr_str, addr, 0);
+    gpr_log(GPR_ERROR, "bind addr=%s: %s", addr_str, strerror(errno));
+    gpr_free(addr_str);
     goto error;
   }
 
@@ -179,13 +179,13 @@
     goto error;
   }
 
-  return fd;
+  return 1;
 
 error:
   if (fd >= 0) {
     close(fd);
   }
-  return -1;
+  return 0;
 }
 
 /* event manager callback when reads are ready */
@@ -200,6 +200,8 @@
   for (;;) {
     struct sockaddr_storage addr;
     socklen_t addrlen = sizeof(addr);
+    /* Note: If we ever decide to return this address to the user, remember to
+             strip off the ::ffff:0.0.0.0/96 prefix first. */
     int fd = grpc_accept4(sp->fd, (struct sockaddr *)&addr, &addrlen, 1, 1);
     if (fd < 0) {
       switch (errno) {
@@ -231,13 +233,12 @@
   gpr_mu_unlock(&sp->server->mu);
 }
 
-int grpc_tcp_server_add_port(grpc_tcp_server *s, struct sockaddr *port,
-                             int len) {
+static int add_socket_to_server(grpc_tcp_server *s, int fd,
+                                const struct sockaddr *addr, int addr_len) {
   server_port *sp;
-  /* create a socket */
-  int fd = create_listening_socket(port, len);
-  if (fd < 0) {
-    return -1;
+
+  if (!prepare_socket(fd, addr, addr_len)) {
+    return 0;
   }
 
   gpr_mu_lock(&s->mu);
@@ -257,11 +258,62 @@
     gpr_free(sp->emfd);
     s->nports--;
     gpr_mu_unlock(&s->mu);
-    return -1;
+    return 0;
   }
   gpr_mu_unlock(&s->mu);
 
-  return fd;
+  return 1;
+}
+
+int grpc_tcp_server_add_port(grpc_tcp_server *s, const struct sockaddr *addr,
+                             int addr_len) {
+  int ok = 0;
+  int fd;
+  grpc_dualstack_mode dsmode;
+  struct sockaddr_in6 addr6_v4mapped;
+  struct sockaddr_in wild4;
+  struct sockaddr_in6 wild6;
+  struct sockaddr_in addr4_copy;
+  int port;
+
+  if (grpc_sockaddr_to_v4mapped(addr, &addr6_v4mapped)) {
+    addr = (const struct sockaddr *)&addr6_v4mapped;
+    addr_len = sizeof(addr6_v4mapped);
+  }
+
+  /* Treat :: or 0.0.0.0 as a family-agnostic wildcard. */
+  if (grpc_sockaddr_is_wildcard(addr, &port)) {
+    grpc_sockaddr_make_wildcards(port, &wild4, &wild6);
+
+    /* Try listening on IPv6 first. */
+    addr = (struct sockaddr *)&wild6;
+    addr_len = sizeof(wild6);
+    fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode);
+    ok |= add_socket_to_server(s, fd, addr, addr_len);
+    if (fd >= 0 && dsmode == GRPC_DSMODE_DUALSTACK) {
+      return ok;
+    }
+
+    /* If we didn't get a dualstack socket, also listen on 0.0.0.0. */
+    addr = (struct sockaddr *)&wild4;
+    addr_len = sizeof(wild4);
+  }
+
+  fd = grpc_create_dualstack_socket(addr, SOCK_STREAM, 0, &dsmode);
+  if (fd < 0) {
+    gpr_log(GPR_ERROR, "Unable to create socket: %s", strerror(errno));
+  }
+  if (dsmode == GRPC_DSMODE_IPV4 &&
+      grpc_sockaddr_is_v4mapped(addr, &addr4_copy)) {
+    addr = (struct sockaddr *)&addr4_copy;
+    addr_len = sizeof(addr4_copy);
+  }
+  ok |= add_socket_to_server(s, fd, addr, addr_len);
+  return ok;
+}
+
+int grpc_tcp_server_get_fd(grpc_tcp_server *s, int index) {
+  return (0 <= index && index < s->nports) ? s->ports[index].fd : -1;
 }
 
 void grpc_tcp_server_start(grpc_tcp_server *s, grpc_tcp_server_cb cb,
diff --git a/src/core/endpoint/tcp_server.h b/src/core/endpoint/tcp_server.h
index 99cb83e..d81cdd0 100644
--- a/src/core/endpoint/tcp_server.h
+++ b/src/core/endpoint/tcp_server.h
@@ -53,11 +53,23 @@
 void grpc_tcp_server_start(grpc_tcp_server *server, grpc_tcp_server_cb cb,
                            void *cb_arg);
 
-/* Add a port to the server, returns a file descriptor on success, or <0 on
-   failure; the file descriptor remains owned by the server and will be cleaned
-   up when grpc_tcp_server_destroy is called */
-int grpc_tcp_server_add_port(grpc_tcp_server *server, struct sockaddr *port,
-                             int len);
+/* Add a port to the server, returning true on success, or false otherwise.
+
+   The :: and 0.0.0.0 wildcard addresses are treated identically, accepting
+   both IPv4 and IPv6 connections, but :: is the preferred style.  This usually
+   creates one socket, but possibly two on systems which support IPv6,
+   but not dualstack sockets.
+
+   For raw access to the underlying sockets, see grpc_tcp_server_get_fd(). */
+int grpc_tcp_server_add_port(grpc_tcp_server *s, const struct sockaddr *addr,
+                             int addr_len);
+
+/* Returns the file descriptor of the Nth listening socket on this server,
+   or -1 if the index is out of bounds.
+
+   The file descriptor remains owned by the server, and will be cleaned
+   up when grpc_tcp_server_destroy is called. */
+int grpc_tcp_server_get_fd(grpc_tcp_server *s, int index);
 
 void grpc_tcp_server_destroy(grpc_tcp_server *server);
 
diff --git a/src/core/eventmanager/em.c b/src/core/eventmanager/em.c
index f16473b..36f3720 100644
--- a/src/core/eventmanager/em.c
+++ b/src/core/eventmanager/em.c
@@ -537,6 +537,8 @@
   em->num_fds--;
   gpr_cv_broadcast(&em->cv);
   gpr_mu_unlock(&em->mu);
+
+  close(em_fd->fd);
 }
 
 int grpc_em_fd_get(struct grpc_em_fd *em_fd) { return em_fd->fd; }
diff --git a/src/core/eventmanager/em.h b/src/core/eventmanager/em.h
index 6f8bded..aa439d1 100644
--- a/src/core/eventmanager/em.h
+++ b/src/core/eventmanager/em.h
@@ -146,10 +146,12 @@
    initialized *em_fd.
    fd is a non-blocking file descriptor.
 
+   This takes ownership of closing fd.
+
    Requires:  *em_fd uninitialized. fd is a non-blocking file descriptor.  */
 grpc_em_error grpc_em_fd_init(grpc_em_fd *em_fd, grpc_em *em, int fd);
 
-/* Cause *em_fd no longer to be initialized.
+/* Cause *em_fd no longer to be initialized and closes the underlying fd.
    Requires: *em_fd initialized; no outstanding notify_on_read or
    notify_on_write.  */
 void grpc_em_fd_destroy(grpc_em_fd *em_fd);
diff --git a/src/core/security/auth.c b/src/core/security/auth.c
index 6480dd2..9ce0c69 100644
--- a/src/core/security/auth.c
+++ b/src/core/security/auth.c
@@ -75,7 +75,7 @@
   switch (op->type) {
     case GRPC_SEND_START: {
       grpc_credentials *channel_creds =
-          channeld->security_context->request_metadata_only_creds;
+          channeld->security_context->request_metadata_creds;
       /* TODO(jboeuf):
          Decide on the policy in this case:
          - populate both channel and call?
diff --git a/src/core/security/credentials.c b/src/core/security/credentials.c
index 969a973..7ff48f9 100644
--- a/src/core/security/credentials.c
+++ b/src/core/security/credentials.c
@@ -47,12 +47,10 @@
 #include <stdio.h>
 
 /* -- Constants. -- */
-
 #define GRPC_COMPUTE_ENGINE_TOKEN_REFRESH_THRESHOLD_SECS 60
 #define GRPC_COMPUTE_ENGINE_METADATA_HOST "metadata"
 #define GRPC_COMPUTE_ENGINE_METADATA_TOKEN_PATH \
   "computeMetadata/v1/instance/service-accounts/default/token"
-#define GRPC_AUTHORIZATION_METADATA_KEY "Authorization"
 
 /* -- Common. -- */
 
@@ -108,7 +106,13 @@
 void grpc_credentials_get_request_metadata(grpc_credentials *creds,
                                            grpc_credentials_metadata_cb cb,
                                            void *user_data) {
-  if (creds == NULL || !grpc_credentials_has_request_metadata(creds)) return;
+  if (creds == NULL || !grpc_credentials_has_request_metadata(creds) ||
+      creds->vtable->get_request_metadata == NULL) {
+    if (cb != NULL) {
+      cb(user_data, NULL, 0, GRPC_CREDENTIALS_OK);
+    }
+    return;
+  }
   creds->vtable->get_request_metadata(creds, cb, user_data);
 }
 
@@ -521,14 +525,235 @@
   return c;
 }
 
+/* -- Composite credentials. -- */
 
-/* -- Composite credentials TODO(jboeuf). -- */
+typedef struct {
+  grpc_credentials base;
+  grpc_credentials_array inner;
+} grpc_composite_credentials;
+
+typedef struct {
+  grpc_composite_credentials *composite_creds;
+  size_t creds_index;
+  grpc_mdelem **md_elems;
+  size_t num_md;
+  void *user_data;
+  grpc_credentials_metadata_cb cb;
+} grpc_composite_credentials_metadata_context;
+
+static void composite_destroy(grpc_credentials *creds) {
+  grpc_composite_credentials *c = (grpc_composite_credentials *)creds;
+  size_t i;
+  for (i = 0; i < c->inner.num_creds; i++) {
+    grpc_credentials_unref(c->inner.creds_array[i]);
+  }
+  gpr_free(c->inner.creds_array);
+  gpr_free(creds);
+}
+
+static int composite_has_request_metadata(const grpc_credentials *creds) {
+  const grpc_composite_credentials *c =
+      (const grpc_composite_credentials *)creds;
+  size_t i;
+  for (i = 0; i < c->inner.num_creds; i++) {
+    if (grpc_credentials_has_request_metadata(c->inner.creds_array[i])) {
+      return 1;
+    }
+  }
+  return 0;
+}
+
+static int composite_has_request_metadata_only(const grpc_credentials *creds) {
+  const grpc_composite_credentials *c =
+      (const grpc_composite_credentials *)creds;
+  size_t i;
+  for (i = 0; i < c->inner.num_creds; i++) {
+    if (!grpc_credentials_has_request_metadata_only(c->inner.creds_array[i])) {
+      return 0;
+    }
+  }
+  return 1;
+}
+
+static void composite_md_context_destroy(
+    grpc_composite_credentials_metadata_context *ctx) {
+  size_t i;
+  for (i = 0; i < ctx->num_md; i++) {
+    grpc_mdelem_unref(ctx->md_elems[i]);
+  }
+  gpr_free(ctx->md_elems);
+  gpr_free(ctx);
+}
+
+static void composite_metadata_cb(void *user_data, grpc_mdelem **md_elems,
+                                  size_t num_md,
+                                  grpc_credentials_status status) {
+  grpc_composite_credentials_metadata_context *ctx =
+      (grpc_composite_credentials_metadata_context *)user_data;
+  size_t i;
+  if (status != GRPC_CREDENTIALS_OK) {
+    ctx->cb(ctx->user_data, NULL, 0, status);
+    return;
+  }
+
+  /* Copy the metadata in the context. */
+  if (num_md > 0) {
+    ctx->md_elems = gpr_realloc(ctx->md_elems,
+                                (ctx->num_md + num_md) * sizeof(grpc_mdelem *));
+    for (i = 0; i < num_md; i++) {
+      ctx->md_elems[i + ctx->num_md] = grpc_mdelem_ref(md_elems[i]);
+    }
+    ctx->num_md += num_md;
+  }
+
+  /* See if we need to get some more metadata. */
+  while (ctx->creds_index < ctx->composite_creds->inner.num_creds) {
+    grpc_credentials *inner_creds =
+        ctx->composite_creds->inner.creds_array[ctx->creds_index++];
+    if (grpc_credentials_has_request_metadata(inner_creds)) {
+      grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb,
+                                            ctx);
+      return;
+    }
+  }
+
+  /* We're done!. */
+  ctx->cb(ctx->user_data, ctx->md_elems, ctx->num_md, GRPC_CREDENTIALS_OK);
+  composite_md_context_destroy(ctx);
+}
+
+static void composite_get_request_metadata(grpc_credentials *creds,
+                                           grpc_credentials_metadata_cb cb,
+                                           void *user_data) {
+  grpc_composite_credentials *c = (grpc_composite_credentials *)creds;
+  grpc_composite_credentials_metadata_context *ctx;
+  if (!grpc_credentials_has_request_metadata(creds)) {
+    cb(user_data, NULL, 0, GRPC_CREDENTIALS_OK);
+    return;
+  }
+  ctx = gpr_malloc(sizeof(grpc_composite_credentials_metadata_context));
+  memset(ctx, 0, sizeof(grpc_composite_credentials_metadata_context));
+  ctx->user_data = user_data;
+  ctx->cb = cb;
+  ctx->composite_creds = c;
+  while (ctx->creds_index < c->inner.num_creds) {
+    grpc_credentials *inner_creds = c->inner.creds_array[ctx->creds_index++];
+    if (grpc_credentials_has_request_metadata(inner_creds)) {
+      grpc_credentials_get_request_metadata(inner_creds, composite_metadata_cb,
+                                            ctx);
+      return;
+    }
+  }
+  GPR_ASSERT(0); /* Should have exited before. */
+}
+
+static grpc_credentials_vtable composite_credentials_vtable = {
+    composite_destroy, composite_has_request_metadata,
+    composite_has_request_metadata_only, composite_get_request_metadata};
+
+static grpc_credentials_array get_creds_array(grpc_credentials **creds_addr) {
+  grpc_credentials_array result;
+  grpc_credentials *creds = *creds_addr;
+  result.creds_array = creds_addr;
+  result.num_creds = 1;
+  if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE)) {
+    result = *grpc_composite_credentials_get_credentials(creds);
+  }
+  return result;
+}
 
 grpc_credentials *grpc_composite_credentials_create(grpc_credentials *creds1,
                                                     grpc_credentials *creds2) {
-  return NULL;
+  size_t i;
+  grpc_credentials_array creds1_array;
+  grpc_credentials_array creds2_array;
+  grpc_composite_credentials *c;
+  GPR_ASSERT(creds1 != NULL);
+  GPR_ASSERT(creds2 != NULL);
+  c = gpr_malloc(sizeof(grpc_composite_credentials));
+  memset(c, 0, sizeof(grpc_composite_credentials));
+  c->base.type = GRPC_CREDENTIALS_TYPE_COMPOSITE;
+  c->base.vtable = &composite_credentials_vtable;
+  gpr_ref_init(&c->base.refcount, 1);
+  creds1_array = get_creds_array(&creds1);
+  creds2_array = get_creds_array(&creds2);
+  c->inner.num_creds = creds1_array.num_creds + creds2_array.num_creds;
+  c->inner.creds_array =
+      gpr_malloc(c->inner.num_creds * sizeof(grpc_credentials *));
+  for (i = 0; i < creds1_array.num_creds; i++) {
+    c->inner.creds_array[i] = grpc_credentials_ref(creds1_array.creds_array[i]);
+  }
+  for (i = 0; i < creds2_array.num_creds; i++) {
+    c->inner.creds_array[i + creds1_array.num_creds] =
+        grpc_credentials_ref(creds2_array.creds_array[i]);
+  }
+  return &c->base;
+}
+
+const grpc_credentials_array *grpc_composite_credentials_get_credentials(
+    grpc_credentials *creds) {
+  const grpc_composite_credentials *c =
+      (const grpc_composite_credentials *)creds;
+  GPR_ASSERT(!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE));
+  return &c->inner;
+}
+
+/* -- IAM credentials. -- */
+
+typedef struct {
+  grpc_credentials base;
+  grpc_mdctx *md_ctx;
+  grpc_mdelem *token_md;
+  grpc_mdelem *authority_selector_md;
+} grpc_iam_credentials;
+
+static void iam_destroy(grpc_credentials *creds) {
+  grpc_iam_credentials *c = (grpc_iam_credentials *)creds;
+  grpc_mdelem_unref(c->token_md);
+  grpc_mdelem_unref(c->authority_selector_md);
+  grpc_mdctx_orphan(c->md_ctx);
+  gpr_free(c);
+}
+
+static int iam_has_request_metadata(const grpc_credentials *creds) { return 1; }
+
+static int iam_has_request_metadata_only(const grpc_credentials *creds) {
+  return 1;
+}
+
+static void iam_get_request_metadata(grpc_credentials *creds,
+                                     grpc_credentials_metadata_cb cb,
+                                     void *user_data) {
+  grpc_iam_credentials *c = (grpc_iam_credentials *)creds;
+  grpc_mdelem *md_array[2];
+  md_array[0] = c->token_md;
+  md_array[1] = c->authority_selector_md;
+  cb(user_data, md_array, 2, GRPC_CREDENTIALS_OK);
+}
+
+static grpc_credentials_vtable iam_vtable = {
+    iam_destroy, iam_has_request_metadata, iam_has_request_metadata_only,
+    iam_get_request_metadata};
+
+grpc_credentials *grpc_iam_credentials_create(const char *token,
+                                              const char *authority_selector) {
+  grpc_iam_credentials *c;
+  GPR_ASSERT(token != NULL);
+  GPR_ASSERT(authority_selector != NULL);
+  c = gpr_malloc(sizeof(grpc_iam_credentials));
+  memset(c, 0, sizeof(grpc_iam_credentials));
+  c->base.type = GRPC_CREDENTIALS_TYPE_IAM;
+  c->base.vtable = &iam_vtable;
+  gpr_ref_init(&c->base.refcount, 1);
+  c->md_ctx = grpc_mdctx_create();
+  c->token_md = grpc_mdelem_from_strings(
+      c->md_ctx, GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY, token);
+  c->authority_selector_md = grpc_mdelem_from_strings(
+      c->md_ctx, GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY, authority_selector);
+  return &c->base;
 }
 
 /* -- Default credentials TODO(jboeuf). -- */
 
 grpc_credentials *grpc_default_credentials_create(void) { return NULL; }
+
diff --git a/src/core/security/credentials.h b/src/core/security/credentials.h
index 1432611..9fb82e1 100644
--- a/src/core/security/credentials.h
+++ b/src/core/security/credentials.h
@@ -50,9 +50,15 @@
 
 #define GRPC_CREDENTIALS_TYPE_SSL "Ssl"
 #define GRPC_CREDENTIALS_TYPE_OAUTH2 "Oauth2"
+#define GRPC_CREDENTIALS_TYPE_IAM "Iam"
 #define GRPC_CREDENTIALS_TYPE_COMPOSITE "Composite"
 #define GRPC_CREDENTIALS_TYPE_FAKE_TRANSPORT_SECURITY "FakeTransportSecurity"
 
+#define GRPC_AUTHORIZATION_METADATA_KEY "Authorization"
+#define GRPC_IAM_AUTHORIZATION_TOKEN_METADATA_KEY \
+  "x-goog-iam-authorization-token"
+#define GRPC_IAM_AUTHORITY_SELECTOR_METADATA_KEY "x-goog-iam-authority-selector"
+
 /* --- grpc_credentials. --- */
 
 typedef void (*grpc_credentials_metadata_cb)(void *user_data,
@@ -94,6 +100,14 @@
 const grpc_ssl_config *grpc_ssl_credentials_get_config(
     const grpc_credentials *ssl_creds);
 
+typedef struct {
+  grpc_credentials **creds_array;
+  size_t num_creds;
+} grpc_credentials_array;
+
+const grpc_credentials_array *grpc_composite_credentials_get_credentials(
+    grpc_credentials *composite_creds);
+
 /* Exposed for testing only. */
 grpc_credentials_status grpc_compute_engine_credentials_parse_server_response(
     const struct grpc_httpcli_response *response, grpc_mdctx *ctx,
diff --git a/src/core/security/security_context.c b/src/core/security/security_context.c
index beda64c..c56692a 100644
--- a/src/core/security/security_context.c
+++ b/src/core/security/security_context.c
@@ -37,6 +37,8 @@
 
 #include "src/core/endpoint/secure_endpoint.h"
 #include "src/core/security/credentials.h"
+#include "src/core/surface/lame_client.h"
+#include "src/core/transport/chttp2/alpn.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/slice_buffer.h>
@@ -47,7 +49,6 @@
 
 /* -- Constants. -- */
 
-#define GRPC_ALPN_PROTOCOL_STRING "h2-15"
 /* Defines the cipher suites that we accept. All these cipher suites are
    compliant with TLS 1.2 and use an RSA public key. We prefer GCM over CBC
    and ECDHE-RSA over just RSA. */
@@ -122,11 +123,11 @@
   return NULL;
 }
 
-static int check_request_metadata_only_creds(grpc_credentials *creds) {
-  if (creds != NULL && !grpc_credentials_has_request_metadata_only(creds)) {
+static int check_request_metadata_creds(grpc_credentials *creds) {
+  if (creds != NULL && !grpc_credentials_has_request_metadata(creds)) {
     gpr_log(GPR_ERROR,
             "Incompatible credentials for channel security context: needs to "
-            "only set request metadata.");
+            "set request metadata.");
     return 0;
   }
   return 1;
@@ -136,7 +137,7 @@
 
 static void fake_channel_destroy(grpc_security_context *ctx) {
   grpc_channel_security_context *c = (grpc_channel_security_context *)ctx;
-  grpc_credentials_unref(c->request_metadata_only_creds);
+  grpc_credentials_unref(c->request_metadata_creds);
   gpr_free(ctx);
 }
 
@@ -191,15 +192,14 @@
     fake_server_destroy, fake_server_create_handshaker, fake_check_peer};
 
 grpc_channel_security_context *grpc_fake_channel_security_context_create(
-    grpc_credentials *request_metadata_only_creds) {
+    grpc_credentials *request_metadata_creds) {
   grpc_channel_security_context *c =
       gpr_malloc(sizeof(grpc_channel_security_context));
   gpr_ref_init(&c->base.refcount, 1);
   c->base.is_client_side = 1;
   c->base.vtable = &fake_channel_vtable;
-  GPR_ASSERT(check_request_metadata_only_creds(request_metadata_only_creds));
-  c->request_metadata_only_creds =
-      grpc_credentials_ref(request_metadata_only_creds);
+  GPR_ASSERT(check_request_metadata_creds(request_metadata_creds));
+  c->request_metadata_creds = grpc_credentials_ref(request_metadata_creds);
   return c;
 }
 
@@ -226,7 +226,7 @@
 static void ssl_channel_destroy(grpc_security_context *ctx) {
   grpc_ssl_channel_security_context *c =
       (grpc_ssl_channel_security_context *)ctx;
-  grpc_credentials_unref(c->base.request_metadata_only_creds);
+  grpc_credentials_unref(c->base.request_metadata_creds);
   if (c->handshaker_factory != NULL) {
     tsi_ssl_handshaker_factory_destroy(c->handshaker_factory);
   }
@@ -282,8 +282,8 @@
     gpr_log(GPR_ERROR, "Invalid or missing selected ALPN property.");
     return GRPC_SECURITY_ERROR;
   }
-  if (strncmp(GRPC_ALPN_PROTOCOL_STRING, p->value.string.data,
-              p->value.string.length)) {
+  if (!grpc_chttp2_is_alpn_version_supported(p->value.string.data,
+                                             p->value.string.length)) {
     gpr_log(GPR_ERROR, "Invalid ALPN value.");
     return GRPC_SECURITY_ERROR;
   }
@@ -320,10 +320,9 @@
     ssl_server_destroy, ssl_server_create_handshaker, ssl_server_check_peer};
 
 grpc_security_status grpc_ssl_channel_security_context_create(
-    grpc_credentials *request_metadata_only_creds,
-    const grpc_ssl_config *config, const char *secure_peer_name,
-    grpc_channel_security_context **ctx) {
-  const char *alpn_protocol_string = GRPC_ALPN_PROTOCOL_STRING;
+    grpc_credentials *request_metadata_creds, const grpc_ssl_config *config,
+    const char *secure_peer_name, grpc_channel_security_context **ctx) {
+  const char *alpn_protocol_string = GRPC_CHTTP2_ALPN_VERSION;
   unsigned char alpn_protocol_string_len =
       (unsigned char)strlen(alpn_protocol_string);
   tsi_result result = TSI_OK;
@@ -334,7 +333,7 @@
     gpr_log(GPR_ERROR, "An ssl channel needs a secure name and root certs.");
     return GRPC_SECURITY_ERROR;
   }
-  if (!check_request_metadata_only_creds(request_metadata_only_creds)) {
+  if (!check_request_metadata_creds(request_metadata_creds)) {
     return GRPC_SECURITY_ERROR;
   }
 
@@ -344,8 +343,7 @@
   gpr_ref_init(&c->base.base.refcount, 1);
   c->base.base.vtable = &ssl_channel_vtable;
   c->base.base.is_client_side = 1;
-  c->base.request_metadata_only_creds =
-      grpc_credentials_ref(request_metadata_only_creds);
+  c->base.request_metadata_creds = grpc_credentials_ref(request_metadata_creds);
   if (secure_peer_name != NULL) {
     c->secure_peer_name = gpr_strdup(secure_peer_name);
   }
@@ -368,7 +366,7 @@
 
 grpc_security_status grpc_ssl_server_security_context_create(
     const grpc_ssl_config *config, grpc_security_context **ctx) {
-  const char *alpn_protocol_string = GRPC_ALPN_PROTOCOL_STRING;
+  const char *alpn_protocol_string = GRPC_CHTTP2_ALPN_VERSION;
   unsigned char alpn_protocol_string_len =
       (unsigned char)strlen(alpn_protocol_string);
   tsi_result result = TSI_OK;
@@ -427,7 +425,7 @@
   status = grpc_ssl_channel_security_context_create(creds, config,
                                                     secure_peer_name, &ctx);
   if (status != GRPC_SECURITY_OK) {
-    return NULL; /* TODO(ctiller): return lame channel. */
+    return grpc_lame_client_channel_create();
   }
   channel = grpc_secure_channel_create_internal(target, args, ctx);
   grpc_security_context_unref(&ctx->base);
@@ -435,13 +433,38 @@
 }
 
 
+static grpc_credentials *get_creds_from_composite(
+    grpc_credentials *composite_creds, const char *type) {
+  size_t i;
+  const grpc_credentials_array *inner_creds_array =
+      grpc_composite_credentials_get_credentials(composite_creds);
+  for (i = 0; i < inner_creds_array->num_creds; i++) {
+    if (!strcmp(type, inner_creds_array->creds_array[i]->type)) {
+      return inner_creds_array->creds_array[i];
+    }
+  }
+  return NULL;
+}
+
+static grpc_channel *grpc_channel_create_from_composite_creds(
+    grpc_credentials *composite_creds, const char *target,
+    const grpc_channel_args *args) {
+  grpc_credentials *creds =
+      get_creds_from_composite(composite_creds, GRPC_CREDENTIALS_TYPE_SSL);
+  if (creds != NULL) {
+    return grpc_ssl_channel_create(
+        composite_creds, grpc_ssl_credentials_get_config(creds), target, args);
+  }
+  return NULL; /* TODO(ctiller): return lame channel. */
+}
+
 grpc_channel *grpc_secure_channel_create(grpc_credentials *creds,
                                          const char *target,
                                          const grpc_channel_args *args) {
   if (grpc_credentials_has_request_metadata_only(creds)) {
     gpr_log(GPR_ERROR,
             "Credentials is insufficient to create a secure channel.");
-    return NULL; /* TODO(ctiller): return lame channel. */
+    return grpc_lame_client_channel_create();
   }
   if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_SSL)) {
     return grpc_ssl_channel_create(NULL, grpc_ssl_credentials_get_config(creds),
@@ -455,11 +478,11 @@
     grpc_security_context_unref(&ctx->base);
     return channel;
   } else if (!strcmp(creds->type, GRPC_CREDENTIALS_TYPE_COMPOSITE)) {
-    return NULL; /* TODO(jboeuf) Implement. */
+    return grpc_channel_create_from_composite_creds(creds, target, args);
   } else {
     gpr_log(GPR_ERROR,
             "Unknown credentials type %s for creating a secure channel.");
-    return NULL; /* TODO(ctiller): return lame channel. */
+    return grpc_lame_client_channel_create();
   }
 }
 
diff --git a/src/core/security/security_context.h b/src/core/security/security_context.h
index 59c9bbd..0c60256 100644
--- a/src/core/security/security_context.h
+++ b/src/core/security/security_context.h
@@ -119,7 +119,7 @@
 
 struct grpc_channel_security_context {
   grpc_security_context base;  /* requires is_client_side to be non 0. */
-  grpc_credentials *request_metadata_only_creds;
+  grpc_credentials *request_metadata_creds;
 };
 
 /* --- Creation security contexts. --- */
@@ -127,14 +127,14 @@
 /* For TESTING ONLY!
    Creates a fake context that emulates real channel security.  */
 grpc_channel_security_context *grpc_fake_channel_security_context_create(
-    grpc_credentials *request_metadata_only_creds);
+    grpc_credentials *request_metadata_creds);
 
 /* For TESTING ONLY!
    Creates a fake context that emulates real server security.  */
 grpc_security_context *grpc_fake_server_security_context_create(void);
 
 /* Creates an SSL channel_security_context.
-   - request_metadata_only_creds is the credentials object which metadata
+   - request_metadata_creds is the credentials object which metadata
      will be sent with each request. This parameter can be NULL.
    - config is the SSL config to be used for the SSL channel establishment.
    - is_client should be 0 for a server or a non-0 value for a client.
@@ -147,9 +147,8 @@
   specific error code otherwise.
 */
 grpc_security_status grpc_ssl_channel_security_context_create(
-    grpc_credentials *request_metadata_only_creds,
-    const grpc_ssl_config *config, const char *secure_peer_name,
-    grpc_channel_security_context **ctx);
+    grpc_credentials *request_metadata_creds, const grpc_ssl_config *config,
+    const char *secure_peer_name, grpc_channel_security_context **ctx);
 
 /* Creates an SSL server_security_context.
    - config is the SSL config to be used for the SSL channel establishment.
diff --git a/src/core/security/server_secure_chttp2.c b/src/core/security/server_secure_chttp2.c
index bce27ec..335d502 100644
--- a/src/core/security/server_secure_chttp2.c
+++ b/src/core/security/server_secure_chttp2.c
@@ -109,7 +109,7 @@
   for (i = 0; i < resolved->naddrs; i++) {
     if (grpc_tcp_server_add_port(tcp,
                                  (struct sockaddr *)&resolved->addrs[i].addr,
-                                 resolved->addrs[i].len) >= 0) {
+                                 resolved->addrs[i].len)) {
       count++;
     }
   }
diff --git a/src/core/support/cpu_posix.c b/src/core/support/cpu_posix.c
index 82d58de..7f9cb8b 100644
--- a/src/core/support/cpu_posix.c
+++ b/src/core/support/cpu_posix.c
@@ -34,8 +34,6 @@
 #include "src/core/support/cpu.h"
 
 #ifdef __linux__
-#include <errno.h>
-#include <unistd.h>
 #define _GNU_SOURCE
 #define __USE_GNU
 #define __USE_MISC
@@ -43,6 +41,9 @@
 #undef _GNU_SOURCE
 #undef __USE_GNU
 #undef __USE_MISC
+
+#include <errno.h>
+#include <unistd.h>
 #include <string.h>
 
 #include <grpc/support/log.h>
diff --git a/src/core/support/string_posix.c b/src/core/support/string_posix.c
index d1da379..7b7e82e 100644
--- a/src/core/support/string_posix.c
+++ b/src/core/support/string_posix.c
@@ -37,6 +37,10 @@
 #define _POSIX_C_SOURCE 200112L
 #endif
 
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_POSIX_STRING
+
 #include <stdio.h>
 #include <stdarg.h>
 #include <string.h>
@@ -84,3 +88,5 @@
   *strp = NULL;
   return -1;
 }
+
+#endif /* GPR_POSIX_STRING */
diff --git a/src/core/support/string_win32.c b/src/core/support/string_win32.c
new file mode 100644
index 0000000..74b1028
--- /dev/null
+++ b/src/core/support/string_win32.c
@@ -0,0 +1,81 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/* Posix code for gpr snprintf support. */
+
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_WIN32
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+
+int gpr_asprintf(char **strp, const char *format, ...) {
+  va_list args;
+  int ret;
+  size_t strp_buflen;
+
+  /* Determine the length. */
+  va_start(args, format);
+  ret = vscprintf(format, args);
+  va_end(args);
+  if (!(0 <= ret && ret < ~(size_t)0)) {
+    *strp = NULL;
+    return -1;
+  }
+
+  /* Allocate a new buffer, with space for the NUL terminator. */
+  strp_buflen = (size_t)ret + 1;
+  if ((*strp = gpr_malloc(strp_buflen)) == NULL) {
+    /* This shouldn't happen, because gpr_malloc() calls abort(). */
+    return -1;
+  }
+
+  /* Print to the buffer. */
+  va_start(args, format);
+  ret = vsnprintf_s(*strp, strp_buflen, _TRUNCATE, format, args);
+  va_end(args);
+  if (ret == strp_buflen - 1) {
+    return ret;
+  }
+
+  /* This should never happen. */
+  gpr_free(*strp);
+  *strp = NULL;
+  return -1;
+}
+
+#endif /* GPR_WIN32 */
diff --git a/src/core/support/thd_posix.c b/src/core/support/thd_posix.c
index c86eea4..1189e0c 100644
--- a/src/core/support/thd_posix.c
+++ b/src/core/support/thd_posix.c
@@ -33,6 +33,10 @@
 
 /* Posix implementation for gpr threads. */
 
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_POSIX_SYNC
+
 #include <pthread.h>
 #include <stdlib.h>
 #include <string.h>
@@ -76,3 +80,5 @@
   memset(&options, 0, sizeof(options));
   return options;
 }
+
+#endif /* GPR_POSIX_SYNC */
diff --git a/src/core/support/thd_win32.c b/src/core/support/thd_win32.c
index 8440479..1762f87 100644
--- a/src/core/support/thd_win32.c
+++ b/src/core/support/thd_win32.c
@@ -48,7 +48,7 @@
 };
 
 /* Body of every thread started via gpr_thd_new. */
-static DWORD thread_body(void *v) {
+static DWORD WINAPI thread_body(void *v) {
   struct thd_arg a = *(struct thd_arg *)v;
   gpr_free(v);
   (*a.body)(a.arg);
@@ -62,7 +62,7 @@
   a->body = thd_body;
   a->arg = arg;
   *t = 0;
-  handle = CreateThread(NULL, 64 * 1024, &thread_body, a, 0, NULL);
+  handle = CreateThread(NULL, 64 * 1024, thread_body, a, 0, NULL);
   if (handle == NULL) {
     gpr_free(a);
   } else {
diff --git a/src/core/support/time_posix.c b/src/core/support/time_posix.c
index e7b79d1..78d4c3b 100644
--- a/src/core/support/time_posix.c
+++ b/src/core/support/time_posix.c
@@ -37,6 +37,11 @@
 #ifndef _POSIX_C_SOURCE
 #define _POSIX_C_SOURCE 199309L
 #endif
+
+#include <grpc/support/port_platform.h>
+
+#ifdef GPR_POSIX_TIME
+
 #include <stdlib.h>
 #include <time.h>
 #include <unistd.h>
@@ -79,3 +84,5 @@
     }
   }
 }
+
+#endif /* GPR_POSIX_TIME */
diff --git a/src/core/support/time_win32.c b/src/core/support/time_win32.c
index 4258091..d9abe2d 100644
--- a/src/core/support/time_win32.c
+++ b/src/core/support/time_win32.c
@@ -38,12 +38,12 @@
 #ifdef GPR_WIN32
 
 #include <grpc/support/time.h>
-#include <windows.h>
+#include <sys/timeb.h>
 
 gpr_timespec gpr_now(void) {
   gpr_timespec now_tv;
-  struct _timeb64 now_tb;
-  _ftime64(&now_tb);
+  struct __timeb64 now_tb;
+  _ftime64_s(&now_tb);
   now_tv.tv_sec = now_tb.time;
   now_tv.tv_nsec = now_tb.millitm * 1000000;
   return now_tv;
diff --git a/src/core/surface/call.c b/src/core/surface/call.c
index 02c224d..a731c7c 100644
--- a/src/core/surface/call.c
+++ b/src/core/surface/call.c
@@ -154,7 +154,12 @@
 
 /* the state of a call, based upon which functions have been called against
    said call */
-typedef enum { CALL_CREATED, CALL_STARTED, CALL_FINISHED } call_state;
+typedef enum {
+  CALL_CREATED,
+  CALL_BOUNDCQ,
+  CALL_STARTED,
+  CALL_FINISHED
+} call_state;
 
 struct grpc_call {
   grpc_completion_queue *cq;
@@ -404,24 +409,18 @@
   return GRPC_CALL_OK;
 }
 
-grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq,
-                                 void *finished_tag, gpr_uint32 flags) {
-  grpc_call_element *elem;
-  grpc_call_op op;
-
+grpc_call_error grpc_call_server_accept(grpc_call *call,
+                                        grpc_completion_queue *cq,
+                                        void *finished_tag) {
   /* validate preconditions */
   if (call->is_client) {
     gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__);
     return GRPC_CALL_ERROR_NOT_ON_CLIENT;
   }
 
-  if (call->state >= CALL_STARTED) {
-    gpr_log(GPR_ERROR, "call is already invoked");
-    return GRPC_CALL_ERROR_ALREADY_INVOKED;
-  }
-
-  if (flags & GRPC_WRITE_NO_COMPRESS) {
-    return GRPC_CALL_ERROR_INVALID_FLAGS;
+  if (call->state >= CALL_BOUNDCQ) {
+    gpr_log(GPR_ERROR, "call is already accepted");
+    return GRPC_CALL_ERROR_ALREADY_ACCEPTED;
   }
 
   /* inform the completion queue of an incoming operation (corresponding to
@@ -430,7 +429,7 @@
 
   /* update state */
   gpr_mu_lock(&call->read_mu);
-  call->state = CALL_STARTED;
+  call->state = CALL_BOUNDCQ;
   call->cq = cq;
   call->finished_tag = finished_tag;
   if (prq_is_empty(&call->prq) && call->received_finish) {
@@ -442,6 +441,32 @@
   }
   gpr_mu_unlock(&call->read_mu);
 
+  return GRPC_CALL_OK;
+}
+
+grpc_call_error grpc_call_server_end_initial_metadata(grpc_call *call,
+                                                      gpr_uint32 flags) {
+  grpc_call_element *elem;
+  grpc_call_op op;
+
+  /* validate preconditions */
+  if (call->is_client) {
+    gpr_log(GPR_ERROR, "can only call %s on servers", __FUNCTION__);
+    return GRPC_CALL_ERROR_NOT_ON_CLIENT;
+  }
+
+  if (call->state >= CALL_STARTED) {
+    gpr_log(GPR_ERROR, "call is already started");
+    return GRPC_CALL_ERROR_ALREADY_INVOKED;
+  }
+
+  if (flags & GRPC_WRITE_NO_COMPRESS) {
+    return GRPC_CALL_ERROR_INVALID_FLAGS;
+  }
+
+  /* update state */
+  call->state = CALL_STARTED;
+
   /* call down */
   op.type = GRPC_SEND_START;
   op.dir = GRPC_CALL_DOWN;
@@ -455,6 +480,17 @@
   return GRPC_CALL_OK;
 }
 
+grpc_call_error grpc_call_accept(grpc_call *call, grpc_completion_queue *cq,
+                                 void *finished_tag, gpr_uint32 flags) {
+  grpc_call_error err;
+
+  err = grpc_call_server_accept(call, cq, finished_tag);
+  if (err != GRPC_CALL_OK) return err;
+  err = grpc_call_server_end_initial_metadata(call, flags);
+  if (err != GRPC_CALL_OK) return err;
+  return GRPC_CALL_OK;
+}
+
 static void done_writes_done(void *user_data, grpc_op_error error) {
   grpc_call *call = user_data;
   void *tag = call->write_tag;
@@ -515,6 +551,7 @@
   switch (call->state) {
     case CALL_CREATED:
       return GRPC_CALL_ERROR_NOT_INVOKED;
+    case CALL_BOUNDCQ:
     case CALL_STARTED:
       break;
     case CALL_FINISHED:
@@ -559,6 +596,7 @@
 
   switch (call->state) {
     case CALL_CREATED:
+    case CALL_BOUNDCQ:
       return GRPC_CALL_ERROR_NOT_INVOKED;
     case CALL_STARTED:
       break;
@@ -607,6 +645,7 @@
 
   switch (call->state) {
     case CALL_CREATED:
+    case CALL_BOUNDCQ:
       return GRPC_CALL_ERROR_NOT_INVOKED;
     case CALL_FINISHED:
       return GRPC_CALL_ERROR_ALREADY_FINISHED;
@@ -646,6 +685,7 @@
 
   switch (call->state) {
     case CALL_CREATED:
+    case CALL_BOUNDCQ:
       return GRPC_CALL_ERROR_NOT_INVOKED;
     case CALL_FINISHED:
       return GRPC_CALL_ERROR_ALREADY_FINISHED;
diff --git a/src/core/surface/channel.c b/src/core/surface/channel.c
index ff99425..dfb47b3 100644
--- a/src/core/surface/channel.c
+++ b/src/core/surface/channel.c
@@ -127,9 +127,16 @@
   grpc_channel_op op;
   grpc_channel_element *elem;
 
-  op.type = GRPC_CHANNEL_SHUTDOWN;
-  op.dir = GRPC_CALL_DOWN;
   elem = grpc_channel_stack_element(CHANNEL_STACK_FROM_CHANNEL(channel), 0);
+
+  op.type = GRPC_CHANNEL_GOAWAY;
+  op.dir = GRPC_CALL_DOWN;
+  op.data.goaway.status = GRPC_STATUS_OK;
+  op.data.goaway.message = gpr_slice_from_copied_string("Client disconnect");
+  elem->filter->channel_op(elem, &op);
+
+  op.type = GRPC_CHANNEL_DISCONNECT;
+  op.dir = GRPC_CALL_DOWN;
   elem->filter->channel_op(elem, &op);
 
   grpc_channel_internal_unref(channel);
diff --git a/src/core/surface/client.c b/src/core/surface/client.c
index 26abffa..f78a45f 100644
--- a/src/core/surface/client.c
+++ b/src/core/surface/client.c
@@ -83,6 +83,9 @@
     case GRPC_TRANSPORT_CLOSED:
       gpr_log(GPR_ERROR, "Transport closed");
       break;
+    case GRPC_TRANSPORT_GOAWAY:
+      gpr_slice_unref(op->data.goaway.message);
+      break;
     default:
       GPR_ASSERT(op->dir == GRPC_CALL_DOWN);
       grpc_channel_next_op(elem, op);
diff --git a/src/core/surface/completion_queue.c b/src/core/surface/completion_queue.c
index a7d6115..2002476 100644
--- a/src/core/surface/completion_queue.c
+++ b/src/core/surface/completion_queue.c
@@ -105,7 +105,7 @@
                          void *tag, grpc_call *call,
                          grpc_event_finish_func on_finish, void *user_data) {
   event *ev = gpr_malloc(sizeof(event));
-  gpr_intptr bucket = ((gpr_intptr)tag) % NUM_TAG_BUCKETS;
+  gpr_uintptr bucket = ((gpr_uintptr)tag) % NUM_TAG_BUCKETS;
   GPR_ASSERT(!cc->shutdown);
   ev->base.type = type;
   ev->base.tag = tag;
@@ -260,9 +260,9 @@
   gpr_mu_lock(&cc->em->mu);
   for (;;) {
     if (cc->queue != NULL) {
-      gpr_intptr bucket;
+      gpr_uintptr bucket;
       ev = cc->queue;
-      bucket = ((gpr_intptr)ev->base.tag) % NUM_TAG_BUCKETS;
+      bucket = ((gpr_uintptr)ev->base.tag) % NUM_TAG_BUCKETS;
       cc->queue = ev->queue_next;
       ev->queue_next->queue_prev = ev->queue_prev;
       ev->queue_prev->queue_next = ev->queue_next;
@@ -297,7 +297,7 @@
 }
 
 static event *pluck_event(grpc_completion_queue *cc, void *tag) {
-  gpr_intptr bucket = ((gpr_intptr)tag) % NUM_TAG_BUCKETS;
+  gpr_uintptr bucket = ((gpr_uintptr)tag) % NUM_TAG_BUCKETS;
   event *ev = cc->buckets[bucket];
   if (ev == NULL) return NULL;
   do {
diff --git a/src/core/surface/lame_client.c b/src/core/surface/lame_client.c
index 18921c4..3744b5b 100644
--- a/src/core/surface/lame_client.c
+++ b/src/core/surface/lame_client.c
@@ -61,7 +61,15 @@
   op->done_cb(op->user_data, GRPC_OP_ERROR);
 }
 
-static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) {}
+static void channel_op(grpc_channel_element *elem, grpc_channel_op *op) {
+  switch (op->type) {
+    case GRPC_CHANNEL_GOAWAY:
+      gpr_slice_unref(op->data.goaway.message);
+      break;
+    default:
+      break;
+  }
+}
 
 static void init_call_elem(grpc_call_element *elem,
                            const void *transport_server_data) {}
@@ -87,7 +95,7 @@
     "lame-client",
 };
 
-grpc_channel *grpc_lame_client_channel_create() {
+grpc_channel *grpc_lame_client_channel_create(void) {
   static const grpc_channel_filter *filters[] = {&lame_filter};
   return grpc_channel_create_from_filters(filters, 1, NULL, grpc_mdctx_create(),
                                           1);
diff --git a/src/core/surface/lame_client.h b/src/core/surface/lame_client.h
index 74b9707..3cfbf7b 100644
--- a/src/core/surface/lame_client.h
+++ b/src/core/surface/lame_client.h
@@ -37,6 +37,6 @@
 #include <grpc/grpc.h>
 
 /* Create a lame client: this client fails every operation attempted on it. */
-grpc_channel *grpc_lame_client_channel_create();
+grpc_channel *grpc_lame_client_channel_create(void);
 
 #endif /* __GRPC_INTERNAL_SURFACE_LAME_CLIENT_H_ */
diff --git a/src/core/surface/server.c b/src/core/surface/server.c
index 99d66ff..d8d5a7a 100644
--- a/src/core/surface/server.c
+++ b/src/core/surface/server.c
@@ -331,6 +331,9 @@
       gpr_mu_unlock(&chand->server->mu);
       server_unref(chand->server);
       break;
+    case GRPC_TRANSPORT_GOAWAY:
+      gpr_slice_unref(op->data.goaway.message);
+      break;
     default:
       GPR_ASSERT(op->dir == GRPC_CALL_DOWN);
       grpc_channel_next_op(elem, op);
@@ -341,7 +344,7 @@
 static void finish_shutdown_channel(void *cd, grpc_em_cb_status status) {
   channel_data *chand = cd;
   grpc_channel_op op;
-  op.type = GRPC_CHANNEL_SHUTDOWN;
+  op.type = GRPC_CHANNEL_DISCONNECT;
   op.dir = GRPC_CALL_DOWN;
   channel_op(grpc_channel_stack_element(
                  grpc_channel_get_channel_stack(chand->channel), 0),
@@ -515,10 +518,15 @@
 }
 
 void grpc_server_shutdown(grpc_server *server) {
-  /* TODO(ctiller): send goaway, etc */
   listener *l;
   void **tags;
   size_t ntags;
+  channel_data **channels;
+  channel_data *c;
+  size_t nchannels;
+  size_t i;
+  grpc_channel_op op;
+  grpc_channel_element *elem;
 
   /* lock, and gather up some stuff to do */
   gpr_mu_lock(&server->mu);
@@ -527,6 +535,20 @@
     return;
   }
 
+  nchannels = 0;
+  for (c = server->root_channel_data.next; c != &server->root_channel_data;
+       c = c->next) {
+    nchannels++;
+  }
+  channels = gpr_malloc(sizeof(channel_data *) * nchannels);
+  i = 0;
+  for (c = server->root_channel_data.next; c != &server->root_channel_data;
+       c = c->next) {
+    grpc_channel_internal_ref(c->channel);
+    channels[i] = c;
+    i++;
+  }
+
   tags = server->tags;
   ntags = server->ntags;
   server->tags = NULL;
@@ -535,6 +557,21 @@
   server->shutdown = 1;
   gpr_mu_unlock(&server->mu);
 
+  for (i = 0; i < nchannels; i++) {
+    c = channels[i];
+    elem = grpc_channel_stack_element(
+        grpc_channel_get_channel_stack(c->channel), 0);
+
+    op.type = GRPC_CHANNEL_GOAWAY;
+    op.dir = GRPC_CALL_DOWN;
+    op.data.goaway.status = GRPC_STATUS_OK;
+    op.data.goaway.message = gpr_slice_from_copied_string("Server shutdown");
+    elem->filter->channel_op(elem, &op);
+
+    grpc_channel_internal_unref(c->channel);
+  }
+  gpr_free(channels);
+
   /* terminate all the requested calls */
   early_terminate_requested_calls(server->cq, tags, ntags);
   gpr_free(tags);
diff --git a/src/core/surface/server_chttp2.c b/src/core/surface/server_chttp2.c
index 24c5757..db8924e 100644
--- a/src/core/surface/server_chttp2.c
+++ b/src/core/surface/server_chttp2.c
@@ -91,7 +91,7 @@
   for (i = 0; i < resolved->naddrs; i++) {
     if (grpc_tcp_server_add_port(tcp,
                                  (struct sockaddr *)&resolved->addrs[i].addr,
-                                 resolved->addrs[i].len) >= 0) {
+                                 resolved->addrs[i].len)) {
       count++;
     }
   }
diff --git a/src/core/transport/chttp2/alpn.c b/src/core/transport/chttp2/alpn.c
new file mode 100644
index 0000000..cd9cf67
--- /dev/null
+++ b/src/core/transport/chttp2/alpn.c
@@ -0,0 +1,45 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/alpn.h"
+
+static const char *const supported_versions[] = {GRPC_CHTTP2_ALPN_VERSION,
+                                                 "h2-15", "h2-14"};
+
+int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size) {
+  size_t i;
+  for (i = 0; i < sizeof(supported_versions) / sizeof(const char *); i++) {
+    if (!strncmp(version, supported_versions[i], size)) return 1;
+  }
+  return 0;
+}
diff --git a/src/core/transport/chttp2/alpn.h b/src/core/transport/chttp2/alpn.h
new file mode 100644
index 0000000..1353a18
--- /dev/null
+++ b/src/core/transport/chttp2/alpn.h
@@ -0,0 +1,44 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_ALPN_H_
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_ALPN_H_
+
+#include <string.h>
+
+#define GRPC_CHTTP2_ALPN_VERSION "h2-15"
+
+/* Retuns 1 if the version is supported, 0 otherwise. */
+int grpc_chttp2_is_alpn_version_supported(const char *version, size_t size);
+
+#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_ALPN_H_ */
diff --git a/src/core/transport/chttp2/bin_encoder.c b/src/core/transport/chttp2/bin_encoder.c
new file mode 100644
index 0000000..39a303b
--- /dev/null
+++ b/src/core/transport/chttp2/bin_encoder.c
@@ -0,0 +1,271 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/bin_encoder.h"
+#include "src/core/transport/chttp2/huffsyms.h"
+#include <grpc/support/log.h>
+
+static const char alphabet[] =
+    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+typedef struct {
+  gpr_uint16 bits;
+  gpr_uint8 length;
+} b64_huff_sym;
+
+static const b64_huff_sym huff_alphabet[64] = {{0x21, 6},
+                                               {0x5d, 7},
+                                               {0x5e, 7},
+                                               {0x5f, 7},
+                                               {0x60, 7},
+                                               {0x61, 7},
+                                               {0x62, 7},
+                                               {0x63, 7},
+                                               {0x64, 7},
+                                               {0x65, 7},
+                                               {0x66, 7},
+                                               {0x67, 7},
+                                               {0x68, 7},
+                                               {0x69, 7},
+                                               {0x6a, 7},
+                                               {0x6b, 7},
+                                               {0x6c, 7},
+                                               {0x6d, 7},
+                                               {0x6e, 7},
+                                               {0x6f, 7},
+                                               {0x70, 7},
+                                               {0x71, 7},
+                                               {0x72, 7},
+                                               {0xfc, 8},
+                                               {0x73, 7},
+                                               {0xfd, 8},
+                                               {0x3, 5},
+                                               {0x23, 6},
+                                               {0x4, 5},
+                                               {0x24, 6},
+                                               {0x5, 5},
+                                               {0x25, 6},
+                                               {0x26, 6},
+                                               {0x27, 6},
+                                               {0x6, 5},
+                                               {0x74, 7},
+                                               {0x75, 7},
+                                               {0x28, 6},
+                                               {0x29, 6},
+                                               {0x2a, 6},
+                                               {0x7, 5},
+                                               {0x2b, 6},
+                                               {0x76, 7},
+                                               {0x2c, 6},
+                                               {0x8, 5},
+                                               {0x9, 5},
+                                               {0x2d, 6},
+                                               {0x77, 7},
+                                               {0x78, 7},
+                                               {0x79, 7},
+                                               {0x7a, 7},
+                                               {0x7b, 7},
+                                               {0x0, 5},
+                                               {0x1, 5},
+                                               {0x2, 5},
+                                               {0x19, 6},
+                                               {0x1a, 6},
+                                               {0x1b, 6},
+                                               {0x1c, 6},
+                                               {0x1d, 6},
+                                               {0x1e, 6},
+                                               {0x1f, 6},
+                                               {0x7fb, 11},
+                                               {0x18, 6}};
+
+static const gpr_uint8 tail_xtra[3] = {0, 2, 3};
+
+gpr_slice grpc_chttp2_base64_encode(gpr_slice input) {
+  size_t input_length = GPR_SLICE_LENGTH(input);
+  size_t input_triplets = input_length / 3;
+  size_t tail_case = input_length % 3;
+  size_t output_length = input_triplets * 4 + tail_xtra[tail_case];
+  gpr_slice output = gpr_slice_malloc(output_length);
+  gpr_uint8 *in = GPR_SLICE_START_PTR(input);
+  gpr_uint8 *out = GPR_SLICE_START_PTR(output);
+  size_t i;
+
+  /* encode full triplets */
+  for (i = 0; i < input_triplets; i++) {
+    out[0] = alphabet[in[0] >> 2];
+    out[1] = alphabet[((in[0] & 0x2) << 4) | (in[1] >> 4)];
+    out[2] = alphabet[((in[1] & 0xf) << 2) | (in[2] >> 6)];
+    out[3] = alphabet[in[2] & 0x3f];
+    out += 4;
+    in += 3;
+  }
+
+  /* encode the remaining bytes */
+  switch (tail_case) {
+    case 0:
+      break;
+    case 1:
+      out[0] = alphabet[in[0] >> 2];
+      out[1] = alphabet[(in[0] & 0x2) << 4];
+      out += 2;
+      in += 1;
+      break;
+    case 2:
+      out[0] = alphabet[in[0] >> 2];
+      out[1] = alphabet[((in[0] & 0x2) << 4) | (in[1] >> 4)];
+      out[2] = alphabet[(in[1] & 0xf) << 2];
+      out += 3;
+      in += 2;
+      break;
+  }
+
+  GPR_ASSERT(out == GPR_SLICE_END_PTR(output));
+  GPR_ASSERT(in == GPR_SLICE_END_PTR(input));
+  return output;
+}
+
+gpr_slice grpc_chttp2_huffman_compress(gpr_slice input) {
+  size_t nbits;
+  gpr_uint8 *in;
+  gpr_uint8 *out;
+  gpr_slice output;
+  gpr_uint32 temp = 0;
+  gpr_uint32 temp_length = 0;
+
+  nbits = 0;
+  for (in = GPR_SLICE_START_PTR(input); in != GPR_SLICE_END_PTR(input); ++in) {
+    nbits += grpc_chttp2_huffsyms[*in].length;
+  }
+
+  output = gpr_slice_malloc(nbits / 8 + (nbits % 8 != 0));
+  out = GPR_SLICE_START_PTR(output);
+  for (in = GPR_SLICE_START_PTR(input); in != GPR_SLICE_END_PTR(input); ++in) {
+    int sym = *in;
+    temp <<= grpc_chttp2_huffsyms[sym].length;
+    temp |= grpc_chttp2_huffsyms[sym].bits;
+    temp_length += grpc_chttp2_huffsyms[sym].length;
+
+    while (temp_length > 8) {
+      temp_length -= 8;
+      *out++ = temp >> temp_length;
+    }
+  }
+
+  if (temp_length) {
+    *out++ = (temp << (8 - temp_length)) | (0xff >> temp_length);
+  }
+
+  GPR_ASSERT(out == GPR_SLICE_END_PTR(output));
+
+  return output;
+}
+
+typedef struct {
+  gpr_uint32 temp;
+  gpr_uint32 temp_length;
+  gpr_uint8 *out;
+} huff_out;
+
+static void enc_flush_some(huff_out *out) {
+  while (out->temp_length > 8) {
+    out->temp_length -= 8;
+    *out->out++ = out->temp >> out->temp_length;
+  }
+}
+
+static void enc_add2(huff_out *out, gpr_uint8 a, gpr_uint8 b) {
+  b64_huff_sym sa = huff_alphabet[a];
+  b64_huff_sym sb = huff_alphabet[b];
+  out->temp =
+      (out->temp << (sa.length + sb.length)) | (sa.bits << sb.length) | sb.bits;
+  out->temp_length += sa.length + sb.length;
+  enc_flush_some(out);
+}
+
+static void enc_add1(huff_out *out, gpr_uint8 a) {
+  b64_huff_sym sa = huff_alphabet[a];
+  out->temp = (out->temp << sa.length) | sa.bits;
+  out->temp_length += sa.length;
+  enc_flush_some(out);
+}
+
+gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input) {
+  size_t input_length = GPR_SLICE_LENGTH(input);
+  size_t input_triplets = input_length / 3;
+  size_t tail_case = input_length % 3;
+  size_t output_syms = input_triplets * 4 + tail_xtra[tail_case];
+  size_t max_output_bits = 11 * output_syms;
+  size_t max_output_length = max_output_bits / 8 + (max_output_bits % 8 != 0);
+  gpr_slice output = gpr_slice_malloc(max_output_length);
+  gpr_uint8 *in = GPR_SLICE_START_PTR(input);
+  gpr_uint8 *start_out = GPR_SLICE_START_PTR(output);
+  huff_out out;
+  size_t i;
+
+  out.temp = 0;
+  out.temp_length = 0;
+  out.out = start_out;
+
+  /* encode full triplets */
+  for (i = 0; i < input_triplets; i++) {
+    enc_add2(&out, in[0] >> 2, ((in[0] & 0x2) << 4) | (in[1] >> 4));
+    enc_add2(&out, ((in[1] & 0xf) << 2) | (in[2] >> 6), in[2] & 0x3f);
+    in += 3;
+  }
+
+  /* encode the remaining bytes */
+  switch (tail_case) {
+    case 0:
+      break;
+    case 1:
+      enc_add2(&out, in[0] >> 2, (in[0] & 0x2) << 4);
+      in += 1;
+      break;
+    case 2:
+      enc_add2(&out, in[0] >> 2, ((in[0] & 0x2) << 4) | (in[1] >> 4));
+      enc_add1(&out, (in[1] & 0xf) << 2);
+      in += 2;
+      break;
+  }
+
+  if (out.temp_length) {
+    *out.out++ =
+        (out.temp << (8 - out.temp_length)) | (0xff >> out.temp_length);
+  }
+
+  GPR_ASSERT(out.out <= GPR_SLICE_END_PTR(output));
+  GPR_SLICE_SET_LENGTH(output, out.out - start_out);
+
+  GPR_ASSERT(in == GPR_SLICE_END_PTR(input));
+  return output;
+}
diff --git a/src/core/transport/chttp2/bin_encoder.h b/src/core/transport/chttp2/bin_encoder.h
new file mode 100644
index 0000000..8603113
--- /dev/null
+++ b/src/core/transport/chttp2/bin_encoder.h
@@ -0,0 +1,54 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_BIN_ENCODER_H_
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_BIN_ENCODER_H_
+
+#include <grpc/support/slice.h>
+
+/* base64 encode a slice. Returns a new slice, does not take ownership of the
+   input */
+gpr_slice grpc_chttp2_base64_encode(gpr_slice input);
+
+/* Compress a slice with the static huffman encoder detailed in the hpack
+   standard. Returns a new slice, does not take ownership of the input */
+gpr_slice grpc_chttp2_huffman_compress(gpr_slice input);
+
+/* equivalent to:
+   gpr_slice x = grpc_chttp2_base64_encode(input);
+   gpr_slice y = grpc_chttp2_huffman_compress(x);
+   gpr_slice_unref(x);
+   return y; */
+gpr_slice grpc_chttp2_base64_encode_and_huffman_compress(gpr_slice input);
+
+#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_BIN_ENCODER_H_ */
diff --git a/src/core/transport/chttp2/frame.h b/src/core/transport/chttp2/frame.h
index 7c0bbe0..a04e442 100644
--- a/src/core/transport/chttp2/frame.h
+++ b/src/core/transport/chttp2/frame.h
@@ -35,6 +35,7 @@
 #define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_H__
 
 #include <grpc/support/port_platform.h>
+#include <grpc/support/slice.h>
 
 /* Common definitions for frame handling in the chttp2 transport */
 
@@ -51,8 +52,12 @@
   gpr_uint8 ack_settings;
   gpr_uint8 send_ping_ack;
   gpr_uint8 process_ping_reply;
+  gpr_uint8 goaway;
 
   gpr_uint32 window_update;
+  gpr_uint32 goaway_last_stream_index;
+  gpr_uint32 goaway_error;
+  gpr_slice goaway_text;
 } grpc_chttp2_parse_state;
 
 #define GRPC_CHTTP2_FRAME_DATA 0
@@ -61,6 +66,7 @@
 #define GRPC_CHTTP2_FRAME_RST_STREAM 3
 #define GRPC_CHTTP2_FRAME_SETTINGS 4
 #define GRPC_CHTTP2_FRAME_PING 6
+#define GRPC_CHTTP2_FRAME_GOAWAY 7
 #define GRPC_CHTTP2_FRAME_WINDOW_UPDATE 8
 
 #define GRPC_CHTTP2_MAX_PAYLOAD_LENGTH ((1 << 14) - 1)
diff --git a/src/core/transport/chttp2/frame_goaway.c b/src/core/transport/chttp2/frame_goaway.c
new file mode 100644
index 0000000..de7c0b0
--- /dev/null
+++ b/src/core/transport/chttp2/frame_goaway.c
@@ -0,0 +1,189 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/frame_goaway.h"
+
+#include <string.h>
+
+#include <grpc/support/alloc.h>
+#include <grpc/support/log.h>
+
+void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p) {
+  p->debug_data = NULL;
+}
+
+void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p) {
+  gpr_free(p->debug_data);
+}
+
+grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame(
+    grpc_chttp2_goaway_parser *p, gpr_uint32 length, gpr_uint8 flags) {
+  if (length < 8) {
+    gpr_log(GPR_ERROR, "goaway frame too short (%d bytes)", length);
+    return GRPC_CHTTP2_CONNECTION_ERROR;
+  }
+
+  gpr_free(p->debug_data);
+  p->debug_length = length - 8;
+  p->debug_data = gpr_malloc(p->debug_length);
+  p->debug_pos = 0;
+  p->state = GRPC_CHTTP2_GOAWAY_LSI0;
+  return GRPC_CHTTP2_PARSE_OK;
+}
+
+grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice,
+    int is_last) {
+  gpr_uint8 *const beg = GPR_SLICE_START_PTR(slice);
+  gpr_uint8 *const end = GPR_SLICE_END_PTR(slice);
+  gpr_uint8 *cur = beg;
+  grpc_chttp2_goaway_parser *p = parser;
+
+  switch (p->state) {
+    case GRPC_CHTTP2_GOAWAY_LSI0:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_LSI0;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->last_stream_id = ((gpr_uint32)*cur) << 24;
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_LSI1:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_LSI1;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->last_stream_id |= ((gpr_uint32)*cur) << 16;
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_LSI2:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_LSI2;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->last_stream_id |= ((gpr_uint32)*cur) << 8;
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_LSI3:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_LSI3;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->last_stream_id |= ((gpr_uint32)*cur);
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_ERR0:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_ERR0;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->error_code = ((gpr_uint32)*cur) << 24;
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_ERR1:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_ERR1;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->error_code |= ((gpr_uint32)*cur) << 16;
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_ERR2:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_ERR2;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->error_code |= ((gpr_uint32)*cur) << 8;
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_ERR3:
+      if (cur == end) {
+        p->state = GRPC_CHTTP2_GOAWAY_ERR3;
+        return GRPC_CHTTP2_PARSE_OK;
+      }
+      p->error_code |= ((gpr_uint32)*cur);
+      ++cur;
+    /* fallthrough */
+    case GRPC_CHTTP2_GOAWAY_DEBUG:
+      memcpy(p->debug_data + p->debug_pos, cur, end - cur);
+      p->debug_pos += end - cur;
+      p->state = GRPC_CHTTP2_GOAWAY_DEBUG;
+      if (is_last) {
+        state->goaway = 1;
+        state->goaway_last_stream_index = p->last_stream_id;
+        state->goaway_error = p->error_code;
+        state->goaway_text =
+            gpr_slice_new(p->debug_data, p->debug_length, gpr_free);
+        p->debug_data = NULL;
+      }
+      return GRPC_CHTTP2_PARSE_OK;
+  }
+  gpr_log(GPR_ERROR, "Should never end up here");
+  abort();
+  return GRPC_CHTTP2_CONNECTION_ERROR;
+}
+
+void grpc_chttp2_goaway_append(gpr_uint32 last_stream_id, gpr_uint32 error_code,
+                               gpr_slice debug_data,
+                               gpr_slice_buffer *slice_buffer) {
+  gpr_slice header = gpr_slice_malloc(9 + 4 + 4);
+  gpr_uint8 *p = GPR_SLICE_START_PTR(header);
+  gpr_uint32 frame_length = 4 + 4 + GPR_SLICE_LENGTH(debug_data);
+
+  /* frame header: length */
+  *p++ = frame_length >> 16;
+  *p++ = frame_length >> 8;
+  *p++ = frame_length;
+  /* frame header: type */
+  *p++ = GRPC_CHTTP2_FRAME_GOAWAY;
+  /* frame header: flags */
+  *p++ = 0;
+  /* frame header: stream id */
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  *p++ = 0;
+  /* payload: last stream id */
+  *p++ = last_stream_id >> 24;
+  *p++ = last_stream_id >> 16;
+  *p++ = last_stream_id >> 8;
+  *p++ = last_stream_id;
+  /* payload: error code */
+  *p++ = error_code >> 24;
+  *p++ = error_code >> 16;
+  *p++ = error_code >> 8;
+  *p++ = error_code;
+  GPR_ASSERT(p == GPR_SLICE_END_PTR(header));
+  gpr_slice_buffer_add(slice_buffer, header);
+  gpr_slice_buffer_add(slice_buffer, debug_data);
+}
diff --git a/src/core/transport/chttp2/frame_goaway.h b/src/core/transport/chttp2/frame_goaway.h
new file mode 100644
index 0000000..9a3f8e6
--- /dev/null
+++ b/src/core/transport/chttp2/frame_goaway.h
@@ -0,0 +1,74 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_GOAWAY_H_
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_GOAWAY_H_
+
+#include "src/core/transport/chttp2/frame.h"
+#include <grpc/support/port_platform.h>
+#include <grpc/support/slice.h>
+#include <grpc/support/slice_buffer.h>
+
+typedef enum {
+  GRPC_CHTTP2_GOAWAY_LSI0,
+  GRPC_CHTTP2_GOAWAY_LSI1,
+  GRPC_CHTTP2_GOAWAY_LSI2,
+  GRPC_CHTTP2_GOAWAY_LSI3,
+  GRPC_CHTTP2_GOAWAY_ERR0,
+  GRPC_CHTTP2_GOAWAY_ERR1,
+  GRPC_CHTTP2_GOAWAY_ERR2,
+  GRPC_CHTTP2_GOAWAY_ERR3,
+  GRPC_CHTTP2_GOAWAY_DEBUG
+} grpc_chttp2_goaway_parse_state;
+
+typedef struct {
+  grpc_chttp2_goaway_parse_state state;
+  gpr_uint32 last_stream_id;
+  gpr_uint32 error_code;
+  char *debug_data;
+  gpr_uint32 debug_length;
+  gpr_uint32 debug_pos;
+} grpc_chttp2_goaway_parser;
+
+void grpc_chttp2_goaway_parser_init(grpc_chttp2_goaway_parser *p);
+void grpc_chttp2_goaway_parser_destroy(grpc_chttp2_goaway_parser *p);
+grpc_chttp2_parse_error grpc_chttp2_goaway_parser_begin_frame(
+    grpc_chttp2_goaway_parser *parser, gpr_uint32 length, gpr_uint8 flags);
+grpc_chttp2_parse_error grpc_chttp2_goaway_parser_parse(
+    void *parser, grpc_chttp2_parse_state *state, gpr_slice slice, int is_last);
+
+void grpc_chttp2_goaway_append(gpr_uint32 last_stream_id, gpr_uint32 error_code,
+                               gpr_slice debug_data,
+                               gpr_slice_buffer *slice_buffer);
+
+#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_FRAME_GOAWAY_H_ */
diff --git a/src/core/transport/chttp2/gen_hpack_tables.c b/src/core/transport/chttp2/gen_hpack_tables.c
index cc94a73..301b79e 100644
--- a/src/core/transport/chttp2/gen_hpack_tables.c
+++ b/src/core/transport/chttp2/gen_hpack_tables.c
@@ -39,6 +39,7 @@
 #include <assert.h>
 
 #include <grpc/support/log.h>
+#include "src/core/transport/chttp2/huffsyms.h"
 
 /*
  * first byte LUT generation
@@ -127,277 +128,10 @@
  * Huffman decoder table generation
  */
 
-#define NSYMS 257
 #define MAXHUFFSTATES 1024
 
-/* Constants pulled from the HPACK spec, and converted to C using the vim
-   command:
-   :%s/.*   \([0-9a-f]\+\)  \[ *\([0-9]\+\)\]/{0x\1, \2},/g */
-static const struct {
-  unsigned bits;
-  unsigned length;
-} huffsyms[NSYMS] = {
-      {0x1ff8, 13},
-      {0x7fffd8, 23},
-      {0xfffffe2, 28},
-      {0xfffffe3, 28},
-      {0xfffffe4, 28},
-      {0xfffffe5, 28},
-      {0xfffffe6, 28},
-      {0xfffffe7, 28},
-      {0xfffffe8, 28},
-      {0xffffea, 24},
-      {0x3ffffffc, 30},
-      {0xfffffe9, 28},
-      {0xfffffea, 28},
-      {0x3ffffffd, 30},
-      {0xfffffeb, 28},
-      {0xfffffec, 28},
-      {0xfffffed, 28},
-      {0xfffffee, 28},
-      {0xfffffef, 28},
-      {0xffffff0, 28},
-      {0xffffff1, 28},
-      {0xffffff2, 28},
-      {0x3ffffffe, 30},
-      {0xffffff3, 28},
-      {0xffffff4, 28},
-      {0xffffff5, 28},
-      {0xffffff6, 28},
-      {0xffffff7, 28},
-      {0xffffff8, 28},
-      {0xffffff9, 28},
-      {0xffffffa, 28},
-      {0xffffffb, 28},
-      {0x14, 6},
-      {0x3f8, 10},
-      {0x3f9, 10},
-      {0xffa, 12},
-      {0x1ff9, 13},
-      {0x15, 6},
-      {0xf8, 8},
-      {0x7fa, 11},
-      {0x3fa, 10},
-      {0x3fb, 10},
-      {0xf9, 8},
-      {0x7fb, 11},
-      {0xfa, 8},
-      {0x16, 6},
-      {0x17, 6},
-      {0x18, 6},
-      {0x0, 5},
-      {0x1, 5},
-      {0x2, 5},
-      {0x19, 6},
-      {0x1a, 6},
-      {0x1b, 6},
-      {0x1c, 6},
-      {0x1d, 6},
-      {0x1e, 6},
-      {0x1f, 6},
-      {0x5c, 7},
-      {0xfb, 8},
-      {0x7ffc, 15},
-      {0x20, 6},
-      {0xffb, 12},
-      {0x3fc, 10},
-      {0x1ffa, 13},
-      {0x21, 6},
-      {0x5d, 7},
-      {0x5e, 7},
-      {0x5f, 7},
-      {0x60, 7},
-      {0x61, 7},
-      {0x62, 7},
-      {0x63, 7},
-      {0x64, 7},
-      {0x65, 7},
-      {0x66, 7},
-      {0x67, 7},
-      {0x68, 7},
-      {0x69, 7},
-      {0x6a, 7},
-      {0x6b, 7},
-      {0x6c, 7},
-      {0x6d, 7},
-      {0x6e, 7},
-      {0x6f, 7},
-      {0x70, 7},
-      {0x71, 7},
-      {0x72, 7},
-      {0xfc, 8},
-      {0x73, 7},
-      {0xfd, 8},
-      {0x1ffb, 13},
-      {0x7fff0, 19},
-      {0x1ffc, 13},
-      {0x3ffc, 14},
-      {0x22, 6},
-      {0x7ffd, 15},
-      {0x3, 5},
-      {0x23, 6},
-      {0x4, 5},
-      {0x24, 6},
-      {0x5, 5},
-      {0x25, 6},
-      {0x26, 6},
-      {0x27, 6},
-      {0x6, 5},
-      {0x74, 7},
-      {0x75, 7},
-      {0x28, 6},
-      {0x29, 6},
-      {0x2a, 6},
-      {0x7, 5},
-      {0x2b, 6},
-      {0x76, 7},
-      {0x2c, 6},
-      {0x8, 5},
-      {0x9, 5},
-      {0x2d, 6},
-      {0x77, 7},
-      {0x78, 7},
-      {0x79, 7},
-      {0x7a, 7},
-      {0x7b, 7},
-      {0x7ffe, 15},
-      {0x7fc, 11},
-      {0x3ffd, 14},
-      {0x1ffd, 13},
-      {0xffffffc, 28},
-      {0xfffe6, 20},
-      {0x3fffd2, 22},
-      {0xfffe7, 20},
-      {0xfffe8, 20},
-      {0x3fffd3, 22},
-      {0x3fffd4, 22},
-      {0x3fffd5, 22},
-      {0x7fffd9, 23},
-      {0x3fffd6, 22},
-      {0x7fffda, 23},
-      {0x7fffdb, 23},
-      {0x7fffdc, 23},
-      {0x7fffdd, 23},
-      {0x7fffde, 23},
-      {0xffffeb, 24},
-      {0x7fffdf, 23},
-      {0xffffec, 24},
-      {0xffffed, 24},
-      {0x3fffd7, 22},
-      {0x7fffe0, 23},
-      {0xffffee, 24},
-      {0x7fffe1, 23},
-      {0x7fffe2, 23},
-      {0x7fffe3, 23},
-      {0x7fffe4, 23},
-      {0x1fffdc, 21},
-      {0x3fffd8, 22},
-      {0x7fffe5, 23},
-      {0x3fffd9, 22},
-      {0x7fffe6, 23},
-      {0x7fffe7, 23},
-      {0xffffef, 24},
-      {0x3fffda, 22},
-      {0x1fffdd, 21},
-      {0xfffe9, 20},
-      {0x3fffdb, 22},
-      {0x3fffdc, 22},
-      {0x7fffe8, 23},
-      {0x7fffe9, 23},
-      {0x1fffde, 21},
-      {0x7fffea, 23},
-      {0x3fffdd, 22},
-      {0x3fffde, 22},
-      {0xfffff0, 24},
-      {0x1fffdf, 21},
-      {0x3fffdf, 22},
-      {0x7fffeb, 23},
-      {0x7fffec, 23},
-      {0x1fffe0, 21},
-      {0x1fffe1, 21},
-      {0x3fffe0, 22},
-      {0x1fffe2, 21},
-      {0x7fffed, 23},
-      {0x3fffe1, 22},
-      {0x7fffee, 23},
-      {0x7fffef, 23},
-      {0xfffea, 20},
-      {0x3fffe2, 22},
-      {0x3fffe3, 22},
-      {0x3fffe4, 22},
-      {0x7ffff0, 23},
-      {0x3fffe5, 22},
-      {0x3fffe6, 22},
-      {0x7ffff1, 23},
-      {0x3ffffe0, 26},
-      {0x3ffffe1, 26},
-      {0xfffeb, 20},
-      {0x7fff1, 19},
-      {0x3fffe7, 22},
-      {0x7ffff2, 23},
-      {0x3fffe8, 22},
-      {0x1ffffec, 25},
-      {0x3ffffe2, 26},
-      {0x3ffffe3, 26},
-      {0x3ffffe4, 26},
-      {0x7ffffde, 27},
-      {0x7ffffdf, 27},
-      {0x3ffffe5, 26},
-      {0xfffff1, 24},
-      {0x1ffffed, 25},
-      {0x7fff2, 19},
-      {0x1fffe3, 21},
-      {0x3ffffe6, 26},
-      {0x7ffffe0, 27},
-      {0x7ffffe1, 27},
-      {0x3ffffe7, 26},
-      {0x7ffffe2, 27},
-      {0xfffff2, 24},
-      {0x1fffe4, 21},
-      {0x1fffe5, 21},
-      {0x3ffffe8, 26},
-      {0x3ffffe9, 26},
-      {0xffffffd, 28},
-      {0x7ffffe3, 27},
-      {0x7ffffe4, 27},
-      {0x7ffffe5, 27},
-      {0xfffec, 20},
-      {0xfffff3, 24},
-      {0xfffed, 20},
-      {0x1fffe6, 21},
-      {0x3fffe9, 22},
-      {0x1fffe7, 21},
-      {0x1fffe8, 21},
-      {0x7ffff3, 23},
-      {0x3fffea, 22},
-      {0x3fffeb, 22},
-      {0x1ffffee, 25},
-      {0x1ffffef, 25},
-      {0xfffff4, 24},
-      {0xfffff5, 24},
-      {0x3ffffea, 26},
-      {0x7ffff4, 23},
-      {0x3ffffeb, 26},
-      {0x7ffffe6, 27},
-      {0x3ffffec, 26},
-      {0x3ffffed, 26},
-      {0x7ffffe7, 27},
-      {0x7ffffe8, 27},
-      {0x7ffffe9, 27},
-      {0x7ffffea, 27},
-      {0x7ffffeb, 27},
-      {0xffffffe, 28},
-      {0x7ffffec, 27},
-      {0x7ffffed, 27},
-      {0x7ffffee, 27},
-      {0x7ffffef, 27},
-      {0x7fffff0, 27},
-      {0x3ffffee, 26},
-      {0x3fffffff, 30},
-};
-
 /* represents a set of symbols as an array of booleans indicating inclusion */
-typedef struct { char included[NSYMS]; } symset;
+typedef struct { char included[GRPC_CHTTP2_NUM_HUFFSYMS]; } symset;
 /* represents a lookup table indexed by a nibble */
 typedef struct { int values[16]; } nibblelut;
 
@@ -430,7 +164,7 @@
 static int nsyms(symset s) {
   int i;
   int c = 0;
-  for (i = 0; i < NSYMS; i++) {
+  for (i = 0; i < GRPC_CHTTP2_NUM_HUFFSYMS; i++) {
     c += s.included[i] != 0;
   }
   return c;
@@ -458,7 +192,8 @@
   int i;
   for (i = 0; i < nhuffstates; i++) {
     if (huffstates[i].bitofs != bitofs) continue;
-    if (0 != memcmp(huffstates[i].syms.included, syms.included, NSYMS))
+    if (0 != memcmp(huffstates[i].syms.included, syms.included,
+                    GRPC_CHTTP2_NUM_HUFFSYMS))
       continue;
     *isnew = 0;
     return i;
@@ -510,12 +245,13 @@
   for (bit = 0; bit < 2; bit++) {
     /* walk over active symbols and see if they have this bit set */
     symset nextsyms = symset_none();
-    for (i = 0; i < NSYMS; i++) {
+    for (i = 0; i < GRPC_CHTTP2_NUM_HUFFSYMS; i++) {
       if (!syms.included[i]) continue; /* disregard inactive symbols */
-      if (((huffsyms[i].bits >> (huffsyms[i].length - bitofs - 1)) & 1) ==
-          bit) {
+      if (((grpc_chttp2_huffsyms[i].bits >>
+            (grpc_chttp2_huffsyms[i].length - bitofs - 1)) &
+           1) == bit) {
         /* the bit is set, include it in the next recursive set */
-        if (huffsyms[i].length == bitofs + 1) {
+        if (grpc_chttp2_huffsyms[i].length == bitofs + 1) {
           /* additionally, we've gotten to the end of a symbol - this is a
              special recursion step: re-activate all the symbols, reset
              bitofs to zero, and recurse */
@@ -581,9 +317,25 @@
   dump_ctbl("emit_sub_tbl");
 }
 
+static void generate_base64_huff_encoder_table() {
+  static const char alphabet[] =
+      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+  int i;
+
+  printf(
+      "static const struct { gpr_uint16 bits, gpr_uint8 length } "
+      "base64_syms[64] = {\n");
+  for (i = 0; i < 64; i++) {
+    printf("{0x%x, %d},", grpc_chttp2_huffsyms[(unsigned char)alphabet[i]].bits,
+           grpc_chttp2_huffsyms[(unsigned char)alphabet[i]].length);
+  }
+  printf("};\n");
+}
+
 int main(void) {
   generate_huff_tables();
   generate_first_byte_lut();
+  generate_base64_huff_encoder_table();
 
   return 0;
 }
diff --git a/src/core/transport/chttp2/huffsyms.c b/src/core/transport/chttp2/huffsyms.c
new file mode 100644
index 0000000..0f86f1b
--- /dev/null
+++ b/src/core/transport/chttp2/huffsyms.c
@@ -0,0 +1,297 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include "src/core/transport/chttp2/huffsyms.h"
+
+/* Constants pulled from the HPACK spec, and converted to C using the vim
+   command:
+   :%s/.*   \([0-9a-f]\+\)  \[ *\([0-9]\+\)\]/{0x\1, \2},/g */
+const grpc_chttp2_huffsym grpc_chttp2_huffsyms[GRPC_CHTTP2_NUM_HUFFSYMS] = {
+    {0x1ff8, 13},
+    {0x7fffd8, 23},
+    {0xfffffe2, 28},
+    {0xfffffe3, 28},
+    {0xfffffe4, 28},
+    {0xfffffe5, 28},
+    {0xfffffe6, 28},
+    {0xfffffe7, 28},
+    {0xfffffe8, 28},
+    {0xffffea, 24},
+    {0x3ffffffc, 30},
+    {0xfffffe9, 28},
+    {0xfffffea, 28},
+    {0x3ffffffd, 30},
+    {0xfffffeb, 28},
+    {0xfffffec, 28},
+    {0xfffffed, 28},
+    {0xfffffee, 28},
+    {0xfffffef, 28},
+    {0xffffff0, 28},
+    {0xffffff1, 28},
+    {0xffffff2, 28},
+    {0x3ffffffe, 30},
+    {0xffffff3, 28},
+    {0xffffff4, 28},
+    {0xffffff5, 28},
+    {0xffffff6, 28},
+    {0xffffff7, 28},
+    {0xffffff8, 28},
+    {0xffffff9, 28},
+    {0xffffffa, 28},
+    {0xffffffb, 28},
+    {0x14, 6},
+    {0x3f8, 10},
+    {0x3f9, 10},
+    {0xffa, 12},
+    {0x1ff9, 13},
+    {0x15, 6},
+    {0xf8, 8},
+    {0x7fa, 11},
+    {0x3fa, 10},
+    {0x3fb, 10},
+    {0xf9, 8},
+    {0x7fb, 11},
+    {0xfa, 8},
+    {0x16, 6},
+    {0x17, 6},
+    {0x18, 6},
+    {0x0, 5},
+    {0x1, 5},
+    {0x2, 5},
+    {0x19, 6},
+    {0x1a, 6},
+    {0x1b, 6},
+    {0x1c, 6},
+    {0x1d, 6},
+    {0x1e, 6},
+    {0x1f, 6},
+    {0x5c, 7},
+    {0xfb, 8},
+    {0x7ffc, 15},
+    {0x20, 6},
+    {0xffb, 12},
+    {0x3fc, 10},
+    {0x1ffa, 13},
+    {0x21, 6},
+    {0x5d, 7},
+    {0x5e, 7},
+    {0x5f, 7},
+    {0x60, 7},
+    {0x61, 7},
+    {0x62, 7},
+    {0x63, 7},
+    {0x64, 7},
+    {0x65, 7},
+    {0x66, 7},
+    {0x67, 7},
+    {0x68, 7},
+    {0x69, 7},
+    {0x6a, 7},
+    {0x6b, 7},
+    {0x6c, 7},
+    {0x6d, 7},
+    {0x6e, 7},
+    {0x6f, 7},
+    {0x70, 7},
+    {0x71, 7},
+    {0x72, 7},
+    {0xfc, 8},
+    {0x73, 7},
+    {0xfd, 8},
+    {0x1ffb, 13},
+    {0x7fff0, 19},
+    {0x1ffc, 13},
+    {0x3ffc, 14},
+    {0x22, 6},
+    {0x7ffd, 15},
+    {0x3, 5},
+    {0x23, 6},
+    {0x4, 5},
+    {0x24, 6},
+    {0x5, 5},
+    {0x25, 6},
+    {0x26, 6},
+    {0x27, 6},
+    {0x6, 5},
+    {0x74, 7},
+    {0x75, 7},
+    {0x28, 6},
+    {0x29, 6},
+    {0x2a, 6},
+    {0x7, 5},
+    {0x2b, 6},
+    {0x76, 7},
+    {0x2c, 6},
+    {0x8, 5},
+    {0x9, 5},
+    {0x2d, 6},
+    {0x77, 7},
+    {0x78, 7},
+    {0x79, 7},
+    {0x7a, 7},
+    {0x7b, 7},
+    {0x7ffe, 15},
+    {0x7fc, 11},
+    {0x3ffd, 14},
+    {0x1ffd, 13},
+    {0xffffffc, 28},
+    {0xfffe6, 20},
+    {0x3fffd2, 22},
+    {0xfffe7, 20},
+    {0xfffe8, 20},
+    {0x3fffd3, 22},
+    {0x3fffd4, 22},
+    {0x3fffd5, 22},
+    {0x7fffd9, 23},
+    {0x3fffd6, 22},
+    {0x7fffda, 23},
+    {0x7fffdb, 23},
+    {0x7fffdc, 23},
+    {0x7fffdd, 23},
+    {0x7fffde, 23},
+    {0xffffeb, 24},
+    {0x7fffdf, 23},
+    {0xffffec, 24},
+    {0xffffed, 24},
+    {0x3fffd7, 22},
+    {0x7fffe0, 23},
+    {0xffffee, 24},
+    {0x7fffe1, 23},
+    {0x7fffe2, 23},
+    {0x7fffe3, 23},
+    {0x7fffe4, 23},
+    {0x1fffdc, 21},
+    {0x3fffd8, 22},
+    {0x7fffe5, 23},
+    {0x3fffd9, 22},
+    {0x7fffe6, 23},
+    {0x7fffe7, 23},
+    {0xffffef, 24},
+    {0x3fffda, 22},
+    {0x1fffdd, 21},
+    {0xfffe9, 20},
+    {0x3fffdb, 22},
+    {0x3fffdc, 22},
+    {0x7fffe8, 23},
+    {0x7fffe9, 23},
+    {0x1fffde, 21},
+    {0x7fffea, 23},
+    {0x3fffdd, 22},
+    {0x3fffde, 22},
+    {0xfffff0, 24},
+    {0x1fffdf, 21},
+    {0x3fffdf, 22},
+    {0x7fffeb, 23},
+    {0x7fffec, 23},
+    {0x1fffe0, 21},
+    {0x1fffe1, 21},
+    {0x3fffe0, 22},
+    {0x1fffe2, 21},
+    {0x7fffed, 23},
+    {0x3fffe1, 22},
+    {0x7fffee, 23},
+    {0x7fffef, 23},
+    {0xfffea, 20},
+    {0x3fffe2, 22},
+    {0x3fffe3, 22},
+    {0x3fffe4, 22},
+    {0x7ffff0, 23},
+    {0x3fffe5, 22},
+    {0x3fffe6, 22},
+    {0x7ffff1, 23},
+    {0x3ffffe0, 26},
+    {0x3ffffe1, 26},
+    {0xfffeb, 20},
+    {0x7fff1, 19},
+    {0x3fffe7, 22},
+    {0x7ffff2, 23},
+    {0x3fffe8, 22},
+    {0x1ffffec, 25},
+    {0x3ffffe2, 26},
+    {0x3ffffe3, 26},
+    {0x3ffffe4, 26},
+    {0x7ffffde, 27},
+    {0x7ffffdf, 27},
+    {0x3ffffe5, 26},
+    {0xfffff1, 24},
+    {0x1ffffed, 25},
+    {0x7fff2, 19},
+    {0x1fffe3, 21},
+    {0x3ffffe6, 26},
+    {0x7ffffe0, 27},
+    {0x7ffffe1, 27},
+    {0x3ffffe7, 26},
+    {0x7ffffe2, 27},
+    {0xfffff2, 24},
+    {0x1fffe4, 21},
+    {0x1fffe5, 21},
+    {0x3ffffe8, 26},
+    {0x3ffffe9, 26},
+    {0xffffffd, 28},
+    {0x7ffffe3, 27},
+    {0x7ffffe4, 27},
+    {0x7ffffe5, 27},
+    {0xfffec, 20},
+    {0xfffff3, 24},
+    {0xfffed, 20},
+    {0x1fffe6, 21},
+    {0x3fffe9, 22},
+    {0x1fffe7, 21},
+    {0x1fffe8, 21},
+    {0x7ffff3, 23},
+    {0x3fffea, 22},
+    {0x3fffeb, 22},
+    {0x1ffffee, 25},
+    {0x1ffffef, 25},
+    {0xfffff4, 24},
+    {0xfffff5, 24},
+    {0x3ffffea, 26},
+    {0x7ffff4, 23},
+    {0x3ffffeb, 26},
+    {0x7ffffe6, 27},
+    {0x3ffffec, 26},
+    {0x3ffffed, 26},
+    {0x7ffffe7, 27},
+    {0x7ffffe8, 27},
+    {0x7ffffe9, 27},
+    {0x7ffffea, 27},
+    {0x7ffffeb, 27},
+    {0xffffffe, 28},
+    {0x7ffffec, 27},
+    {0x7ffffed, 27},
+    {0x7ffffee, 27},
+    {0x7ffffef, 27},
+    {0x7fffff0, 27},
+    {0x3ffffee, 26},
+    {0x3fffffff, 30},
+};
diff --git a/src/core/transport/chttp2/huffsyms.h b/src/core/transport/chttp2/huffsyms.h
new file mode 100644
index 0000000..02d0e53
--- /dev/null
+++ b/src/core/transport/chttp2/huffsyms.h
@@ -0,0 +1,48 @@
+/*
+ *
+ * Copyright 2014, Google Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ *     * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *     * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *     * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#ifndef __GRPC_INTERNAL_TRANSPORT_CHTTP2_HUFFSYMS_H_
+#define __GRPC_INTERNAL_TRANSPORT_CHTTP2_HUFFSYMS_H_
+
+/* HPACK static huffman table */
+
+#define GRPC_CHTTP2_NUM_HUFFSYMS 257
+
+typedef struct {
+  unsigned bits;
+  unsigned length;
+} grpc_chttp2_huffsym;
+
+extern const grpc_chttp2_huffsym grpc_chttp2_huffsyms[GRPC_CHTTP2_NUM_HUFFSYMS];
+
+#endif /* __GRPC_INTERNAL_TRANSPORT_CHTTP2_HUFFSYMS_H_ */
diff --git a/src/core/transport/chttp2_transport.c b/src/core/transport/chttp2_transport.c
index 8a6b427..e6629ac 100644
--- a/src/core/transport/chttp2_transport.c
+++ b/src/core/transport/chttp2_transport.c
@@ -37,23 +37,24 @@
 #include <stdio.h>
 #include <string.h>
 
+#include "src/core/transport/chttp2/frame_data.h"
+#include "src/core/transport/chttp2/frame_goaway.h"
+#include "src/core/transport/chttp2/frame_ping.h"
+#include "src/core/transport/chttp2/frame_rst_stream.h"
+#include "src/core/transport/chttp2/frame_settings.h"
+#include "src/core/transport/chttp2/frame_window_update.h"
+#include "src/core/transport/chttp2/hpack_parser.h"
+#include "src/core/transport/chttp2/http2_errors.h"
+#include "src/core/transport/chttp2/status_conversion.h"
+#include "src/core/transport/chttp2/stream_encoder.h"
+#include "src/core/transport/chttp2/stream_map.h"
+#include "src/core/transport/chttp2/timeout_encoding.h"
+#include "src/core/transport/transport_impl.h"
 #include <grpc/support/alloc.h>
 #include <grpc/support/log.h>
 #include <grpc/support/slice_buffer.h>
 #include <grpc/support/string.h>
 #include <grpc/support/useful.h>
-#include "src/core/transport/transport_impl.h"
-#include "src/core/transport/chttp2/http2_errors.h"
-#include "src/core/transport/chttp2/hpack_parser.h"
-#include "src/core/transport/chttp2/frame_data.h"
-#include "src/core/transport/chttp2/frame_ping.h"
-#include "src/core/transport/chttp2/frame_rst_stream.h"
-#include "src/core/transport/chttp2/frame_settings.h"
-#include "src/core/transport/chttp2/frame_window_update.h"
-#include "src/core/transport/chttp2/status_conversion.h"
-#include "src/core/transport/chttp2/stream_encoder.h"
-#include "src/core/transport/chttp2/stream_map.h"
-#include "src/core/transport/chttp2/timeout_encoding.h"
 
 #define DEFAULT_WINDOW 65536
 #define MAX_WINDOW 0x7fffffffu
@@ -160,6 +161,11 @@
   void *user_data;
 } outstanding_ping;
 
+typedef struct {
+  grpc_status_code status;
+  gpr_slice debug;
+} pending_goaway;
+
 struct transport {
   grpc_transport base; /* must be first */
   const grpc_transport_callbacks *cb;
@@ -180,6 +186,7 @@
 
   /* stream indexing */
   gpr_uint32 next_stream_id;
+  gpr_uint32 last_incoming_stream_id;
 
   /* settings */
   gpr_uint32 settings[NUM_SETTING_SETS][GRPC_CHTTP2_NUM_SETTINGS];
@@ -211,6 +218,12 @@
     grpc_chttp2_ping_parser ping;
   } simple_parsers;
 
+  /* goaway */
+  grpc_chttp2_goaway_parser goaway_parser;
+  pending_goaway *pending_goaways;
+  size_t num_pending_goaways;
+  size_t cap_pending_goaways;
+
   /* state for a stream that's not yet been created */
   grpc_stream_op_buffer new_stream_sopb;
 
@@ -310,6 +323,7 @@
   gpr_slice_buffer_destroy(&t->qbuf);
   grpc_chttp2_hpack_parser_destroy(&t->hpack_parser);
   grpc_chttp2_hpack_compressor_destroy(&t->hpack_compressor);
+  grpc_chttp2_goaway_parser_destroy(&t->goaway_parser);
 
   grpc_mdstr_unref(t->str_grpc_timeout);
 
@@ -332,6 +346,11 @@
   }
   gpr_free(t->pings);
 
+  for (i = 0; i < t->num_pending_goaways; i++) {
+    gpr_slice_unref(t->pending_goaways[i].debug);
+  }
+  gpr_free(t->pending_goaways);
+
   gpr_free(t);
 }
 
@@ -360,6 +379,7 @@
   t->writing = 0;
   t->error_state = ERROR_STATE_NONE;
   t->next_stream_id = is_client ? 1 : 2;
+  t->last_incoming_stream_id = 0;
   t->is_client = is_client;
   t->outgoing_window = DEFAULT_WINDOW;
   t->incoming_window = DEFAULT_WINDOW;
@@ -370,6 +390,10 @@
   t->ping_capacity = 0;
   t->ping_counter = gpr_now().tv_nsec;
   grpc_chttp2_hpack_compressor_init(&t->hpack_compressor, mdctx);
+  grpc_chttp2_goaway_parser_init(&t->goaway_parser);
+  t->pending_goaways = NULL;
+  t->num_pending_goaways = 0;
+  t->cap_pending_goaways = 0;
   gpr_slice_buffer_init(&t->outbuf);
   gpr_slice_buffer_init(&t->qbuf);
   if (is_client) {
@@ -456,6 +480,16 @@
   gpr_mu_unlock(&t->mu);
 }
 
+static void goaway(grpc_transport *gt, grpc_status_code status,
+                   gpr_slice debug_data) {
+  transport *t = (transport *)gt;
+  lock(t);
+  grpc_chttp2_goaway_append(t->last_incoming_stream_id,
+                            grpc_chttp2_grpc_status_to_http2_error(status),
+                            debug_data, &t->qbuf);
+  unlock(t);
+}
+
 static int init_stream(grpc_transport *gt, grpc_stream *gs,
                        const void *server_data) {
   transport *t = (transport *)gt;
@@ -609,6 +643,9 @@
   int start_write = 0;
   int perform_callbacks = 0;
   int call_closed = 0;
+  int num_goaways = 0;
+  int i;
+  pending_goaway *goaways = NULL;
   grpc_endpoint *ep = t->ep;
 
   /* see if we need to trigger a write - and if so, get the data ready */
@@ -630,9 +667,16 @@
       t->calling_back = 1;
       t->error_state = ERROR_STATE_NOTIFIED;
     }
+    if (t->num_pending_goaways) {
+      goaways = t->pending_goaways;
+      num_goaways = t->num_pending_goaways;
+      t->pending_goaways = NULL;
+      t->num_pending_goaways = 0;
+      t->calling_back = 1;
+    }
   }
 
-  if (perform_callbacks || call_closed) {
+  if (perform_callbacks || call_closed || num_goaways) {
     ref_transport(t);
   }
 
@@ -640,6 +684,11 @@
   gpr_mu_unlock(&t->mu);
 
   /* perform some callbacks if necessary */
+  for (i = 0; i < num_goaways; i++) {
+    t->cb->goaway(t->cb_user_data, &t->base, goaways[i].status,
+                  goaways[i].debug);
+  }
+
   if (perform_callbacks) {
     run_callbacks(t);
   }
@@ -698,13 +747,15 @@
     }
   }
 
-  if (perform_callbacks || call_closed) {
+  if (perform_callbacks || call_closed || num_goaways) {
     lock(t);
     t->calling_back = 0;
     gpr_cv_broadcast(&t->cv);
     unlock(t);
     unref_transport(t);
   }
+
+  gpr_free(goaways);
 }
 
 /*
@@ -1130,6 +1181,12 @@
         gpr_log(GPR_ERROR, "ignoring new stream creation on client");
       }
       return init_skip_frame(t, 1);
+    } else if (t->last_incoming_stream_id > t->incoming_stream_id) {
+      gpr_log(GPR_ERROR,
+              "ignoring out of order new stream request on server; last stream "
+              "id=%d, new stream id=%d",
+              t->last_incoming_stream_id, t->incoming_stream);
+      return init_skip_frame(t, 1);
     }
     t->incoming_stream = NULL;
     /* if stream is accepted, we set incoming_stream in init_stream */
@@ -1187,6 +1244,19 @@
   return ok;
 }
 
+static int init_goaway_parser(transport *t) {
+  int ok =
+      GRPC_CHTTP2_PARSE_OK ==
+      grpc_chttp2_goaway_parser_begin_frame(
+          &t->goaway_parser, t->incoming_frame_size, t->incoming_frame_flags);
+  if (!ok) {
+    drop_connection(t);
+  }
+  t->parser = grpc_chttp2_goaway_parser_parse;
+  t->parser_data = &t->goaway_parser;
+  return ok;
+}
+
 static int init_settings_frame_parser(transport *t) {
   int ok = GRPC_CHTTP2_PARSE_OK ==
            grpc_chttp2_settings_parser_begin_frame(
@@ -1240,6 +1310,8 @@
       return init_window_update_frame_parser(t);
     case GRPC_CHTTP2_FRAME_PING:
       return init_ping_parser(t);
+    case GRPC_CHTTP2_FRAME_GOAWAY:
+      return init_goaway_parser(t);
     default:
       gpr_log(GPR_ERROR, "Unknown frame type %02x", t->incoming_frame_type);
       return init_skip_frame(t, 0);
@@ -1277,6 +1349,18 @@
             &t->qbuf,
             grpc_chttp2_ping_create(1, t->simple_parsers.ping.opaque_8bytes));
       }
+      if (st.goaway) {
+        if (t->num_pending_goaways == t->cap_pending_goaways) {
+          t->cap_pending_goaways = GPR_MAX(1, t->cap_pending_goaways * 2);
+          t->pending_goaways =
+              gpr_realloc(t->pending_goaways,
+                          sizeof(pending_goaway) * t->cap_pending_goaways);
+        }
+        t->pending_goaways[t->num_pending_goaways].status =
+            grpc_chttp2_http2_error_to_grpc_status(st.goaway_error);
+        t->pending_goaways[t->num_pending_goaways].debug = st.goaway_text;
+        t->num_pending_goaways++;
+      }
       if (st.process_ping_reply) {
         for (i = 0; i < t->ping_count; i++) {
           if (0 ==
@@ -1455,6 +1539,7 @@
       if (!init_frame_parser(t)) {
         return 0;
       }
+      t->last_incoming_stream_id = t->incoming_stream_id;
       if (t->incoming_frame_size == 0) {
         if (!parse_frame_slice(t, gpr_empty_slice(), 1)) {
           return 0;
@@ -1599,7 +1684,7 @@
 
 static const grpc_transport_vtable vtable = {
     sizeof(stream), init_stream, send_batch, set_allow_window_updates,
-    destroy_stream, abort_stream, close_transport, send_ping,
+    destroy_stream, abort_stream, goaway, close_transport, send_ping,
     destroy_transport};
 
 void grpc_create_chttp2_transport(grpc_transport_setup_callback setup,
diff --git a/src/core/transport/transport.c b/src/core/transport/transport.c
index d3291bb..1c44abc 100644
--- a/src/core/transport/transport.c
+++ b/src/core/transport/transport.c
@@ -38,6 +38,11 @@
   return transport->vtable->sizeof_stream;
 }
 
+void grpc_transport_goaway(grpc_transport *transport, grpc_status_code status,
+                           gpr_slice message) {
+  transport->vtable->goaway(transport, status, message);
+}
+
 void grpc_transport_close(grpc_transport *transport) {
   transport->vtable->close(transport);
 }
diff --git a/src/core/transport/transport.h b/src/core/transport/transport.h
index 1872947..6a089a2 100644
--- a/src/core/transport/transport.h
+++ b/src/core/transport/transport.h
@@ -116,6 +116,10 @@
                      grpc_stream *stream, grpc_stream_op *ops, size_t ops_count,
                      grpc_stream_state final_state);
 
+  /* The transport received a goaway */
+  void (*goaway)(void *user_data, grpc_transport *transport,
+                 grpc_status_code status, gpr_slice debug);
+
   /* The transport has been closed */
   void (*closed)(void *user_data, grpc_transport *transport);
 };
@@ -198,6 +202,10 @@
 void grpc_transport_abort_stream(grpc_transport *transport, grpc_stream *stream,
                                  grpc_status_code status);
 
+/* Advise peer of pending connection termination. */
+void grpc_transport_goaway(struct grpc_transport *transport,
+                           grpc_status_code status, gpr_slice debug_data);
+
 /* Close a transport. Aborts all open streams. */
 void grpc_transport_close(struct grpc_transport *transport);
 
diff --git a/src/core/transport/transport_impl.h b/src/core/transport/transport_impl.h
index 6acdbf2..328ead2 100644
--- a/src/core/transport/transport_impl.h
+++ b/src/core/transport/transport_impl.h
@@ -60,6 +60,10 @@
   void (*abort_stream)(grpc_transport *self, grpc_stream *stream,
                        grpc_status_code status);
 
+  /* implementation of grpc_transport_goaway */
+  void (*goaway)(grpc_transport *self, grpc_status_code status,
+                 gpr_slice debug_data);
+
   /* implementation of grpc_transport_close */
   void (*close)(grpc_transport *self);
 
diff --git a/src/core/tsi/ssl_transport_security.c b/src/core/tsi/ssl_transport_security.c
index 7bd178b..2c74b36 100644
--- a/src/core/tsi/ssl_transport_security.c
+++ b/src/core/tsi/ssl_transport_security.c
@@ -289,6 +289,17 @@
   return result;
 }
 
+/* Logs the SSL error stack. */
+static void log_ssl_error_stack(void) {
+  unsigned long err;
+  while ((err = ERR_get_error()) != 0) {
+    char details[256];
+    ERR_error_string_n(err, details, sizeof(details));
+    gpr_log(GPR_ERROR, "%s", details);
+  }
+}
+
+
 /* Performs an SSL_read and handle errors. */
 static tsi_result do_ssl_read(SSL* ssl, unsigned char* unprotected_bytes,
                               uint32_t* unprotected_bytes_size) {
@@ -312,6 +323,7 @@
         return TSI_UNIMPLEMENTED;
       case SSL_ERROR_SSL:
         gpr_log(GPR_ERROR, "Corruption detected.");
+        log_ssl_error_stack();
         return TSI_DATA_CORRUPTED;
       default:
         gpr_log(GPR_ERROR, "SSL_read failed with error %s.",
@@ -364,7 +376,10 @@
     }
     while (1) {
       X509* certificate_authority = PEM_read_bio_X509(pem, NULL, NULL, "");
-      if (certificate_authority == NULL) break;  /* Done reading. */
+      if (certificate_authority == NULL) {
+        ERR_clear_error();
+        break;  /* Done reading. */
+      }
       if (!SSL_CTX_add_extra_chain_cert(context, certificate_authority)) {
         X509_free(certificate_authority);
         result = TSI_INVALID_ARGUMENT;
@@ -425,7 +440,10 @@
 
   while (1) {
     root = PEM_read_bio_X509_AUX(pem, NULL, NULL, "");
-    if (root == NULL) break;  /* We're at the end of stream. */
+    if (root == NULL) {
+      ERR_clear_error();
+      break;  /* We're at the end of stream. */
+    }
     if (root_names != NULL) {
       root_name = X509_get_subject_name(root);
       if (root_name == NULL) {