Introduce PPB_UDPSocket_Dev.

This change exposes the PPB_UDPSocket_Dev interface and makes it to share the same backend as PPB_UDPSocket_Private.

It doesn't include:
- apps permission check;
- UDP socket options that PPB_UDPSocket_Private doesn't support.
These will be implemented in separate CLs.

BUG=247225
TEST=newly added test_udp_socket.{h,cc}.

Review URL: https://chromiumcodereview.appspot.com/16282005

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@206183 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/test/ppapi/ppapi_browsertest.cc b/chrome/test/ppapi/ppapi_browsertest.cc
index 15853be86..31dabe6 100644
--- a/chrome/test/ppapi/ppapi_browsertest.cc
+++ b/chrome/test/ppapi/ppapi_browsertest.cc
@@ -337,6 +337,46 @@
 TEST_PPAPI_OUT_OF_PROCESS_WITH_SSL_SERVER(TCPSocketPrivateTrusted)
 TEST_PPAPI_IN_PROCESS_WITH_SSL_SERVER(TCPSocketPrivateTrusted)
 
+// UDPSocket tests.
+// UDPSocket_Broadcast is disabled for OSX because it requires root permissions
+// on OSX 10.7+.
+IN_PROC_BROWSER_TEST_F(OutOfProcessPPAPITest, UDPSocket) {
+  RunTestViaHTTP(
+      LIST_TEST(UDPSocket_ReadWrite)
+      LIST_TEST(UDPSocket_SetOption)
+#if !defined(OS_MACOSX)
+      LIST_TEST(UDPSocket_Broadcast)
+#endif
+  );
+}
+IN_PROC_BROWSER_TEST_F(PPAPINaClNewlibTest, UDPSocket) {
+  RunTestViaHTTP(
+      LIST_TEST(UDPSocket_ReadWrite)
+      LIST_TEST(UDPSocket_SetOption)
+#if !defined(OS_MACOSX)
+      LIST_TEST(UDPSocket_Broadcast)
+#endif
+  );
+}
+IN_PROC_BROWSER_TEST_F(PPAPINaClGLibcTest, MAYBE_GLIBC(UDPSocket)) {
+  RunTestViaHTTP(
+      LIST_TEST(UDPSocket_ReadWrite)
+      LIST_TEST(UDPSocket_SetOption)
+#if !defined(OS_MACOSX)
+      LIST_TEST(UDPSocket_Broadcast)
+#endif
+  );
+}
+IN_PROC_BROWSER_TEST_F(PPAPINaClPNaClTest, UDPSocket) {
+  RunTestViaHTTP(
+      LIST_TEST(UDPSocket_ReadWrite)
+      LIST_TEST(UDPSocket_SetOption)
+#if !defined(OS_MACOSX)
+      LIST_TEST(UDPSocket_Broadcast)
+#endif
+  );
+}
+
 // UDPSocketPrivate tests.
 // UDPSocketPrivate_Broadcast is disabled for OSX because it requires
 // root permissions on OSX 10.7+.
diff --git a/native_client_sdk/src/build_tools/sdk_files.list b/native_client_sdk/src/build_tools/sdk_files.list
index 0f227be4..b12f3de7 100644
--- a/native_client_sdk/src/build_tools/sdk_files.list
+++ b/native_client_sdk/src/build_tools/sdk_files.list
@@ -288,6 +288,7 @@
 include/ppapi/c/dev/ppb_text_input_dev.h
 include/ppapi/c/dev/ppb_trace_event_dev.h
 include/ppapi/c/dev/ppb_truetype_font_dev.h
+include/ppapi/c/dev/ppb_udp_socket_dev.h
 include/ppapi/c/dev/ppb_url_util_dev.h
 include/ppapi/c/dev/ppb_var_array_dev.h
 include/ppapi/c/dev/ppb_var_deprecated.h
@@ -389,6 +390,7 @@
 include/ppapi/cpp/dev/tcp_socket_dev.h
 include/ppapi/cpp/dev/text_input_dev.h
 include/ppapi/cpp/dev/truetype_font_dev.h
+include/ppapi/cpp/dev/udp_socket_dev.h
 include/ppapi/cpp/dev/url_util_dev.h
 include/ppapi/cpp/dev/var_array_dev.h
 include/ppapi/cpp/dev/var_dictionary_dev.h
diff --git a/ppapi/api/dev/ppb_udp_socket_dev.idl b/ppapi/api/dev/ppb_udp_socket_dev.idl
new file mode 100644
index 0000000..32ad30a
--- /dev/null
+++ b/ppapi/api/dev/ppb_udp_socket_dev.idl
@@ -0,0 +1,107 @@
+/* Copyright 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/**
+ * This file defines the <code>PPB_UDPSocket_Dev</code> interface.
+ * TODO(yzshen): Tidy up the document.
+ */
+
+[generate_thunk]
+
+label Chrome {
+  M29 = 0.1
+};
+
+[assert_size(4)]
+enum PP_UDPSocket_Option_Dev {
+  // Allows the socket to share the local address to which it will be bound with
+  // other processes. Value's type should be PP_VARTYPE_BOOL.
+  PP_UDPSOCKET_OPTION_ADDRESS_REUSE = 0,
+
+  // Allows sending and receiving packets to and from broadcast addresses.
+  // Value's type should be PP_VARTYPE_BOOL.
+  PP_UDPSOCKET_OPTION_BROADCAST = 1,
+
+  // Specifies the total per-socket buffer space reserved for sends. Value's
+  // type should be PP_VARTYPE_INT32.
+  // Note: This is only treated as a hint for the browser to set the buffer
+  // size. Even if SetOption() reports that this option has been successfully
+  // set, the browser doesn't guarantee it will conform to it.
+  PP_UDPSOCKET_OPTION_SEND_BUFFER_SIZE = 2,
+
+  // Specifies the total per-socket buffer space reserved for receives. Value's
+  // type should be PP_VARTYPE_INT32.
+  // Note: This is only treated as a hint for the browser to set the buffer
+  // size. Even if SetOption() reports that this option has been successfully
+  // set, the browser doesn't guarantee it will conform to it.
+  PP_UDPSOCKET_OPTION_RECV_BUFFER_SIZE = 3
+};
+
+interface PPB_UDPSocket_Dev {
+  /**
+   * Creates a UDP socket resource.
+   */
+  PP_Resource Create([in] PP_Instance instance);
+
+  /**
+   * Determines if a given resource is a UDP socket.
+   */
+  PP_Bool IsUDPSocket([in] PP_Resource resource);
+
+  /**
+   * Binds to the address given by |addr|, which is a PPB_NetAddress_Dev
+   * resource.
+   */
+  int32_t Bind([in] PP_Resource udp_socket,
+               [in] PP_Resource addr,
+               [in] PP_CompletionCallback callback);
+
+  /**
+   * Returns the address that the socket has bound to, as a PPB_NetAddress_Dev
+   * resource.  Bind must be called and succeed first. Returns 0 if Bind fails,
+   * or if Close has been called.
+   */
+  PP_Resource GetBoundAddress([in] PP_Resource udp_socket);
+
+  /**
+   * Performs a non-blocking recvfrom call on socket.
+   * Bind must be called first. |callback| is invoked when recvfrom reads data.
+   * |addr| will store a PPB_NetAddress_Dev resource on success.
+   */
+  int32_t RecvFrom([in] PP_Resource udp_socket,
+                   [out] str_t buffer,
+                   [in] int32_t num_bytes,
+                   [out] PP_Resource addr,
+                   [in] PP_CompletionCallback callback);
+
+  /**
+   * Performs a non-blocking sendto call on the socket.
+   * Bind must be called first. |addr| is a PPB_NetAddress_Dev resource holding
+   * the target address. |callback| is invoked when sendto completes.
+   */
+  int32_t SendTo([in] PP_Resource udp_socket,
+                 [in] str_t buffer,
+                 [in] int32_t num_bytes,
+                 [in] PP_Resource addr,
+                 [in] PP_CompletionCallback callback);
+
+  /**
+   * Cancels all pending reads and writes, and closes the socket.
+   */
+  void Close([in] PP_Resource udp_socket);
+
+  /**
+   * Sets a socket option to |udp_socket|. Should be called before Bind().
+   * See the PP_UDPSocket_Option_Dev description for option names, value types
+   * and allowed values.
+   * Returns PP_OK on success. Otherwise, returns PP_ERROR_BADRESOURCE (if bad
+   * |udp_socket| provided), PP_ERROR_BADARGUMENT (if bad name/value/value's
+   * type provided) or PP_ERROR_FAILED in the case of internal errors.
+   */
+  int32_t SetOption([in] PP_Resource udp_socket,
+                    [in] PP_UDPSocket_Option_Dev name,
+                    [in] PP_Var value,
+                    [in] PP_CompletionCallback callback);
+};
diff --git a/ppapi/c/dev/ppb_udp_socket_dev.h b/ppapi/c/dev/ppb_udp_socket_dev.h
new file mode 100644
index 0000000..5aba580
--- /dev/null
+++ b/ppapi/c/dev/ppb_udp_socket_dev.h
@@ -0,0 +1,128 @@
+/* Copyright 2013 The Chromium Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* From dev/ppb_udp_socket_dev.idl modified Thu Jun 13 09:38:45 2013. */
+
+#ifndef PPAPI_C_DEV_PPB_UDP_SOCKET_DEV_H_
+#define PPAPI_C_DEV_PPB_UDP_SOCKET_DEV_H_
+
+#include "ppapi/c/pp_bool.h"
+#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_instance.h"
+#include "ppapi/c/pp_macros.h"
+#include "ppapi/c/pp_resource.h"
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/c/pp_var.h"
+
+#define PPB_UDPSOCKET_DEV_INTERFACE_0_1 "PPB_UDPSocket(Dev);0.1"
+#define PPB_UDPSOCKET_DEV_INTERFACE PPB_UDPSOCKET_DEV_INTERFACE_0_1
+
+/**
+ * @file
+ * This file defines the <code>PPB_UDPSocket_Dev</code> interface.
+ * TODO(yzshen): Tidy up the document.
+ */
+
+
+/**
+ * @addtogroup Enums
+ * @{
+ */
+typedef enum {
+  /* Allows the socket to share the local address to which it will be bound with
+   * other processes. Value's type should be PP_VARTYPE_BOOL. */
+  PP_UDPSOCKET_OPTION_ADDRESS_REUSE = 0,
+  /* Allows sending and receiving packets to and from broadcast addresses.
+   * Value's type should be PP_VARTYPE_BOOL. */
+  PP_UDPSOCKET_OPTION_BROADCAST = 1,
+  /* Specifies the total per-socket buffer space reserved for sends. Value's
+   * type should be PP_VARTYPE_INT32.
+   * Note: This is only treated as a hint for the browser to set the buffer
+   * size. Even if SetOption() reports that this option has been successfully
+   * set, the browser doesn't guarantee it will conform to it. */
+  PP_UDPSOCKET_OPTION_SEND_BUFFER_SIZE = 2,
+  /* Specifies the total per-socket buffer space reserved for receives. Value's
+   * type should be PP_VARTYPE_INT32.
+   * Note: This is only treated as a hint for the browser to set the buffer
+   * size. Even if SetOption() reports that this option has been successfully
+   * set, the browser doesn't guarantee it will conform to it. */
+  PP_UDPSOCKET_OPTION_RECV_BUFFER_SIZE = 3
+} PP_UDPSocket_Option_Dev;
+PP_COMPILE_ASSERT_SIZE_IN_BYTES(PP_UDPSocket_Option_Dev, 4);
+/**
+ * @}
+ */
+
+/**
+ * @addtogroup Interfaces
+ * @{
+ */
+struct PPB_UDPSocket_Dev_0_1 {
+  /**
+   * Creates a UDP socket resource.
+   */
+  PP_Resource (*Create)(PP_Instance instance);
+  /**
+   * Determines if a given resource is a UDP socket.
+   */
+  PP_Bool (*IsUDPSocket)(PP_Resource resource);
+  /**
+   * Binds to the address given by |addr|, which is a PPB_NetAddress_Dev
+   * resource.
+   */
+  int32_t (*Bind)(PP_Resource udp_socket,
+                  PP_Resource addr,
+                  struct PP_CompletionCallback callback);
+  /**
+   * Returns the address that the socket has bound to, as a PPB_NetAddress_Dev
+   * resource.  Bind must be called and succeed first. Returns 0 if Bind fails,
+   * or if Close has been called.
+   */
+  PP_Resource (*GetBoundAddress)(PP_Resource udp_socket);
+  /**
+   * Performs a non-blocking recvfrom call on socket.
+   * Bind must be called first. |callback| is invoked when recvfrom reads data.
+   * |addr| will store a PPB_NetAddress_Dev resource on success.
+   */
+  int32_t (*RecvFrom)(PP_Resource udp_socket,
+                      char* buffer,
+                      int32_t num_bytes,
+                      PP_Resource* addr,
+                      struct PP_CompletionCallback callback);
+  /**
+   * Performs a non-blocking sendto call on the socket.
+   * Bind must be called first. |addr| is a PPB_NetAddress_Dev resource holding
+   * the target address. |callback| is invoked when sendto completes.
+   */
+  int32_t (*SendTo)(PP_Resource udp_socket,
+                    const char* buffer,
+                    int32_t num_bytes,
+                    PP_Resource addr,
+                    struct PP_CompletionCallback callback);
+  /**
+   * Cancels all pending reads and writes, and closes the socket.
+   */
+  void (*Close)(PP_Resource udp_socket);
+  /**
+   * Sets a socket option to |udp_socket|. Should be called before Bind().
+   * See the PP_UDPSocket_Option_Dev description for option names, value types
+   * and allowed values.
+   * Returns PP_OK on success. Otherwise, returns PP_ERROR_BADRESOURCE (if bad
+   * |udp_socket| provided), PP_ERROR_BADARGUMENT (if bad name/value/value's
+   * type provided) or PP_ERROR_FAILED in the case of internal errors.
+   */
+  int32_t (*SetOption)(PP_Resource udp_socket,
+                       PP_UDPSocket_Option_Dev name,
+                       struct PP_Var value,
+                       struct PP_CompletionCallback callback);
+};
+
+typedef struct PPB_UDPSocket_Dev_0_1 PPB_UDPSocket_Dev;
+/**
+ * @}
+ */
+
+#endif  /* PPAPI_C_DEV_PPB_UDP_SOCKET_DEV_H_ */
+
diff --git a/ppapi/cpp/dev/udp_socket_dev.cc b/ppapi/cpp/dev/udp_socket_dev.cc
new file mode 100644
index 0000000..dd8ffd0
--- /dev/null
+++ b/ppapi/cpp/dev/udp_socket_dev.cc
@@ -0,0 +1,111 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ppapi/cpp/dev/udp_socket_dev.h"
+
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/cpp/completion_callback.h"
+#include "ppapi/cpp/instance_handle.h"
+#include "ppapi/cpp/module_impl.h"
+#include "ppapi/cpp/var.h"
+
+namespace pp {
+
+namespace {
+
+template <> const char* interface_name<PPB_UDPSocket_Dev_0_1>() {
+  return PPB_UDPSOCKET_DEV_INTERFACE_0_1;
+}
+
+}  // namespace
+
+UDPSocket_Dev::UDPSocket_Dev() {
+}
+
+UDPSocket_Dev::UDPSocket_Dev(const InstanceHandle& instance) {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>()) {
+    PassRefFromConstructor(get_interface<PPB_UDPSocket_Dev_0_1>()->Create(
+        instance.pp_instance()));
+  }
+}
+
+UDPSocket_Dev::UDPSocket_Dev(PassRef, PP_Resource resource)
+    : Resource(PASS_REF, resource) {
+}
+
+UDPSocket_Dev::UDPSocket_Dev(const UDPSocket_Dev& other) : Resource(other) {
+}
+
+UDPSocket_Dev::~UDPSocket_Dev() {
+}
+
+UDPSocket_Dev& UDPSocket_Dev::operator=(const UDPSocket_Dev& other) {
+  Resource::operator=(other);
+  return *this;
+}
+
+// static
+bool UDPSocket_Dev::IsAvailable() {
+  return has_interface<PPB_UDPSocket_Dev_0_1>();
+}
+
+int32_t UDPSocket_Dev::Bind(const NetAddress_Dev& addr,
+                            const CompletionCallback& callback) {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>()) {
+    return get_interface<PPB_UDPSocket_Dev_0_1>()->Bind(
+        pp_resource(), addr.pp_resource(), callback.pp_completion_callback());
+  }
+  return callback.MayForce(PP_ERROR_NOINTERFACE);
+}
+
+NetAddress_Dev UDPSocket_Dev::GetBoundAddress() {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>()) {
+    return NetAddress_Dev(
+        PASS_REF,
+        get_interface<PPB_UDPSocket_Dev_0_1>()->GetBoundAddress(pp_resource()));
+  }
+  return NetAddress_Dev();
+}
+
+int32_t UDPSocket_Dev::RecvFrom(
+    char* buffer,
+    int32_t num_bytes,
+    const CompletionCallbackWithOutput<NetAddress_Dev>& callback) {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>()) {
+    return get_interface<PPB_UDPSocket_Dev_0_1>()->RecvFrom(
+        pp_resource(), buffer, num_bytes, callback.output(),
+        callback.pp_completion_callback());
+  }
+  return callback.MayForce(PP_ERROR_NOINTERFACE);
+}
+
+int32_t UDPSocket_Dev::SendTo(const char* buffer,
+                              int32_t num_bytes,
+                              const NetAddress_Dev& addr,
+                              const CompletionCallback& callback) {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>()) {
+    return get_interface<PPB_UDPSocket_Dev_0_1>()->SendTo(
+        pp_resource(), buffer, num_bytes, addr.pp_resource(),
+        callback.pp_completion_callback());
+  }
+  return callback.MayForce(PP_ERROR_NOINTERFACE);
+}
+
+void UDPSocket_Dev::Close() {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>())
+    return get_interface<PPB_UDPSocket_Dev_0_1>()->Close(pp_resource());
+}
+
+int32_t UDPSocket_Dev::SetOption(PP_UDPSocket_Option_Dev name,
+                                 const Var& value,
+                                 const CompletionCallback& callback) {
+  if (has_interface<PPB_UDPSocket_Dev_0_1>()) {
+    return get_interface<PPB_UDPSocket_Dev_0_1>()->SetOption(
+        pp_resource(), name, value.pp_var(),
+        callback.pp_completion_callback());
+  }
+  return callback.MayForce(PP_ERROR_NOINTERFACE);
+}
+
+}  // namespace pp
diff --git a/ppapi/cpp/dev/udp_socket_dev.h b/ppapi/cpp/dev/udp_socket_dev.h
new file mode 100644
index 0000000..9f46f0a
--- /dev/null
+++ b/ppapi/cpp/dev/udp_socket_dev.h
@@ -0,0 +1,57 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PPAPI_CPP_DEV_UDP_SOCKET_DEV_H_
+#define PPAPI_CPP_DEV_UDP_SOCKET_DEV_H_
+
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
+#include "ppapi/cpp/dev/net_address_dev.h"
+#include "ppapi/cpp/pass_ref.h"
+#include "ppapi/cpp/resource.h"
+
+namespace pp {
+
+class CompletionCallback;
+class InstanceHandle;
+class Var;
+
+template <typename T> class CompletionCallbackWithOutput;
+
+class UDPSocket_Dev: public Resource {
+ public:
+  UDPSocket_Dev();
+
+  explicit UDPSocket_Dev(const InstanceHandle& instance);
+
+  UDPSocket_Dev(PassRef, PP_Resource resource);
+
+  UDPSocket_Dev(const UDPSocket_Dev& other);
+
+  virtual ~UDPSocket_Dev();
+
+  UDPSocket_Dev& operator=(const UDPSocket_Dev& other);
+
+  // Returns true if the required interface is available.
+  static bool IsAvailable();
+
+  int32_t Bind(const NetAddress_Dev& addr,
+               const CompletionCallback& callback);
+  NetAddress_Dev GetBoundAddress();
+  int32_t RecvFrom(
+      char* buffer,
+      int32_t num_bytes,
+      const CompletionCallbackWithOutput<NetAddress_Dev>& callback);
+  int32_t SendTo(const char* buffer,
+                 int32_t num_bytes,
+                 const NetAddress_Dev& addr,
+                 const CompletionCallback& callback);
+  void Close();
+  int32_t SetOption(PP_UDPSocket_Option_Dev name,
+                    const Var& value,
+                    const CompletionCallback& callback);
+};
+
+}  // namespace pp
+
+#endif  // PPAPI_CPP_DEV_UDP_SOCKET_DEV_H_
diff --git a/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c b/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c
index 194925e..fcd0d8b 100644
--- a/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c
+++ b/ppapi/native_client/src/untrusted/pnacl_irt_shim/pnacl_shim.c
@@ -28,6 +28,7 @@
 #include "ppapi/c/dev/ppb_text_input_dev.h"
 #include "ppapi/c/dev/ppb_trace_event_dev.h"
 #include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
 #include "ppapi/c/dev/ppb_url_util_dev.h"
 #include "ppapi/c/dev/ppb_var_array_dev.h"
 #include "ppapi/c/dev/ppb_var_dictionary_dev.h"
@@ -209,6 +210,7 @@
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_TextInput_Dev_0_2;
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_Trace_Event_Dev_0_1;
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_TrueTypeFont_Dev_0_1;
+static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1;
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_URLUtil_Dev_0_6;
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_VarArray_Dev_0_1;
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_VarDictionary_Dev_0_1;
@@ -1949,6 +1951,50 @@
 
 /* End wrapper methods for PPB_TrueTypeFont_Dev_0_1 */
 
+/* Begin wrapper methods for PPB_UDPSocket_Dev_0_1 */
+
+static PP_Resource Pnacl_M29_PPB_UDPSocket_Dev_Create(PP_Instance instance) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->Create(instance);
+}
+
+static PP_Bool Pnacl_M29_PPB_UDPSocket_Dev_IsUDPSocket(PP_Resource resource) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->IsUDPSocket(resource);
+}
+
+static int32_t Pnacl_M29_PPB_UDPSocket_Dev_Bind(PP_Resource udp_socket, PP_Resource addr, struct PP_CompletionCallback* callback) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->Bind(udp_socket, addr, *callback);
+}
+
+static PP_Resource Pnacl_M29_PPB_UDPSocket_Dev_GetBoundAddress(PP_Resource udp_socket) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->GetBoundAddress(udp_socket);
+}
+
+static int32_t Pnacl_M29_PPB_UDPSocket_Dev_RecvFrom(PP_Resource udp_socket, char* buffer, int32_t num_bytes, PP_Resource* addr, struct PP_CompletionCallback* callback) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->RecvFrom(udp_socket, buffer, num_bytes, addr, *callback);
+}
+
+static int32_t Pnacl_M29_PPB_UDPSocket_Dev_SendTo(PP_Resource udp_socket, const char* buffer, int32_t num_bytes, PP_Resource addr, struct PP_CompletionCallback* callback) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->SendTo(udp_socket, buffer, num_bytes, addr, *callback);
+}
+
+static void Pnacl_M29_PPB_UDPSocket_Dev_Close(PP_Resource udp_socket) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  iface->Close(udp_socket);
+}
+
+static int32_t Pnacl_M29_PPB_UDPSocket_Dev_SetOption(PP_Resource udp_socket, PP_UDPSocket_Option_Dev name, struct PP_Var* value, struct PP_CompletionCallback* callback) {
+  const struct PPB_UDPSocket_Dev_0_1 *iface = Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1.real_iface;
+  return iface->SetOption(udp_socket, name, *value, *callback);
+}
+
+/* End wrapper methods for PPB_UDPSocket_Dev_0_1 */
+
 /* Begin wrapper methods for PPB_URLUtil_Dev_0_6 */
 
 static void Pnacl_M17_PPB_URLUtil_Dev_Canonicalize(struct PP_Var* _struct_result, struct PP_Var* url, struct PP_URLComponents_Dev* components) {
@@ -4299,6 +4345,17 @@
     .GetTable = (int32_t (*)(PP_Resource font, uint32_t table, int32_t offset, int32_t max_data_length, struct PP_ArrayOutput output, struct PP_CompletionCallback callback))&Pnacl_M26_PPB_TrueTypeFont_Dev_GetTable
 };
 
+struct PPB_UDPSocket_Dev_0_1 Pnacl_Wrappers_PPB_UDPSocket_Dev_0_1 = {
+    .Create = (PP_Resource (*)(PP_Instance instance))&Pnacl_M29_PPB_UDPSocket_Dev_Create,
+    .IsUDPSocket = (PP_Bool (*)(PP_Resource resource))&Pnacl_M29_PPB_UDPSocket_Dev_IsUDPSocket,
+    .Bind = (int32_t (*)(PP_Resource udp_socket, PP_Resource addr, struct PP_CompletionCallback callback))&Pnacl_M29_PPB_UDPSocket_Dev_Bind,
+    .GetBoundAddress = (PP_Resource (*)(PP_Resource udp_socket))&Pnacl_M29_PPB_UDPSocket_Dev_GetBoundAddress,
+    .RecvFrom = (int32_t (*)(PP_Resource udp_socket, char* buffer, int32_t num_bytes, PP_Resource* addr, struct PP_CompletionCallback callback))&Pnacl_M29_PPB_UDPSocket_Dev_RecvFrom,
+    .SendTo = (int32_t (*)(PP_Resource udp_socket, const char* buffer, int32_t num_bytes, PP_Resource addr, struct PP_CompletionCallback callback))&Pnacl_M29_PPB_UDPSocket_Dev_SendTo,
+    .Close = (void (*)(PP_Resource udp_socket))&Pnacl_M29_PPB_UDPSocket_Dev_Close,
+    .SetOption = (int32_t (*)(PP_Resource udp_socket, PP_UDPSocket_Option_Dev name, struct PP_Var value, struct PP_CompletionCallback callback))&Pnacl_M29_PPB_UDPSocket_Dev_SetOption
+};
+
 struct PPB_URLUtil_Dev_0_6 Pnacl_Wrappers_PPB_URLUtil_Dev_0_6 = {
     .Canonicalize = (struct PP_Var (*)(struct PP_Var url, struct PP_URLComponents_Dev* components))&Pnacl_M17_PPB_URLUtil_Dev_Canonicalize,
     .ResolveRelativeToURL = (struct PP_Var (*)(struct PP_Var base_url, struct PP_Var relative_string, struct PP_URLComponents_Dev* components))&Pnacl_M17_PPB_URLUtil_Dev_ResolveRelativeToURL,
@@ -5283,6 +5340,12 @@
   .real_iface = NULL
 };
 
+static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1 = {
+  .iface_macro = PPB_UDPSOCKET_DEV_INTERFACE_0_1,
+  .wrapped_iface = (void *) &Pnacl_Wrappers_PPB_UDPSocket_Dev_0_1,
+  .real_iface = NULL
+};
+
 static struct __PnaclWrapperInfo Pnacl_WrapperInfo_PPB_URLUtil_Dev_0_6 = {
   .iface_macro = PPB_URLUTIL_DEV_INTERFACE_0_6,
   .wrapped_iface = (void *) &Pnacl_Wrappers_PPB_URLUtil_Dev_0_6,
@@ -5789,6 +5852,7 @@
   &Pnacl_WrapperInfo_PPB_TextInput_Dev_0_2,
   &Pnacl_WrapperInfo_PPB_Trace_Event_Dev_0_1,
   &Pnacl_WrapperInfo_PPB_TrueTypeFont_Dev_0_1,
+  &Pnacl_WrapperInfo_PPB_UDPSocket_Dev_0_1,
   &Pnacl_WrapperInfo_PPB_URLUtil_Dev_0_6,
   &Pnacl_WrapperInfo_PPB_VarArray_Dev_0_1,
   &Pnacl_WrapperInfo_PPB_VarDictionary_Dev_0_1,
diff --git a/ppapi/ppapi_proxy.gypi b/ppapi/ppapi_proxy.gypi
index b71178c5..b82c706d 100644
--- a/ppapi/ppapi_proxy.gypi
+++ b/ppapi/ppapi_proxy.gypi
@@ -178,6 +178,10 @@
           'proxy/truetype_font_singleton_resource.h',
           'proxy/udp_socket_private_resource.cc',
           'proxy/udp_socket_private_resource.h',
+          'proxy/udp_socket_resource.cc',
+          'proxy/udp_socket_resource.h',
+          'proxy/udp_socket_resource_base.cc',
+          'proxy/udp_socket_resource_base.h',
           'proxy/url_loader_resource.cc',
           'proxy/url_loader_resource.h',
           'proxy/url_request_info_resource.cc',
diff --git a/ppapi/ppapi_shared.gypi b/ppapi/ppapi_shared.gypi
index a834433..b5b015f 100644
--- a/ppapi/ppapi_shared.gypi
+++ b/ppapi/ppapi_shared.gypi
@@ -229,6 +229,8 @@
           'thunk/ppb_truetype_font_api.h',
           'thunk/ppb_truetype_font_singleton_api.h',
           'thunk/ppb_truetype_font_dev_thunk.cc',
+          'thunk/ppb_udp_socket_api.h',
+          'thunk/ppb_udp_socket_dev_thunk.cc',
           'thunk/ppb_udp_socket_private_api.h',
           'thunk/ppb_udp_socket_private_thunk.cc',
           'thunk/ppb_url_loader_api.h',
diff --git a/ppapi/ppapi_sources.gypi b/ppapi/ppapi_sources.gypi
index 4c6d666..6ddc4d0c 100644
--- a/ppapi/ppapi_sources.gypi
+++ b/ppapi/ppapi_sources.gypi
@@ -77,6 +77,7 @@
       'c/dev/ppb_testing_dev.h',
       'c/dev/ppb_text_input_dev.h',
       'c/dev/ppb_truetype_font_dev.h',
+      'c/dev/ppb_udp_socket_dev.h',
       'c/dev/ppb_url_util_dev.h',
       'c/dev/ppb_var_array_dev.h',
       'c/dev/ppb_var_dictionary_dev.h',
@@ -247,6 +248,8 @@
       'cpp/dev/text_input_dev.h',
       'cpp/dev/truetype_font_dev.cc',
       'cpp/dev/truetype_font_dev.h',
+      'cpp/dev/udp_socket_dev.cc',
+      'cpp/dev/udp_socket_dev.h',
       'cpp/dev/url_util_dev.cc',
       'cpp/dev/url_util_dev.h',
       'cpp/dev/var_array_dev.cc',
@@ -450,6 +453,8 @@
       'tests/test_tcp_socket_private.h',
       'tests/test_truetype_font.cc',
       'tests/test_truetype_font.h',
+      'tests/test_udp_socket.cc',
+      'tests/test_udp_socket.h',
       'tests/test_udp_socket_private.cc',
       'tests/test_udp_socket_private.h',
       'tests/test_url_loader.cc',
diff --git a/ppapi/proxy/interface_list.cc b/ppapi/proxy/interface_list.cc
index 0a62aa1c..46346a132 100644
--- a/ppapi/proxy/interface_list.cc
+++ b/ppapi/proxy/interface_list.cc
@@ -27,6 +27,7 @@
 #include "ppapi/c/dev/ppb_text_input_dev.h"
 #include "ppapi/c/dev/ppb_trace_event_dev.h"
 #include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
 #include "ppapi/c/dev/ppb_url_util_dev.h"
 #include "ppapi/c/dev/ppb_var_array_dev.h"
 #include "ppapi/c/dev/ppb_var_deprecated.h"
diff --git a/ppapi/proxy/resource_creation_proxy.cc b/ppapi/proxy/resource_creation_proxy.cc
index a9300cce..0171dbc 100644
--- a/ppapi/proxy/resource_creation_proxy.cc
+++ b/ppapi/proxy/resource_creation_proxy.cc
@@ -40,6 +40,7 @@
 #include "ppapi/proxy/talk_resource.h"
 #include "ppapi/proxy/truetype_font_resource.h"
 #include "ppapi/proxy/udp_socket_private_resource.h"
+#include "ppapi/proxy/udp_socket_resource.h"
 #include "ppapi/proxy/url_loader_resource.h"
 #include "ppapi/proxy/url_request_info_resource.h"
 #include "ppapi/proxy/url_response_info_resource.h"
@@ -313,6 +314,10 @@
   return PPB_TCPSocket_Private_Proxy::CreateProxyResource(instance);
 }
 
+PP_Resource ResourceCreationProxy::CreateUDPSocket(PP_Instance instance) {
+  return (new UDPSocketResource(GetConnection(), instance))->GetReference();
+}
+
 PP_Resource ResourceCreationProxy::CreateUDPSocketPrivate(
     PP_Instance instance) {
   return (new UDPSocketPrivateResource(
diff --git a/ppapi/proxy/resource_creation_proxy.h b/ppapi/proxy/resource_creation_proxy.h
index d00a8bd..33d3b04 100644
--- a/ppapi/proxy/resource_creation_proxy.h
+++ b/ppapi/proxy/resource_creation_proxy.h
@@ -140,6 +140,7 @@
       PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateTCPSocket(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instance) OVERRIDE;
+  virtual PP_Resource CreateUDPSocket(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateWebSocket(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateX509CertificatePrivate(
diff --git a/ppapi/proxy/udp_socket_private_resource.cc b/ppapi/proxy/udp_socket_private_resource.cc
index 608fc00..cd2333c 100644
--- a/ppapi/proxy/udp_socket_private_resource.cc
+++ b/ppapi/proxy/udp_socket_private_resource.cc
@@ -4,37 +4,14 @@
 
 #include "ppapi/proxy/udp_socket_private_resource.h"
 
-#include <algorithm>
-#include <cstring>
-
-#include "base/basictypes.h"
-#include "base/logging.h"
-#include "ppapi/c/pp_bool.h"
-#include "ppapi/c/pp_completion_callback.h"
-#include "ppapi/c/pp_errors.h"
-#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/shared_impl/tracked_callback.h"
 
 namespace ppapi {
 namespace proxy {
 
-const int32_t UDPSocketPrivateResource::kMaxReadSize = 1024 * 1024;
-const int32_t UDPSocketPrivateResource::kMaxWriteSize = 1024 * 1024;
-
 UDPSocketPrivateResource::UDPSocketPrivateResource(Connection connection,
                                                    PP_Instance instance)
-    : PluginResource(connection, instance),
-      bound_(false),
-      closed_(false),
-      read_buffer_(NULL),
-      bytes_to_read_(-1) {
-  recvfrom_addr_.size = 0;
-  memset(recvfrom_addr_.data, 0,
-         arraysize(recvfrom_addr_.data) * sizeof(*recvfrom_addr_.data));
-  bound_addr_.size = 0;
-  memset(bound_addr_.data, 0,
-         arraysize(bound_addr_.data) * sizeof(*bound_addr_.data));
-
-  SendCreate(BROWSER, PpapiHostMsg_UDPSocketPrivate_Create());
+    : UDPSocketResourceBase(connection, instance) {
 }
 
 UDPSocketPrivateResource::~UDPSocketPrivateResource() {
@@ -48,80 +25,29 @@
 int32_t UDPSocketPrivateResource::SetSocketFeature(
     PP_UDPSocketFeature_Private name,
     PP_Var value) {
-  if (bound_ || closed_)
-    return PP_ERROR_FAILED;
-
-  switch (name) {
-    case PP_UDPSOCKETFEATURE_ADDRESS_REUSE:
-    case PP_UDPSOCKETFEATURE_BROADCAST:
-      if (value.type != PP_VARTYPE_BOOL)
-        return PP_ERROR_BADARGUMENT;
-      SendBoolSocketFeature(static_cast<int32_t>(name),
-                            PP_ToBool(value.value.as_bool));
-      break;
-    default:
-      return PP_ERROR_BADARGUMENT;
-  }
-  return PP_OK;
+  return SetSocketFeatureImpl(name, value);
 }
 
 int32_t UDPSocketPrivateResource::Bind(
     const PP_NetAddress_Private* addr,
     scoped_refptr<TrackedCallback> callback) {
-  if (!addr)
-    return PP_ERROR_BADARGUMENT;
-  if (bound_ || closed_)
-    return PP_ERROR_FAILED;
-  if (TrackedCallback::IsPending(bind_callback_))
-    return PP_ERROR_INPROGRESS;
-
-  bind_callback_ = callback;
-
-  // Send the request, the browser will call us back via BindReply.
-  SendBind(*addr);
-  return PP_OK_COMPLETIONPENDING;
+  return BindImpl(addr, callback);
 }
 
 PP_Bool UDPSocketPrivateResource::GetBoundAddress(PP_NetAddress_Private* addr) {
-  if (!addr || !bound_ || closed_)
-    return PP_FALSE;
-
-  *addr = bound_addr_;
-  return PP_TRUE;
+  return GetBoundAddressImpl(addr);
 }
 
 int32_t UDPSocketPrivateResource::RecvFrom(
     char* buffer,
     int32_t num_bytes,
     scoped_refptr<TrackedCallback> callback) {
-  if (!buffer || num_bytes <= 0)
-    return PP_ERROR_BADARGUMENT;
-  if (!bound_)
-    return PP_ERROR_FAILED;
-  if (TrackedCallback::IsPending(recvfrom_callback_))
-    return PP_ERROR_INPROGRESS;
-
-  read_buffer_ = buffer;
-  bytes_to_read_ = std::min(num_bytes, kMaxReadSize);
-  recvfrom_callback_ = callback;
-
-  // Send the request, the browser will call us back via RecvFromReply.
-  SendRecvFrom(bytes_to_read_);
-  return PP_OK_COMPLETIONPENDING;
+  return RecvFromImpl(buffer, num_bytes, NULL, callback);
 }
 
 PP_Bool UDPSocketPrivateResource::GetRecvFromAddress(
     PP_NetAddress_Private* addr) {
-  if (!addr)
-    return PP_FALSE;
-  *addr = recvfrom_addr_;
-  return PP_TRUE;
-}
-
-void UDPSocketPrivateResource::PostAbortIfNecessary(
-    scoped_refptr<TrackedCallback>* callback) {
-  if (TrackedCallback::IsPending(*callback))
-    (*callback)->PostAbort();
+  return GetRecvFromAddressImpl(addr);
 }
 
 int32_t UDPSocketPrivateResource::SendTo(
@@ -129,123 +55,11 @@
     int32_t num_bytes,
     const PP_NetAddress_Private* addr,
     scoped_refptr<TrackedCallback> callback) {
-  if (!buffer || num_bytes <= 0 || !addr)
-    return PP_ERROR_BADARGUMENT;
-  if (!bound_)
-    return PP_ERROR_FAILED;
-  if (TrackedCallback::IsPending(sendto_callback_))
-    return PP_ERROR_INPROGRESS;
-
-  if (num_bytes > kMaxWriteSize)
-    num_bytes = kMaxWriteSize;
-
-  sendto_callback_ = callback;
-
-  // Send the request, the browser will call us back via SendToReply.
-  SendSendTo(std::string(buffer, num_bytes), *addr);
-  return PP_OK_COMPLETIONPENDING;
+  return SendToImpl(buffer, num_bytes, addr, callback);
 }
 
 void UDPSocketPrivateResource::Close() {
-  if(closed_)
-    return;
-
-  bound_ = false;
-  closed_ = true;
-
-  SendClose();
-
-  PostAbortIfNecessary(&bind_callback_);
-  PostAbortIfNecessary(&recvfrom_callback_);
-  PostAbortIfNecessary(&sendto_callback_);
-}
-
-void UDPSocketPrivateResource::SendBoolSocketFeature(int32_t name, bool value) {
-  PpapiHostMsg_UDPSocketPrivate_SetBoolSocketFeature msg(name, value);
-  Post(BROWSER, msg);
-}
-
-void UDPSocketPrivateResource::SendBind(const PP_NetAddress_Private& addr) {
-  PpapiHostMsg_UDPSocketPrivate_Bind msg(addr);
-  Call<PpapiPluginMsg_UDPSocketPrivate_BindReply>(
-      BROWSER,
-      msg,
-      base::Bind(&UDPSocketPrivateResource::OnPluginMsgBindReply,
-                 base::Unretained(this)));
-}
-
-void UDPSocketPrivateResource::SendRecvFrom(int32_t num_bytes) {
-  PpapiHostMsg_UDPSocketPrivate_RecvFrom msg(num_bytes);
-  Call<PpapiPluginMsg_UDPSocketPrivate_RecvFromReply>(
-      BROWSER,
-      msg,
-      base::Bind(&UDPSocketPrivateResource::OnPluginMsgRecvFromReply,
-                 base::Unretained(this)));
-}
-
-void UDPSocketPrivateResource::SendSendTo(const std::string& buffer,
-                                          const PP_NetAddress_Private& addr) {
-  PpapiHostMsg_UDPSocketPrivate_SendTo msg(buffer, addr);
-  Call<PpapiPluginMsg_UDPSocketPrivate_SendToReply>(
-      BROWSER,
-      msg,
-      base::Bind(&UDPSocketPrivateResource::OnPluginMsgSendToReply,
-                 base::Unretained(this)));
-}
-
-void UDPSocketPrivateResource::SendClose() {
-  PpapiHostMsg_UDPSocketPrivate_Close msg;
-  Post(BROWSER, msg);
-}
-
-void UDPSocketPrivateResource::OnPluginMsgBindReply(
-    const ResourceMessageReplyParams& params,
-    const PP_NetAddress_Private& bound_addr) {
-  if (!TrackedCallback::IsPending(bind_callback_)) {
-    NOTREACHED();
-    return;
-  }
-  if (params.result() == PP_OK)
-    bound_ = true;
-  bound_addr_ = bound_addr;
-  bind_callback_->Run(params.result());
-}
-
-void UDPSocketPrivateResource::OnPluginMsgRecvFromReply(
-    const ResourceMessageReplyParams& params,
-    const std::string& data,
-    const PP_NetAddress_Private& addr) {
-  if (!TrackedCallback::IsPending(recvfrom_callback_) || !read_buffer_) {
-    NOTREACHED();
-    return;
-  }
-  bool succeeded = (params.result() == PP_OK);
-  if (succeeded) {
-    CHECK_LE(static_cast<int32_t>(data.size()), bytes_to_read_);
-    if (!data.empty())
-      memcpy(read_buffer_, data.c_str(), data.size());
-  }
-  read_buffer_ = NULL;
-  bytes_to_read_ = -1;
-  recvfrom_addr_ = addr;
-
-  if (succeeded)
-    recvfrom_callback_->Run(static_cast<int32_t>(data.size()));
-  else
-    recvfrom_callback_->Run(params.result());
-}
-
-void UDPSocketPrivateResource::OnPluginMsgSendToReply(
-    const ResourceMessageReplyParams& params,
-    int32_t bytes_written) {
-  if (!TrackedCallback::IsPending(sendto_callback_)) {
-    NOTREACHED();
-    return;
-  }
-  if (params.result() == PP_OK)
-    sendto_callback_->Run(bytes_written);
-  else
-    sendto_callback_->Run(params.result());
+  CloseImpl();
 }
 
 }  // namespace proxy
diff --git a/ppapi/proxy/udp_socket_private_resource.h b/ppapi/proxy/udp_socket_private_resource.h
index 6403f6b..48f5d71 100644
--- a/ppapi/proxy/udp_socket_private_resource.h
+++ b/ppapi/proxy/udp_socket_private_resource.h
@@ -7,29 +7,20 @@
 
 #include "base/basictypes.h"
 #include "base/compiler_specific.h"
-#include "ppapi/proxy/plugin_resource.h"
 #include "ppapi/proxy/ppapi_proxy_export.h"
-#include "ppapi/shared_impl/tracked_callback.h"
+#include "ppapi/proxy/udp_socket_resource_base.h"
 #include "ppapi/thunk/ppb_udp_socket_private_api.h"
 
 namespace ppapi {
 namespace proxy {
 
 class PPAPI_PROXY_EXPORT UDPSocketPrivateResource
-    : public PluginResource,
+    : public UDPSocketResourceBase,
       public thunk::PPB_UDPSocket_Private_API {
  public:
-  UDPSocketPrivateResource(Connection connection,
-                           PP_Instance instance);
+  UDPSocketPrivateResource(Connection connection, PP_Instance instance);
   virtual ~UDPSocketPrivateResource();
 
-  // The maximum number of bytes that each PpapiHostMsg_PPBUDPSocket_RecvFrom
-  // message is allowed to request.
-  static const int32_t kMaxReadSize;
-  // The maximum number of bytes that each PpapiHostMsg_PPBUDPSocket_SendTo
-  // message is allowed to carry.
-  static const int32_t kMaxWriteSize;
-
   // PluginResource implementation.
   virtual thunk::PPB_UDPSocket_Private_API*
       AsPPB_UDPSocket_Private_API() OVERRIDE;
@@ -51,37 +42,6 @@
   virtual void Close() OVERRIDE;
 
  private:
-  void PostAbortIfNecessary(scoped_refptr<TrackedCallback>* callback);
-
-  void SendBoolSocketFeature(int32_t name, bool value);
-  void SendBind(const PP_NetAddress_Private& addr);
-  void SendRecvFrom(int32_t num_bytes);
-  void SendSendTo(const std::string& buffer,
-                  const PP_NetAddress_Private& addr);
-  void SendClose();
-
-  // IPC message handlers.
-  void OnPluginMsgBindReply(const ResourceMessageReplyParams& params,
-                            const PP_NetAddress_Private& bound_addr);
-  void OnPluginMsgRecvFromReply(const ResourceMessageReplyParams& params,
-                                const std::string& data,
-                                const PP_NetAddress_Private& addr);
-  void OnPluginMsgSendToReply(const ResourceMessageReplyParams& params,
-                              int32_t bytes_written);
-
-  bool bound_;
-  bool closed_;
-
-  scoped_refptr<TrackedCallback> bind_callback_;
-  scoped_refptr<TrackedCallback> recvfrom_callback_;
-  scoped_refptr<TrackedCallback> sendto_callback_;
-
-  char* read_buffer_;
-  int32_t bytes_to_read_;
-
-  PP_NetAddress_Private recvfrom_addr_;
-  PP_NetAddress_Private bound_addr_;
-
   DISALLOW_COPY_AND_ASSIGN(UDPSocketPrivateResource);
 };
 
diff --git a/ppapi/proxy/udp_socket_resource.cc b/ppapi/proxy/udp_socket_resource.cc
new file mode 100644
index 0000000..9a1dbd6
--- /dev/null
+++ b/ppapi/proxy/udp_socket_resource.cc
@@ -0,0 +1,99 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ppapi/proxy/udp_socket_resource.h"
+
+#include "ppapi/shared_impl/tracked_callback.h"
+#include "ppapi/thunk/enter.h"
+#include "ppapi/thunk/ppb_net_address_api.h"
+#include "ppapi/thunk/resource_creation_api.h"
+
+namespace ppapi {
+namespace proxy {
+
+namespace {
+
+typedef thunk::EnterResourceNoLock<thunk::PPB_NetAddress_API>
+    EnterNetAddressNoLock;
+
+}  // namespace
+
+UDPSocketResource::UDPSocketResource(Connection connection,
+                                     PP_Instance instance)
+    : UDPSocketResourceBase(connection, instance) {
+}
+
+UDPSocketResource::~UDPSocketResource() {
+}
+
+thunk::PPB_UDPSocket_API* UDPSocketResource::AsPPB_UDPSocket_API() {
+  return this;
+}
+
+int32_t UDPSocketResource::Bind(PP_Resource addr,
+                                scoped_refptr<TrackedCallback> callback) {
+  EnterNetAddressNoLock enter(addr, true);
+  if (enter.failed())
+    return PP_ERROR_BADARGUMENT;
+
+  return BindImpl(&enter.object()->GetNetAddressPrivate(), callback);
+}
+
+PP_Resource UDPSocketResource::GetBoundAddress() {
+  PP_NetAddress_Private addr_private;
+  if (!GetBoundAddressImpl(&addr_private))
+    return 0;
+
+  thunk::EnterResourceCreationNoLock enter(pp_instance());
+  if (enter.failed())
+    return 0;
+  return enter.functions()->CreateNetAddressFromNetAddressPrivate(
+      pp_instance(), addr_private);
+}
+
+int32_t UDPSocketResource::RecvFrom(char* buffer,
+                                    int32_t num_bytes,
+                                    PP_Resource* addr,
+                                    scoped_refptr<TrackedCallback> callback) {
+  return RecvFromImpl(buffer, num_bytes, addr, callback);
+}
+
+int32_t UDPSocketResource::SendTo(const char* buffer,
+                                  int32_t num_bytes,
+                                  PP_Resource addr,
+                                  scoped_refptr<TrackedCallback> callback) {
+  EnterNetAddressNoLock enter(addr, true);
+  if (enter.failed())
+    return PP_ERROR_BADARGUMENT;
+
+  return SendToImpl(buffer, num_bytes, &enter.object()->GetNetAddressPrivate(),
+                    callback);
+}
+
+void UDPSocketResource::Close() {
+  CloseImpl();
+}
+
+int32_t UDPSocketResource::SetOption(
+    PP_UDPSocket_Option_Dev name,
+    const PP_Var& value,
+    scoped_refptr<TrackedCallback> /* callback */) {
+  // TODO(yzshen): Add support for other options.
+  PP_UDPSocketFeature_Private feature;
+  switch (name) {
+    case PP_UDPSOCKET_OPTION_ADDRESS_REUSE:
+      feature = PP_UDPSOCKETFEATURE_ADDRESS_REUSE;
+      break;
+    case PP_UDPSOCKET_OPTION_BROADCAST:
+      feature = PP_UDPSOCKETFEATURE_BROADCAST;
+      break;
+    default:
+      return PP_ERROR_NOTSUPPORTED;
+  }
+
+  return SetSocketFeatureImpl(feature, value);
+}
+
+}  // namespace proxy
+}  // namespace ppapi
diff --git a/ppapi/proxy/udp_socket_resource.h b/ppapi/proxy/udp_socket_resource.h
new file mode 100644
index 0000000..ac24187
--- /dev/null
+++ b/ppapi/proxy/udp_socket_resource.h
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PPAPI_PROXY_UDP_SOCKET_RESOURCE_H_
+#define PPAPI_PROXY_UDP_SOCKET_RESOURCE_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "ppapi/proxy/ppapi_proxy_export.h"
+#include "ppapi/proxy/udp_socket_resource_base.h"
+#include "ppapi/thunk/ppb_udp_socket_api.h"
+
+namespace ppapi {
+namespace proxy {
+
+class PPAPI_PROXY_EXPORT UDPSocketResource : public UDPSocketResourceBase,
+                                             public thunk::PPB_UDPSocket_API {
+ public:
+  UDPSocketResource(Connection connection, PP_Instance instance);
+  virtual ~UDPSocketResource();
+
+  // PluginResource implementation.
+  virtual thunk::PPB_UDPSocket_API* AsPPB_UDPSocket_API() OVERRIDE;
+
+  // thunk::PPB_UDPSocket_API implementation.
+  virtual int32_t Bind(PP_Resource addr,
+                       scoped_refptr<TrackedCallback> callback) OVERRIDE;
+  virtual PP_Resource GetBoundAddress() OVERRIDE;
+  virtual int32_t RecvFrom(char* buffer,
+                           int32_t num_bytes,
+                           PP_Resource* addr,
+                           scoped_refptr<TrackedCallback> callback) OVERRIDE;
+  virtual int32_t SendTo(const char* buffer,
+                         int32_t num_bytes,
+                         PP_Resource addr,
+                         scoped_refptr<TrackedCallback> callback) OVERRIDE;
+  virtual void Close() OVERRIDE;
+  virtual int32_t SetOption(PP_UDPSocket_Option_Dev name,
+                            const PP_Var& value,
+                            scoped_refptr<TrackedCallback> callback) OVERRIDE;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(UDPSocketResource);
+};
+
+}  // namespace proxy
+}  // namespace ppapi
+
+#endif  // PPAPI_PROXY_UDP_SOCKET_RESOURCE_H_
diff --git a/ppapi/proxy/udp_socket_resource_base.cc b/ppapi/proxy/udp_socket_resource_base.cc
new file mode 100644
index 0000000..5d31890
--- /dev/null
+++ b/ppapi/proxy/udp_socket_resource_base.cc
@@ -0,0 +1,240 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ppapi/proxy/udp_socket_resource_base.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "base/logging.h"
+#include "ppapi/c/pp_bool.h"
+#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/proxy/ppapi_messages.h"
+#include "ppapi/thunk/enter.h"
+#include "ppapi/thunk/resource_creation_api.h"
+
+namespace ppapi {
+namespace proxy {
+
+const int32_t UDPSocketResourceBase::kMaxReadSize = 1024 * 1024;
+const int32_t UDPSocketResourceBase::kMaxWriteSize = 1024 * 1024;
+
+UDPSocketResourceBase::UDPSocketResourceBase(Connection connection,
+                                             PP_Instance instance)
+    : PluginResource(connection, instance),
+      bound_(false),
+      closed_(false),
+      read_buffer_(NULL),
+      bytes_to_read_(-1) {
+  recvfrom_addr_.size = 0;
+  memset(recvfrom_addr_.data, 0,
+         arraysize(recvfrom_addr_.data) * sizeof(*recvfrom_addr_.data));
+  bound_addr_.size = 0;
+  memset(bound_addr_.data, 0,
+         arraysize(bound_addr_.data) * sizeof(*bound_addr_.data));
+
+  SendCreate(BROWSER, PpapiHostMsg_UDPSocketPrivate_Create());
+}
+
+UDPSocketResourceBase::~UDPSocketResourceBase() {
+}
+
+int32_t UDPSocketResourceBase::SetSocketFeatureImpl(
+    PP_UDPSocketFeature_Private name,
+    const PP_Var& value) {
+  if (bound_ || closed_)
+    return PP_ERROR_FAILED;
+
+  switch (name) {
+    case PP_UDPSOCKETFEATURE_ADDRESS_REUSE:
+    case PP_UDPSOCKETFEATURE_BROADCAST: {
+      if (value.type != PP_VARTYPE_BOOL)
+        return PP_ERROR_BADARGUMENT;
+      Post(BROWSER,
+           PpapiHostMsg_UDPSocketPrivate_SetBoolSocketFeature(
+               static_cast<int32_t>(name), PP_ToBool(value.value.as_bool)));
+      break;
+    }
+    default: {
+      return PP_ERROR_BADARGUMENT;
+    }
+  }
+  return PP_OK;
+}
+
+int32_t UDPSocketResourceBase::BindImpl(
+    const PP_NetAddress_Private* addr,
+    scoped_refptr<TrackedCallback> callback) {
+  if (!addr)
+    return PP_ERROR_BADARGUMENT;
+  if (bound_ || closed_)
+    return PP_ERROR_FAILED;
+  if (TrackedCallback::IsPending(bind_callback_))
+    return PP_ERROR_INPROGRESS;
+
+  bind_callback_ = callback;
+
+  // Send the request, the browser will call us back via BindReply.
+  Call<PpapiPluginMsg_UDPSocketPrivate_BindReply>(
+      BROWSER,
+      PpapiHostMsg_UDPSocketPrivate_Bind(*addr),
+      base::Bind(&UDPSocketResourceBase::OnPluginMsgBindReply,
+                 base::Unretained(this)));
+  return PP_OK_COMPLETIONPENDING;
+}
+
+PP_Bool UDPSocketResourceBase::GetBoundAddressImpl(
+    PP_NetAddress_Private* addr) {
+  if (!addr || !bound_ || closed_)
+    return PP_FALSE;
+
+  *addr = bound_addr_;
+  return PP_TRUE;
+}
+
+int32_t UDPSocketResourceBase::RecvFromImpl(
+    char* buffer,
+    int32_t num_bytes,
+    PP_Resource* addr,
+    scoped_refptr<TrackedCallback> callback) {
+  if (!buffer || num_bytes <= 0)
+    return PP_ERROR_BADARGUMENT;
+  if (!bound_)
+    return PP_ERROR_FAILED;
+  if (TrackedCallback::IsPending(recvfrom_callback_))
+    return PP_ERROR_INPROGRESS;
+
+  read_buffer_ = buffer;
+  bytes_to_read_ = std::min(num_bytes, kMaxReadSize);
+  recvfrom_callback_ = callback;
+
+  // Send the request, the browser will call us back via RecvFromReply.
+  Call<PpapiPluginMsg_UDPSocketPrivate_RecvFromReply>(
+      BROWSER,
+      PpapiHostMsg_UDPSocketPrivate_RecvFrom(bytes_to_read_),
+      base::Bind(&UDPSocketResourceBase::OnPluginMsgRecvFromReply,
+                 base::Unretained(this), addr));
+  return PP_OK_COMPLETIONPENDING;
+}
+
+PP_Bool UDPSocketResourceBase::GetRecvFromAddressImpl(
+    PP_NetAddress_Private* addr) {
+  if (!addr)
+    return PP_FALSE;
+  *addr = recvfrom_addr_;
+  return PP_TRUE;
+}
+
+int32_t UDPSocketResourceBase::SendToImpl(
+    const char* buffer,
+    int32_t num_bytes,
+    const PP_NetAddress_Private* addr,
+    scoped_refptr<TrackedCallback> callback) {
+  if (!buffer || num_bytes <= 0 || !addr)
+    return PP_ERROR_BADARGUMENT;
+  if (!bound_)
+    return PP_ERROR_FAILED;
+  if (TrackedCallback::IsPending(sendto_callback_))
+    return PP_ERROR_INPROGRESS;
+
+  if (num_bytes > kMaxWriteSize)
+    num_bytes = kMaxWriteSize;
+
+  sendto_callback_ = callback;
+
+  // Send the request, the browser will call us back via SendToReply.
+  Call<PpapiPluginMsg_UDPSocketPrivate_SendToReply>(
+      BROWSER,
+      PpapiHostMsg_UDPSocketPrivate_SendTo(
+          std::string(buffer, num_bytes), *addr),
+      base::Bind(&UDPSocketResourceBase::OnPluginMsgSendToReply,
+                 base::Unretained(this)));
+  return PP_OK_COMPLETIONPENDING;
+}
+
+void UDPSocketResourceBase::CloseImpl() {
+  if(closed_)
+    return;
+
+  bound_ = false;
+  closed_ = true;
+
+  Post(BROWSER, PpapiHostMsg_UDPSocketPrivate_Close());
+
+  PostAbortIfNecessary(&bind_callback_);
+  PostAbortIfNecessary(&recvfrom_callback_);
+  PostAbortIfNecessary(&sendto_callback_);
+}
+
+void UDPSocketResourceBase::PostAbortIfNecessary(
+    scoped_refptr<TrackedCallback>* callback) {
+  if (TrackedCallback::IsPending(*callback))
+    (*callback)->PostAbort();
+}
+
+void UDPSocketResourceBase::OnPluginMsgBindReply(
+    const ResourceMessageReplyParams& params,
+    const PP_NetAddress_Private& bound_addr) {
+  if (!TrackedCallback::IsPending(bind_callback_)) {
+    NOTREACHED();
+    return;
+  }
+  if (params.result() == PP_OK)
+    bound_ = true;
+  bound_addr_ = bound_addr;
+  bind_callback_->Run(params.result());
+}
+
+void UDPSocketResourceBase::OnPluginMsgRecvFromReply(
+    PP_Resource* output_addr,
+    const ResourceMessageReplyParams& params,
+    const std::string& data,
+    const PP_NetAddress_Private& addr) {
+  if (!TrackedCallback::IsPending(recvfrom_callback_)) {
+    NOTREACHED();
+    return;
+  }
+  bool succeeded = (params.result() == PP_OK);
+  if (succeeded && output_addr) {
+    thunk::EnterResourceCreationNoLock enter(pp_instance());
+    if (enter.succeeded()) {
+      *output_addr = enter.functions()->CreateNetAddressFromNetAddressPrivate(
+          pp_instance(), addr);
+    } else {
+      succeeded = false;
+    }
+  }
+
+  if (succeeded) {
+    CHECK_LE(static_cast<int32_t>(data.size()), bytes_to_read_);
+    if (!data.empty())
+      memcpy(read_buffer_, data.c_str(), data.size());
+  }
+
+  read_buffer_ = NULL;
+  bytes_to_read_ = -1;
+  recvfrom_addr_ = addr;
+
+  if (succeeded)
+    recvfrom_callback_->Run(static_cast<int32_t>(data.size()));
+  else
+    recvfrom_callback_->Run(params.result());
+}
+
+void UDPSocketResourceBase::OnPluginMsgSendToReply(
+    const ResourceMessageReplyParams& params,
+    int32_t bytes_written) {
+  if (!TrackedCallback::IsPending(sendto_callback_)) {
+    NOTREACHED();
+    return;
+  }
+  if (params.result() == PP_OK)
+    sendto_callback_->Run(bytes_written);
+  else
+    sendto_callback_->Run(params.result());
+}
+
+}  // namespace proxy
+}  // namespace ppapi
diff --git a/ppapi/proxy/udp_socket_resource_base.h b/ppapi/proxy/udp_socket_resource_base.h
new file mode 100644
index 0000000..b3a6669
--- /dev/null
+++ b/ppapi/proxy/udp_socket_resource_base.h
@@ -0,0 +1,86 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PPAPI_PROXY_UDP_SOCKET_SHARED_H_
+#define PPAPI_PROXY_UDP_SOCKET_SHARED_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "ppapi/c/private/ppb_net_address_private.h"
+#include "ppapi/c/private/ppb_udp_socket_private.h"
+#include "ppapi/proxy/plugin_resource.h"
+#include "ppapi/proxy/ppapi_proxy_export.h"
+#include "ppapi/shared_impl/tracked_callback.h"
+
+namespace ppapi {
+namespace proxy {
+
+class ResourceMessageReplyParams;
+
+class PPAPI_PROXY_EXPORT UDPSocketResourceBase: public PluginResource {
+ public:
+  // The maximum number of bytes that each PpapiHostMsg_PPBUDPSocket_RecvFrom
+  // message is allowed to request.
+  static const int32_t kMaxReadSize;
+  // The maximum number of bytes that each PpapiHostMsg_PPBUDPSocket_SendTo
+  // message is allowed to carry.
+  static const int32_t kMaxWriteSize;
+
+ protected:
+  UDPSocketResourceBase(Connection connection, PP_Instance instance);
+  virtual ~UDPSocketResourceBase();
+
+  int32_t SetSocketFeatureImpl(PP_UDPSocketFeature_Private name,
+                               const PP_Var& value);
+  int32_t BindImpl(const PP_NetAddress_Private* addr,
+                   scoped_refptr<TrackedCallback> callback);
+  PP_Bool GetBoundAddressImpl(PP_NetAddress_Private* addr);
+  // |addr| could be NULL to indicate that an output value is not needed.
+  int32_t RecvFromImpl(char* buffer,
+                       int32_t num_bytes,
+                       PP_Resource* addr,
+                       scoped_refptr<TrackedCallback> callback);
+  PP_Bool GetRecvFromAddressImpl(PP_NetAddress_Private* addr);
+  int32_t SendToImpl(const char* buffer,
+                     int32_t num_bytes,
+                     const PP_NetAddress_Private* addr,
+                     scoped_refptr<TrackedCallback> callback);
+  void CloseImpl();
+
+ private:
+  void PostAbortIfNecessary(scoped_refptr<TrackedCallback>* callback);
+
+  // IPC message handlers.
+  void OnPluginMsgBindReply(const ResourceMessageReplyParams& params,
+                            const PP_NetAddress_Private& bound_addr);
+  void OnPluginMsgRecvFromReply(PP_Resource* output_addr,
+                                const ResourceMessageReplyParams& params,
+                                const std::string& data,
+                                const PP_NetAddress_Private& addr);
+  void OnPluginMsgSendToReply(const ResourceMessageReplyParams& params,
+                              int32_t bytes_written);
+
+  bool bound_;
+  bool closed_;
+
+  scoped_refptr<TrackedCallback> bind_callback_;
+  scoped_refptr<TrackedCallback> recvfrom_callback_;
+  scoped_refptr<TrackedCallback> sendto_callback_;
+
+  char* read_buffer_;
+  int32_t bytes_to_read_;
+
+  PP_NetAddress_Private recvfrom_addr_;
+  PP_NetAddress_Private bound_addr_;
+
+  DISALLOW_COPY_AND_ASSIGN(UDPSocketResourceBase);
+};
+
+}  // namespace proxy
+}  // namespace ppapi
+
+#endif  // PPAPI_PROXY_UDP_SOCKET_SHARED_H_
diff --git a/ppapi/shared_impl/resource.h b/ppapi/shared_impl/resource.h
index 05b3d514..72a060c 100644
--- a/ppapi/shared_impl/resource.h
+++ b/ppapi/shared_impl/resource.h
@@ -65,6 +65,7 @@
   F(PPB_TCPServerSocket_Private_API) \
   F(PPB_TCPSocket_API) \
   F(PPB_TCPSocket_Private_API) \
+  F(PPB_UDPSocket_API) \
   F(PPB_UDPSocket_Private_API) \
   F(PPB_URLLoader_API) \
   F(PPB_URLRequestInfo_API) \
diff --git a/ppapi/tests/all_c_includes.h b/ppapi/tests/all_c_includes.h
index 4a8ae13e..1a18294a 100644
--- a/ppapi/tests/all_c_includes.h
+++ b/ppapi/tests/all_c_includes.h
@@ -31,6 +31,7 @@
 #include "ppapi/c/dev/ppb_text_input_dev.h"
 #include "ppapi/c/dev/ppb_trace_event_dev.h"
 #include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
 #include "ppapi/c/dev/ppb_url_util_dev.h"
 #include "ppapi/c/dev/ppb_var_array_dev.h"
 #include "ppapi/c/dev/ppb_var_deprecated.h"
diff --git a/ppapi/tests/all_cpp_includes.h b/ppapi/tests/all_cpp_includes.h
index 37a4100..717d743 100644
--- a/ppapi/tests/all_cpp_includes.h
+++ b/ppapi/tests/all_cpp_includes.h
@@ -28,6 +28,7 @@
 #include "ppapi/cpp/dev/selection_dev.h"
 #include "ppapi/cpp/dev/tcp_socket_dev.h"
 #include "ppapi/cpp/dev/text_input_dev.h"
+#include "ppapi/cpp/dev/udp_socket_dev.h"
 #include "ppapi/cpp/dev/url_util_dev.h"
 #include "ppapi/cpp/dev/var_array_dev.h"
 #include "ppapi/cpp/dev/var_dictionary_dev.h"
diff --git a/ppapi/tests/test_udp_socket.cc b/ppapi/tests/test_udp_socket.cc
new file mode 100644
index 0000000..d3bb27c
--- /dev/null
+++ b/ppapi/tests/test_udp_socket.cc
@@ -0,0 +1,271 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "ppapi/tests/test_udp_socket.h"
+
+#include <vector>
+
+#include "ppapi/cpp/dev/tcp_socket_dev.h"
+#include "ppapi/cpp/dev/udp_socket_dev.h"
+#include "ppapi/cpp/pass_ref.h"
+#include "ppapi/cpp/var.h"
+#include "ppapi/tests/test_utils.h"
+#include "ppapi/tests/testing_instance.h"
+
+REGISTER_TEST_CASE(UDPSocket);
+
+namespace {
+
+const uint16_t kPortScanFrom = 1024;
+const uint16_t kPortScanTo = 4096;
+
+pp::NetAddress_Dev ReplacePort(const pp::InstanceHandle& instance,
+                               const pp::NetAddress_Dev& addr,
+                               uint16_t port) {
+  switch (addr.GetFamily()) {
+    case PP_NETADDRESS_FAMILY_IPV4: {
+      PP_NetAddress_IPv4_Dev ipv4_addr;
+      if (!addr.DescribeAsIPv4Address(&ipv4_addr))
+        break;
+      ipv4_addr.port = ConvertToNetEndian16(port);
+      return pp::NetAddress_Dev(instance, ipv4_addr);
+    }
+    case PP_NETADDRESS_FAMILY_IPV6: {
+      PP_NetAddress_IPv6_Dev ipv6_addr;
+      if (!addr.DescribeAsIPv6Address(&ipv6_addr))
+        break;
+      ipv6_addr.port = ConvertToNetEndian16(port);
+      return pp::NetAddress_Dev(instance, ipv6_addr);
+    }
+    default: {
+      PP_NOTREACHED();
+    }
+  }
+  return pp::NetAddress_Dev();
+}
+
+}  // namespace
+
+TestUDPSocket::TestUDPSocket(TestingInstance* instance) : TestCase(instance) {
+}
+
+bool TestUDPSocket::Init() {
+  bool tcp_socket_is_available = pp::TCPSocket_Dev::IsAvailable();
+  if (!tcp_socket_is_available)
+    instance_->AppendError("PPB_TCPSocket interface not available");
+
+  bool udp_socket_is_available = pp::UDPSocket_Dev::IsAvailable();
+  if (!udp_socket_is_available)
+    instance_->AppendError("PPB_UDPSocket interface not available");
+
+  bool net_address_is_available = pp::NetAddress_Dev::IsAvailable();
+  if (!net_address_is_available)
+    instance_->AppendError("PPB_NetAddress interface not available");
+
+  std::string host;
+  uint16_t port = 0;
+  bool init_address =
+      GetLocalHostPort(instance_->pp_instance(), &host, &port) &&
+      ResolveHost(instance_->pp_instance(), host, port, &address_);
+  if (!init_address)
+    instance_->AppendError("Can't init address");
+
+  return tcp_socket_is_available &&
+      udp_socket_is_available &&
+      net_address_is_available &&
+      init_address &&
+      CheckTestingInterface() &&
+      EnsureRunningOverHTTP();
+}
+
+void TestUDPSocket::RunTests(const std::string& filter) {
+  RUN_CALLBACK_TEST(TestUDPSocket, ReadWrite, filter);
+  RUN_CALLBACK_TEST(TestUDPSocket, Broadcast, filter);
+  RUN_CALLBACK_TEST(TestUDPSocket, SetOption, filter);
+}
+
+std::string TestUDPSocket::GetLocalAddress(pp::NetAddress_Dev* address) {
+  pp::TCPSocket_Dev socket(instance_);
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  callback.WaitForResult(socket.Connect(address_, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+  *address = socket.GetLocalAddress();
+  ASSERT_NE(0, address->pp_resource());
+  socket.Close();
+  PASS();
+}
+
+std::string TestUDPSocket::SetBroadcastOptions(pp::UDPSocket_Dev* socket) {
+  TestCompletionCallback callback_1(instance_->pp_instance(), callback_type());
+  callback_1.WaitForResult(socket->SetOption(
+      PP_UDPSOCKET_OPTION_ADDRESS_REUSE, pp::Var(true),
+      callback_1.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback_1);
+  ASSERT_EQ(PP_OK, callback_1.result());
+
+  TestCompletionCallback callback_2(instance_->pp_instance(), callback_type());
+  callback_2.WaitForResult(socket->SetOption(
+      PP_UDPSOCKET_OPTION_BROADCAST, pp::Var(true), callback_2.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback_2);
+  ASSERT_EQ(PP_OK, callback_2.result());
+
+  PASS();
+}
+
+std::string TestUDPSocket::BindUDPSocket(pp::UDPSocket_Dev* socket,
+                                         const pp::NetAddress_Dev& address) {
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  callback.WaitForResult(socket->Bind(address, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_OK, callback.result());
+  PASS();
+}
+
+std::string TestUDPSocket::LookupPortAndBindUDPSocket(
+    pp::UDPSocket_Dev* socket,
+    pp::NetAddress_Dev* address) {
+  pp::NetAddress_Dev base_address;
+  ASSERT_SUBTEST_SUCCESS(GetLocalAddress(&base_address));
+
+  bool is_free_port_found = false;
+  for (uint16_t port = kPortScanFrom; port < kPortScanTo; ++port) {
+    pp::NetAddress_Dev new_address = ReplacePort(instance_, base_address, port);
+    ASSERT_NE(0, new_address.pp_resource());
+    if (BindUDPSocket(socket, new_address).empty()) {
+      is_free_port_found = true;
+      break;
+    }
+  }
+  if (!is_free_port_found)
+    return "Can't find available port";
+
+  *address = socket->GetBoundAddress();
+  ASSERT_NE(0, address->pp_resource());
+
+  PASS();
+}
+
+std::string TestUDPSocket::ReadSocket(pp::UDPSocket_Dev* socket,
+                                      pp::NetAddress_Dev* address,
+                                      size_t size,
+                                      std::string* message) {
+  std::vector<char> buffer(size);
+  TestCompletionCallbackWithOutput<pp::NetAddress_Dev> callback(
+      instance_->pp_instance(), callback_type());
+  callback.WaitForResult(
+      socket->RecvFrom(&buffer[0], size, callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_FALSE(callback.result() < 0);
+  ASSERT_EQ(size, static_cast<size_t>(callback.result()));
+  *address = callback.output();
+  message->assign(buffer.begin(), buffer.end());
+  PASS();
+}
+
+std::string TestUDPSocket::PassMessage(pp::UDPSocket_Dev* target,
+                                       pp::UDPSocket_Dev* source,
+                                       const pp::NetAddress_Dev& target_address,
+                                       const std::string& message,
+                                       pp::NetAddress_Dev* recvfrom_address) {
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  int32_t rv = source->SendTo(message.c_str(), message.size(),
+                              target_address,
+                              callback.GetCallback());
+  std::string str;
+  ASSERT_SUBTEST_SUCCESS(ReadSocket(target, recvfrom_address, message.size(),
+                                    &str));
+
+  callback.WaitForResult(rv);
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_FALSE(callback.result() < 0);
+  ASSERT_EQ(message.size(), static_cast<size_t>(callback.result()));
+  ASSERT_EQ(message, str);
+  PASS();
+}
+
+std::string TestUDPSocket::TestReadWrite() {
+  pp::UDPSocket_Dev server_socket(instance_), client_socket(instance_);
+  pp::NetAddress_Dev server_address, client_address;
+
+  ASSERT_SUBTEST_SUCCESS(LookupPortAndBindUDPSocket(&server_socket,
+                                                    &server_address));
+  ASSERT_SUBTEST_SUCCESS(LookupPortAndBindUDPSocket(&client_socket,
+                                                    &client_address));
+  const std::string message = "Simple message that will be sent via UDP";
+  pp::NetAddress_Dev recvfrom_address;
+  ASSERT_SUBTEST_SUCCESS(PassMessage(&server_socket, &client_socket,
+                                     server_address, message,
+                                     &recvfrom_address));
+  ASSERT_TRUE(EqualNetAddress(recvfrom_address, client_address));
+
+  server_socket.Close();
+  client_socket.Close();
+
+  if (server_socket.GetBoundAddress().pp_resource() != 0)
+    return "PPB_UDPSocket::GetBoundAddress: expected failure";
+
+  PASS();
+}
+
+std::string TestUDPSocket::TestBroadcast() {
+  pp::UDPSocket_Dev server1(instance_), server2(instance_);
+
+  ASSERT_SUBTEST_SUCCESS(SetBroadcastOptions(&server1));
+  ASSERT_SUBTEST_SUCCESS(SetBroadcastOptions(&server2));
+
+  PP_NetAddress_IPv4_Dev any_ipv4_address = { 0, { 0, 0, 0, 0 } };
+  pp::NetAddress_Dev any_address(instance_, any_ipv4_address);
+  ASSERT_SUBTEST_SUCCESS(BindUDPSocket(&server1, any_address));
+  // Fill port field of |server_address|.
+  pp::NetAddress_Dev server_address = server1.GetBoundAddress();
+  ASSERT_NE(0, server_address.pp_resource());
+  ASSERT_SUBTEST_SUCCESS(BindUDPSocket(&server2, server_address));
+
+  PP_NetAddress_IPv4_Dev server_ipv4_address;
+  ASSERT_TRUE(server_address.DescribeAsIPv4Address(&server_ipv4_address));
+
+  PP_NetAddress_IPv4_Dev broadcast_ipv4_address = {
+    server_ipv4_address.port, { 0xff, 0xff, 0xff, 0xff }
+  };
+  pp::NetAddress_Dev broadcast_address(instance_, broadcast_ipv4_address);
+
+  std::string message;
+  const std::string first_message = "first message";
+  const std::string second_message = "second_message";
+
+  pp::NetAddress_Dev recvfrom_address;
+  ASSERT_SUBTEST_SUCCESS(PassMessage(&server1, &server2, broadcast_address,
+                                     first_message, &recvfrom_address));
+  // |first_message| was also received by |server2|.
+  ASSERT_SUBTEST_SUCCESS(ReadSocket(&server2, &recvfrom_address,
+                                    first_message.size(), &message));
+  ASSERT_EQ(first_message, message);
+
+  ASSERT_SUBTEST_SUCCESS(PassMessage(&server2, &server1, broadcast_address,
+                                     second_message, &recvfrom_address));
+  // |second_message| was also received by |server1|.
+  ASSERT_SUBTEST_SUCCESS(ReadSocket(&server1, &recvfrom_address,
+                                    second_message.size(), &message));
+  ASSERT_EQ(second_message, message);
+
+  server1.Close();
+  server2.Close();
+  PASS();
+}
+
+std::string TestUDPSocket::TestSetOption() {
+  pp::UDPSocket_Dev socket(instance_);
+
+  ASSERT_SUBTEST_SUCCESS(SetBroadcastOptions(&socket));
+
+  // Try to pass incorrect option value's type.
+  TestCompletionCallback callback(instance_->pp_instance(), callback_type());
+  callback.WaitForResult(socket.SetOption(
+      PP_UDPSOCKET_OPTION_ADDRESS_REUSE, pp::Var(1), callback.GetCallback()));
+  CHECK_CALLBACK_BEHAVIOR(callback);
+  ASSERT_EQ(PP_ERROR_BADARGUMENT, callback.result());
+
+  PASS();
+}
diff --git a/ppapi/tests/test_udp_socket.h b/ppapi/tests/test_udp_socket.h
new file mode 100644
index 0000000..986806c
--- /dev/null
+++ b/ppapi/tests/test_udp_socket.h
@@ -0,0 +1,50 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PPAPI_TESTS_TEST_UDP_SOCKET_H_
+#define PPAPI_TESTS_TEST_UDP_SOCKET_H_
+
+#include <string>
+
+#include "ppapi/c/pp_stdint.h"
+#include "ppapi/cpp/dev/net_address_dev.h"
+#include "ppapi/tests/test_case.h"
+
+namespace pp {
+class UDPSocket_Dev;
+}
+
+class TestUDPSocket: public TestCase {
+ public:
+  explicit TestUDPSocket(TestingInstance* instance);
+
+  // TestCase implementation.
+  virtual bool Init();
+  virtual void RunTests(const std::string& filter);
+
+ private:
+  std::string GetLocalAddress(pp::NetAddress_Dev* address);
+  std::string SetBroadcastOptions(pp::UDPSocket_Dev* socket);
+  std::string BindUDPSocket(pp::UDPSocket_Dev* socket,
+                            const pp::NetAddress_Dev& address);
+  std::string LookupPortAndBindUDPSocket(pp::UDPSocket_Dev* socket,
+                                         pp::NetAddress_Dev* address);
+  std::string ReadSocket(pp::UDPSocket_Dev* socket,
+                         pp::NetAddress_Dev* address,
+                         size_t size,
+                         std::string* message);
+  std::string PassMessage(pp::UDPSocket_Dev* target,
+                          pp::UDPSocket_Dev* source,
+                          const pp::NetAddress_Dev& target_address,
+                          const std::string& message,
+                          pp::NetAddress_Dev* recvfrom_address);
+
+  std::string TestReadWrite();
+  std::string TestBroadcast();
+  std::string TestSetOption();
+
+  pp::NetAddress_Dev address_;
+};
+
+#endif  // PPAPI_TESTS_TEST_UDP_SOCKET_H_
diff --git a/ppapi/thunk/interfaces_ppb_public_dev.h b/ppapi/thunk/interfaces_ppb_public_dev.h
index e953811..db1ab469 100644
--- a/ppapi/thunk/interfaces_ppb_public_dev.h
+++ b/ppapi/thunk/interfaces_ppb_public_dev.h
@@ -42,6 +42,8 @@
               PPB_TextInput_Dev_0_2)
 PROXIED_IFACE(NoAPIName, PPB_TRUETYPEFONT_DEV_INTERFACE_0_1,
               PPB_TrueTypeFont_Dev_0_1)
+PROXIED_IFACE(NoAPIName, PPB_UDPSOCKET_DEV_INTERFACE_0_1,
+              PPB_UDPSocket_Dev_0_1)
 PROXIED_IFACE(NoAPIName, PPB_VAR_ARRAY_DEV_INTERFACE_0_1, PPB_VarArray_Dev_0_1)
 PROXIED_IFACE(NoAPIName, PPB_VAR_DICTIONARY_DEV_INTERFACE_0_1,
               PPB_VarDictionary_Dev_0_1)
diff --git a/ppapi/thunk/ppb_udp_socket_api.h b/ppapi/thunk/ppb_udp_socket_api.h
new file mode 100644
index 0000000..1368e9c
--- /dev/null
+++ b/ppapi/thunk/ppb_udp_socket_api.h
@@ -0,0 +1,42 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef PPAPI_THUNK_PPB_UDP_SOCKET_API_H_
+#define PPAPI_THUNK_PPB_UDP_SOCKET_API_H_
+
+#include "base/memory/ref_counted.h"
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
+#include "ppapi/thunk/ppapi_thunk_export.h"
+
+namespace ppapi {
+
+class TrackedCallback;
+
+namespace thunk {
+
+class PPAPI_THUNK_EXPORT PPB_UDPSocket_API {
+ public:
+  virtual ~PPB_UDPSocket_API() {}
+
+  virtual int32_t Bind(PP_Resource addr,
+                       scoped_refptr<TrackedCallback> callback) = 0;
+  virtual PP_Resource GetBoundAddress() = 0;
+  virtual int32_t RecvFrom(char* buffer,
+                           int32_t num_bytes,
+                           PP_Resource* addr,
+                           scoped_refptr<TrackedCallback> callback) = 0;
+  virtual int32_t SendTo(const char* buffer,
+                         int32_t num_bytes,
+                         PP_Resource addr,
+                         scoped_refptr<TrackedCallback> callback) = 0;
+  virtual void Close() = 0;
+  virtual int32_t SetOption(PP_UDPSocket_Option_Dev name,
+                            const PP_Var& value,
+                            scoped_refptr<TrackedCallback> callback) = 0;
+};
+
+}  // namespace thunk
+}  // namespace ppapi
+
+#endif  // PPAPI_THUNK_PPB_UDP_SOCKET_API_H_
diff --git a/ppapi/thunk/ppb_udp_socket_dev_thunk.cc b/ppapi/thunk/ppb_udp_socket_dev_thunk.cc
new file mode 100644
index 0000000..aa5165da
--- /dev/null
+++ b/ppapi/thunk/ppb_udp_socket_dev_thunk.cc
@@ -0,0 +1,123 @@
+// Copyright 2013 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// From dev/ppb_udp_socket_dev.idl modified Fri Jun 07 14:22:41 2013.
+
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
+#include "ppapi/c/pp_completion_callback.h"
+#include "ppapi/c/pp_errors.h"
+#include "ppapi/shared_impl/tracked_callback.h"
+#include "ppapi/thunk/enter.h"
+#include "ppapi/thunk/ppb_instance_api.h"
+#include "ppapi/thunk/ppb_udp_socket_api.h"
+#include "ppapi/thunk/resource_creation_api.h"
+#include "ppapi/thunk/thunk.h"
+
+namespace ppapi {
+namespace thunk {
+
+namespace {
+
+PP_Resource Create(PP_Instance instance) {
+  VLOG(4) << "PPB_UDPSocket_Dev::Create()";
+  EnterResourceCreation enter(instance);
+  if (enter.failed())
+    return 0;
+  return enter.functions()->CreateUDPSocket(instance);
+}
+
+PP_Bool IsUDPSocket(PP_Resource resource) {
+  VLOG(4) << "PPB_UDPSocket_Dev::IsUDPSocket()";
+  EnterResource<PPB_UDPSocket_API> enter(resource, false);
+  return PP_FromBool(enter.succeeded());
+}
+
+int32_t Bind(PP_Resource udp_socket,
+             PP_Resource addr,
+             struct PP_CompletionCallback callback) {
+  VLOG(4) << "PPB_UDPSocket_Dev::Bind()";
+  EnterResource<PPB_UDPSocket_API> enter(udp_socket, callback, true);
+  if (enter.failed())
+    return enter.retval();
+  return enter.SetResult(enter.object()->Bind(addr, enter.callback()));
+}
+
+PP_Resource GetBoundAddress(PP_Resource udp_socket) {
+  VLOG(4) << "PPB_UDPSocket_Dev::GetBoundAddress()";
+  EnterResource<PPB_UDPSocket_API> enter(udp_socket, true);
+  if (enter.failed())
+    return 0;
+  return enter.object()->GetBoundAddress();
+}
+
+int32_t RecvFrom(PP_Resource udp_socket,
+                 char* buffer,
+                 int32_t num_bytes,
+                 PP_Resource* addr,
+                 struct PP_CompletionCallback callback) {
+  VLOG(4) << "PPB_UDPSocket_Dev::RecvFrom()";
+  EnterResource<PPB_UDPSocket_API> enter(udp_socket, callback, true);
+  if (enter.failed())
+    return enter.retval();
+  return enter.SetResult(enter.object()->RecvFrom(buffer,
+                                                  num_bytes,
+                                                  addr,
+                                                  enter.callback()));
+}
+
+int32_t SendTo(PP_Resource udp_socket,
+               const char* buffer,
+               int32_t num_bytes,
+               PP_Resource addr,
+               struct PP_CompletionCallback callback) {
+  VLOG(4) << "PPB_UDPSocket_Dev::SendTo()";
+  EnterResource<PPB_UDPSocket_API> enter(udp_socket, callback, true);
+  if (enter.failed())
+    return enter.retval();
+  return enter.SetResult(enter.object()->SendTo(buffer,
+                                                num_bytes,
+                                                addr,
+                                                enter.callback()));
+}
+
+void Close(PP_Resource udp_socket) {
+  VLOG(4) << "PPB_UDPSocket_Dev::Close()";
+  EnterResource<PPB_UDPSocket_API> enter(udp_socket, true);
+  if (enter.failed())
+    return;
+  enter.object()->Close();
+}
+
+int32_t SetOption(PP_Resource udp_socket,
+                  PP_UDPSocket_Option_Dev name,
+                  struct PP_Var value,
+                  struct PP_CompletionCallback callback) {
+  VLOG(4) << "PPB_UDPSocket_Dev::SetOption()";
+  EnterResource<PPB_UDPSocket_API> enter(udp_socket, callback, true);
+  if (enter.failed())
+    return enter.retval();
+  return enter.SetResult(enter.object()->SetOption(name,
+                                                   value,
+                                                   enter.callback()));
+}
+
+const PPB_UDPSocket_Dev_0_1 g_ppb_udpsocket_dev_thunk_0_1 = {
+  &Create,
+  &IsUDPSocket,
+  &Bind,
+  &GetBoundAddress,
+  &RecvFrom,
+  &SendTo,
+  &Close,
+  &SetOption
+};
+
+}  // namespace
+
+const PPB_UDPSocket_Dev_0_1* GetPPB_UDPSocket_Dev_0_1_Thunk() {
+  return &g_ppb_udpsocket_dev_thunk_0_1;
+}
+
+}  // namespace thunk
+}  // namespace ppapi
diff --git a/ppapi/thunk/resource_creation_api.h b/ppapi/thunk/resource_creation_api.h
index a760030..70d21b9 100644
--- a/ppapi/thunk/resource_creation_api.h
+++ b/ppapi/thunk/resource_creation_api.h
@@ -153,6 +153,7 @@
   virtual PP_Resource CreateTCPServerSocketPrivate(PP_Instance instance) = 0;
   virtual PP_Resource CreateTCPSocket(PP_Instance instace) = 0;
   virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instace) = 0;
+  virtual PP_Resource CreateUDPSocket(PP_Instance instace) = 0;
   virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instace) = 0;
   virtual PP_Resource CreateWebSocket(PP_Instance instance) = 0;
   virtual PP_Resource CreateX509CertificatePrivate(PP_Instance instance) = 0;
diff --git a/webkit/common/plugins/ppapi/ppapi_utils.cc b/webkit/common/plugins/ppapi/ppapi_utils.cc
index 8d3d830..ad82d21 100644
--- a/webkit/common/plugins/ppapi/ppapi_utils.cc
+++ b/webkit/common/plugins/ppapi/ppapi_utils.cc
@@ -29,6 +29,7 @@
 #include "ppapi/c/dev/ppb_text_input_dev.h"
 #include "ppapi/c/dev/ppb_trace_event_dev.h"
 #include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
 #include "ppapi/c/dev/ppb_url_util_dev.h"
 #include "ppapi/c/dev/ppb_var_array_dev.h"
 #include "ppapi/c/dev/ppb_var_deprecated.h"
diff --git a/webkit/plugins/ppapi/plugin_module.cc b/webkit/plugins/ppapi/plugin_module.cc
index 228f222..efae9b9 100644
--- a/webkit/plugins/ppapi/plugin_module.cc
+++ b/webkit/plugins/ppapi/plugin_module.cc
@@ -35,6 +35,7 @@
 #include "ppapi/c/dev/ppb_text_input_dev.h"
 #include "ppapi/c/dev/ppb_trace_event_dev.h"
 #include "ppapi/c/dev/ppb_truetype_font_dev.h"
+#include "ppapi/c/dev/ppb_udp_socket_dev.h"
 #include "ppapi/c/dev/ppb_url_util_dev.h"
 #include "ppapi/c/dev/ppb_var_array_dev.h"
 #include "ppapi/c/dev/ppb_var_deprecated.h"
diff --git a/webkit/plugins/ppapi/resource_creation_impl.cc b/webkit/plugins/ppapi/resource_creation_impl.cc
index 640701f..4c80cb0 100644
--- a/webkit/plugins/ppapi/resource_creation_impl.cc
+++ b/webkit/plugins/ppapi/resource_creation_impl.cc
@@ -252,6 +252,10 @@
   return PPB_TCPSocket_Private_Impl::CreateResource(instance);
 }
 
+PP_Resource ResourceCreationImpl::CreateUDPSocket(PP_Instance instance) {
+  return 0;  // Not supported in-process.
+}
+
 PP_Resource ResourceCreationImpl::CreateUDPSocketPrivate(PP_Instance instance) {
   return 0;  // Not supported in-process.
 }
diff --git a/webkit/plugins/ppapi/resource_creation_impl.h b/webkit/plugins/ppapi/resource_creation_impl.h
index fdf3b4d..53196ffa 100644
--- a/webkit/plugins/ppapi/resource_creation_impl.h
+++ b/webkit/plugins/ppapi/resource_creation_impl.h
@@ -119,6 +119,7 @@
       PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateTCPSocket(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateTCPSocketPrivate(PP_Instance instance) OVERRIDE;
+  virtual PP_Resource CreateUDPSocket(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateUDPSocketPrivate(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateVideoCapture(PP_Instance instance) OVERRIDE;
   virtual PP_Resource CreateVideoDecoder(