diff --git a/.gitignore b/.gitignore
index 4eab18e..64b8d9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@ build/
 .deps/
 /Makefile
 src/Makefile
+server/Makefile
 libev/Makefile
 libudns/Makefile
 libcork/Makefile
diff --git a/Makefile.am b/Makefile.am
index 690af43..75e158e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1,7 +1,7 @@
 if USE_SYSTEM_SHARED_LIB
-SUBDIRS = libcork libipset src
+SUBDIRS = libcork libipset src server
 else
-SUBDIRS = libsodium libcork libipset libudns libev src
+SUBDIRS = libsodium libcork libipset libudns libev src server
 endif
 
 if ENABLE_DOCUMENTATION
diff --git a/Makefile.in b/Makefile.in
index 4cb3deb..e210bc0 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -195,7 +195,7 @@ am__define_uniq_tagged_files = \
 ETAGS = etags
 CTAGS = ctags
 CSCOPE = cscope
-DIST_SUBDIRS = libsodium libcork libipset libudns libev src doc
+DIST_SUBDIRS = libsodium libcork libipset libudns libev src server doc
 am__DIST_COMMON = $(srcdir)/Makefile.in $(srcdir)/config.h.in \
 	$(srcdir)/shadowsocks-libev.pc.in $(top_srcdir)/auto/ar-lib \
 	$(top_srcdir)/auto/compile $(top_srcdir)/auto/config.guess \
@@ -377,8 +377,9 @@ top_build_prefix = @top_build_prefix@
 top_builddir = @top_builddir@
 top_srcdir = @top_srcdir@
 @USE_SYSTEM_SHARED_LIB_FALSE@SUBDIRS = libsodium libcork libipset \
-@USE_SYSTEM_SHARED_LIB_FALSE@	libudns libev src $(am__append_1)
-@USE_SYSTEM_SHARED_LIB_TRUE@SUBDIRS = libcork libipset src \
+@USE_SYSTEM_SHARED_LIB_FALSE@	libudns libev src server \
+@USE_SYSTEM_SHARED_LIB_FALSE@	$(am__append_1)
+@USE_SYSTEM_SHARED_LIB_TRUE@SUBDIRS = libcork libipset src server \
 @USE_SYSTEM_SHARED_LIB_TRUE@	$(am__append_1)
 ACLOCAL_AMFLAGS = -I m4
 pkgconfiglibdir = $(libdir)/pkgconfig
diff --git a/configure b/configure
index 7d854c4..01d66ab 100755
--- a/configure
+++ b/configure
@@ -649,7 +649,6 @@ PTHREAD_CC
 ax_pthread_config
 INET_NTOP_LIB
 MV
-RM
 GZIP
 XMLTO
 ASCIIDOC
@@ -757,6 +756,7 @@ infodir
 docdir
 oldincludedir
 includedir
+runstatedir
 localstatedir
 sharedstatedir
 sysconfdir
@@ -857,6 +857,7 @@ datadir='${datarootdir}'
 sysconfdir='${prefix}/etc'
 sharedstatedir='${prefix}/com'
 localstatedir='${prefix}/var'
+runstatedir='${localstatedir}/run'
 includedir='${prefix}/include'
 oldincludedir='/usr/include'
 docdir='${datarootdir}/doc/${PACKAGE_TARNAME}'
@@ -1109,6 +1110,15 @@ do
   | -silent | --silent | --silen | --sile | --sil)
     silent=yes ;;
 
+  -runstatedir | --runstatedir | --runstatedi | --runstated \
+  | --runstate | --runstat | --runsta | --runst | --runs \
+  | --run | --ru | --r)
+    ac_prev=runstatedir ;;
+  -runstatedir=* | --runstatedir=* | --runstatedi=* | --runstated=* \
+  | --runstate=* | --runstat=* | --runsta=* | --runst=* | --runs=* \
+  | --run=* | --ru=* | --r=*)
+    runstatedir=$ac_optarg ;;
+
   -sbindir | --sbindir | --sbindi | --sbind | --sbin | --sbi | --sb)
     ac_prev=sbindir ;;
   -sbindir=* | --sbindir=* | --sbindi=* | --sbind=* | --sbin=* \
@@ -1246,7 +1256,7 @@ fi
 for ac_var in	exec_prefix prefix bindir sbindir libexecdir datarootdir \
 		datadir sysconfdir sharedstatedir localstatedir includedir \
 		oldincludedir docdir infodir htmldir dvidir pdfdir psdir \
-		libdir localedir mandir
+		libdir localedir mandir runstatedir
 do
   eval ac_val=\$$ac_var
   # Remove trailing slashes.
@@ -1399,6 +1409,7 @@ Fine tuning of the installation directories:
   --sysconfdir=DIR        read-only single-machine data [PREFIX/etc]
   --sharedstatedir=DIR    modifiable architecture-independent data [PREFIX/com]
   --localstatedir=DIR     modifiable single-machine data [PREFIX/var]
+  --runstatedir=DIR       modifiable per-process data [LOCALSTATEDIR/run]
   --libdir=DIR            object code libraries [EPREFIX/lib]
   --includedir=DIR        C header files [PREFIX/include]
   --oldincludedir=DIR     C header files for non-gcc [/usr/include]
@@ -2472,8 +2483,8 @@ ac_configure="$SHELL $ac_aux_dir/configure"  # Please don't use this var.
 
 
 
-# expand $ac_aux_dir to an absolute path
-am_aux_dir=`cd $ac_aux_dir && pwd`
+# Expand $ac_aux_dir to an absolute path.
+am_aux_dir=`cd "$ac_aux_dir" && pwd`
 
 ac_ext=c
 ac_cpp='$CPP $CPPFLAGS'
@@ -3783,7 +3794,7 @@ $as_echo "$ac_cv_safe_to_define___extensions__" >&6; }
 
 
 
-am__api_version='1.14'
+am__api_version='1.15'
 
 # Find a good install program.  We prefer a C program (faster),
 # so one script is as good as another.  But avoid the broken or
@@ -3972,7 +3983,7 @@ else
 $as_echo "$as_me: WARNING: 'missing' script is too old or missing" >&2;}
 fi
 
-if test x"${install_sh}" != xset; then
+if test x"${install_sh+set}" != xset; then
   case $am_aux_dir in
   *\ * | *\	*)
     install_sh="\${SHELL} '$am_aux_dir/install-sh'" ;;
@@ -4363,8 +4374,8 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"}
 # <http://lists.gnu.org/archive/html/automake/2012-07/msg00014.html>
 mkdir_p='$(MKDIR_P)'
 
-# We need awk for the "check" target.  The system "awk" is bad on
-# some platforms.
+# We need awk for the "check" target (and possibly the TAP driver).  The
+# system "awk" is bad on some platforms.
 # Always define AMTAR for backward compatibility.  Yes, it's still used
 # in the wild :-(  We should find a proper way to deprecate it ...
 AMTAR='$${TAR-tar}'
@@ -4549,6 +4560,7 @@ END
     as_fn_error $? "Your 'rm' program is bad, sorry." "$LINENO" 5
   fi
 fi
+
 if test -n "$ac_tool_prefix"; then
   for ac_prog in ar lib "link -lib"
   do
@@ -12494,47 +12506,6 @@ $as_echo "no" >&6; }
 fi
 
 
-  # Extract the first word of "rm", so it can be a program name with args.
-set dummy rm; ac_word=$2
-{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
-$as_echo_n "checking for $ac_word... " >&6; }
-if ${ac_cv_path_RM+:} false; then :
-  $as_echo_n "(cached) " >&6
-else
-  case $RM in
-  [\\/]* | ?:[\\/]*)
-  ac_cv_path_RM="$RM" # Let the user override the test with a path.
-  ;;
-  *)
-  as_save_IFS=$IFS; IFS=$PATH_SEPARATOR
-for as_dir in $PATH
-do
-  IFS=$as_save_IFS
-  test -z "$as_dir" && as_dir=.
-    for ac_exec_ext in '' $ac_executable_extensions; do
-  if as_fn_executable_p "$as_dir/$ac_word$ac_exec_ext"; then
-    ac_cv_path_RM="$as_dir/$ac_word$ac_exec_ext"
-    $as_echo "$as_me:${as_lineno-$LINENO}: found $as_dir/$ac_word$ac_exec_ext" >&5
-    break 2
-  fi
-done
-  done
-IFS=$as_save_IFS
-
-  test -z "$ac_cv_path_RM" && ac_cv_path_RM="rm"
-  ;;
-esac
-fi
-RM=$ac_cv_path_RM
-if test -n "$RM"; then
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: $RM" >&5
-$as_echo "$RM" >&6; }
-else
-  { $as_echo "$as_me:${as_lineno-$LINENO}: result: no" >&5
-$as_echo "no" >&6; }
-fi
-
-
   # Extract the first word of "mv", so it can be a program name with args.
 set dummy mv; ac_word=$2
 { $as_echo "$as_me:${as_lineno-$LINENO}: checking for $ac_word" >&5
@@ -16204,15 +16175,162 @@ $as_echo "#define HAVE_IPv6 1" >>confdefs.h
 
 
 if test -z "$USE_SYSTEM_SHARED_LIB_TRUE"; then :
-  else
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for sodium_init in -lsodium" >&5
+$as_echo_n "checking for sodium_init in -lsodium... " >&6; }
+if ${ac_cv_lib_sodium_sodium_init+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lsodium  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char sodium_init ();
+int
+main ()
+{
+return sodium_init ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_sodium_sodium_init=yes
+else
+  ac_cv_lib_sodium_sodium_init=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_sodium_sodium_init" >&5
+$as_echo "$ac_cv_lib_sodium_sodium_init" >&6; }
+if test "x$ac_cv_lib_sodium_sodium_init" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBSODIUM 1
+_ACEOF
+
+  LIBS="-lsodium $LIBS"
+
+else
+
+       as_fn_error $? "Couldn't find libsodium. Try installing libsodium-dev[el]." "$LINENO" 5
+
+fi
+
+
+else
   subdirs="$subdirs libsodium"
 
 fi
 
-ac_config_files="$ac_config_files shadowsocks-libev.pc Makefile libcork/Makefile libipset/Makefile src/Makefile"
+ac_config_files="$ac_config_files shadowsocks-libev.pc Makefile libcork/Makefile libipset/Makefile src/Makefile server/Makefile"
 
 if test -z "$USE_SYSTEM_SHARED_LIB_TRUE"; then :
-  else
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for dns_dnlen in -ludns" >&5
+$as_echo_n "checking for dns_dnlen in -ludns... " >&6; }
+if ${ac_cv_lib_udns_dns_dnlen+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-ludns  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char dns_dnlen ();
+int
+main ()
+{
+return dns_dnlen ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_udns_dns_dnlen=yes
+else
+  ac_cv_lib_udns_dns_dnlen=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_udns_dns_dnlen" >&5
+$as_echo "$ac_cv_lib_udns_dns_dnlen" >&6; }
+if test "x$ac_cv_lib_udns_dns_dnlen" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBUDNS 1
+_ACEOF
+
+  LIBS="-ludns $LIBS"
+
+else
+  as_fn_error $? "Couldn't find libudns. Try installing libudns-dev or udns-devel." "$LINENO" 5
+fi
+
+    { $as_echo "$as_me:${as_lineno-$LINENO}: checking for ev_loop_destroy in -lev" >&5
+$as_echo_n "checking for ev_loop_destroy in -lev... " >&6; }
+if ${ac_cv_lib_ev_ev_loop_destroy+:} false; then :
+  $as_echo_n "(cached) " >&6
+else
+  ac_check_lib_save_LIBS=$LIBS
+LIBS="-lev  $LIBS"
+cat confdefs.h - <<_ACEOF >conftest.$ac_ext
+/* end confdefs.h.  */
+
+/* Override any GCC internal prototype to avoid an error.
+   Use char because int might match the return type of a GCC
+   builtin and then its argument prototype would still apply.  */
+#ifdef __cplusplus
+extern "C"
+#endif
+char ev_loop_destroy ();
+int
+main ()
+{
+return ev_loop_destroy ();
+  ;
+  return 0;
+}
+_ACEOF
+if ac_fn_c_try_link "$LINENO"; then :
+  ac_cv_lib_ev_ev_loop_destroy=yes
+else
+  ac_cv_lib_ev_ev_loop_destroy=no
+fi
+rm -f core conftest.err conftest.$ac_objext \
+    conftest$ac_exeext conftest.$ac_ext
+LIBS=$ac_check_lib_save_LIBS
+fi
+{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_cv_lib_ev_ev_loop_destroy" >&5
+$as_echo "$ac_cv_lib_ev_ev_loop_destroy" >&6; }
+if test "x$ac_cv_lib_ev_ev_loop_destroy" = xyes; then :
+  cat >>confdefs.h <<_ACEOF
+#define HAVE_LIBEV 1
+_ACEOF
+
+  LIBS="-lev $LIBS"
+
+else
+  as_fn_error $? "Couldn't find libev. Try installing libev-dev[el]." "$LINENO" 5
+fi
+
+
+else
   ac_config_files="$ac_config_files libudns/Makefile libev/Makefile"
 
 fi
@@ -17258,6 +17376,7 @@ do
     "libcork/Makefile") CONFIG_FILES="$CONFIG_FILES libcork/Makefile" ;;
     "libipset/Makefile") CONFIG_FILES="$CONFIG_FILES libipset/Makefile" ;;
     "src/Makefile") CONFIG_FILES="$CONFIG_FILES src/Makefile" ;;
+    "server/Makefile") CONFIG_FILES="$CONFIG_FILES server/Makefile" ;;
     "libudns/Makefile") CONFIG_FILES="$CONFIG_FILES libudns/Makefile" ;;
     "libev/Makefile") CONFIG_FILES="$CONFIG_FILES libev/Makefile" ;;
     "doc/Makefile") CONFIG_FILES="$CONFIG_FILES doc/Makefile" ;;
@@ -17958,8 +18077,8 @@ $as_echo X"$file" |
     fi
 
     cfgfile="${ofile}T"
-    trap "$RM -f \"$cfgfile\"; exit 1" 1 2 15
-    $RM -f "$cfgfile"
+    trap "$RM \"$cfgfile\"; exit 1" 1 2 15
+    $RM "$cfgfile"
 
     cat <<_LT_EOF >> "$cfgfile"
 #! $SHELL
diff --git a/configure.ac b/configure.ac
index 6586f2b..f9c51ab 100755
--- a/configure.ac
+++ b/configure.ac
@@ -315,7 +315,8 @@ AC_CONFIG_FILES([ shadowsocks-libev.pc
                  Makefile
                  libcork/Makefile
                  libipset/Makefile
-                 src/Makefile])
+                 src/Makefile
+		 server/Makefile])
 AM_COND_IF([USE_SYSTEM_SHARED_LIB],[
     AC_CHECK_LIB([udns], [dns_dnlen], ,[AC_MSG_ERROR([Couldn't find libudns. Try installing libudns-dev or udns-devel.])])
     AC_CHECK_LIB([ev], [ev_loop_destroy], ,[AC_MSG_ERROR([Couldn't find libev. Try installing libev-dev@<:@el@:>@.])])
diff --git a/server/Makefile.am b/server/Makefile.am
new file mode 100644
index 0000000..3ae8bc2
--- /dev/null
+++ b/server/Makefile.am
@@ -0,0 +1,55 @@
+VERSION_INFO = 2:0:0
+
+AM_CFLAGS = -g -O2 -Wall -Werror -Wno-deprecated-declarations -fno-strict-aliasing -std=gnu99 -D_GNU_SOURCE
+AM_CFLAGS += $(PTHREAD_CFLAGS)
+if !USE_SYSTEM_SHARED_LIB
+AM_CFLAGS += -I$(top_srcdir)/libev
+AM_CFLAGS += -I$(top_srcdir)/libudns
+AM_CFLAGS += -I$(top_srcdir)/libsodium/src/libsodium/include
+endif
+AM_CFLAGS += -I$(top_srcdir)/libipset/include
+AM_CFLAGS += -I$(top_srcdir)/libcork/include
+AM_CFLAGS += $(LIBPCRE_CFLAGS)
+
+SS_COMMON_LIBS = $(top_builddir)/libipset/libipset.la \
+                 $(top_builddir)/libcork/libcork.la \
+                 $(INET_NTOP_LIB) $(LIBPCRE_LIBS)
+if USE_SYSTEM_SHARED_LIB
+SS_COMMON_LIBS += -lev -lsodium -lm
+else
+SS_COMMON_LIBS += $(top_builddir)/libev/libev.la \
+                  $(top_builddir)/libsodium/src/libsodium/libsodium.la
+endif
+
+bin_PROGRAMS = ss-server ss-check
+
+sni_src = http.c \
+          tls.c \
+          rule.c
+
+ss_check_SOURCES = check.c 
+
+ss_server_SOURCES = utils.c \
+                    netutils.c \
+                    jconf.c \
+                    json.c \
+                    encrypt.c \
+                    udprelay.c \
+                    cache.c \
+                    acl.c \
+                    resolv.c \
+                    server.c \
+                    $(sni_src)
+
+
+ss_check_LDADD = $(SS_COMMON_LIBS)
+ss_server_LDADD = $(SS_COMMON_LIBS)
+
+if USE_SYSTEM_SHARED_LIB
+ss_server_LDADD += -ludns
+else
+ss_server_LDADD += $(top_builddir)/libudns/libudns.la
+endif
+
+ss_check_CFLAGS = $(AM_CFLAGS) 
+ss_server_CFLAGS = $(AM_CFLAGS) -DMODULE_REMOTE
diff --git a/server/Makefile.in b/server/Makefile.in
new file mode 100644
index 0000000..3bfa53e
--- /dev/null
+++ b/server/Makefile.in
@@ -0,0 +1,919 @@
+# Makefile.in generated by automake 1.15 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2014 Free Software Foundation, Inc.
+
+# This Makefile.in is free software; the Free Software Foundation
+# gives unlimited permission to copy and/or distribute it,
+# with or without modifications, as long as this notice is preserved.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY, to the extent permitted by law; without
+# even the implied warranty of MERCHANTABILITY or FITNESS FOR A
+# PARTICULAR PURPOSE.
+
+@SET_MAKE@
+
+VPATH = @srcdir@
+am__is_gnu_make = { \
+  if test -z '$(MAKELEVEL)'; then \
+    false; \
+  elif test -n '$(MAKE_HOST)'; then \
+    true; \
+  elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \
+    true; \
+  else \
+    false; \
+  fi; \
+}
+am__make_running_with_option = \
+  case $${target_option-} in \
+      ?) ;; \
+      *) echo "am__make_running_with_option: internal error: invalid" \
+              "target option '$${target_option-}' specified" >&2; \
+         exit 1;; \
+  esac; \
+  has_opt=no; \
+  sane_makeflags=$$MAKEFLAGS; \
+  if $(am__is_gnu_make); then \
+    sane_makeflags=$$MFLAGS; \
+  else \
+    case $$MAKEFLAGS in \
+      *\\[\ \	]*) \
+        bs=\\; \
+        sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \
+          | sed "s/$$bs$$bs[$$bs $$bs	]*//g"`;; \
+    esac; \
+  fi; \
+  skip_next=no; \
+  strip_trailopt () \
+  { \
+    flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \
+  }; \
+  for flg in $$sane_makeflags; do \
+    test $$skip_next = yes && { skip_next=no; continue; }; \
+    case $$flg in \
+      *=*|--*) continue;; \
+        -*I) strip_trailopt 'I'; skip_next=yes;; \
+      -*I?*) strip_trailopt 'I';; \
+        -*O) strip_trailopt 'O'; skip_next=yes;; \
+      -*O?*) strip_trailopt 'O';; \
+        -*l) strip_trailopt 'l'; skip_next=yes;; \
+      -*l?*) strip_trailopt 'l';; \
+      -[dEDm]) skip_next=yes;; \
+      -[JT]) skip_next=yes;; \
+    esac; \
+    case $$flg in \
+      *$$target_option*) has_opt=yes; break;; \
+    esac; \
+  done; \
+  test $$has_opt = yes
+am__make_dryrun = (target_option=n; $(am__make_running_with_option))
+am__make_keepgoing = (target_option=k; $(am__make_running_with_option))
+pkgdatadir = $(datadir)/@PACKAGE@
+pkgincludedir = $(includedir)/@PACKAGE@
+pkglibdir = $(libdir)/@PACKAGE@
+pkglibexecdir = $(libexecdir)/@PACKAGE@
+am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd
+install_sh_DATA = $(install_sh) -c -m 644
+install_sh_PROGRAM = $(install_sh) -c
+install_sh_SCRIPT = $(install_sh) -c
+INSTALL_HEADER = $(INSTALL_DATA)
+transform = $(program_transform_name)
+NORMAL_INSTALL = :
+PRE_INSTALL = :
+POST_INSTALL = :
+NORMAL_UNINSTALL = :
+PRE_UNINSTALL = :
+POST_UNINSTALL = :
+build_triplet = @build@
+host_triplet = @host@
+@USE_SYSTEM_SHARED_LIB_FALSE@am__append_1 = -I$(top_srcdir)/libev \
+@USE_SYSTEM_SHARED_LIB_FALSE@	-I$(top_srcdir)/libudns \
+@USE_SYSTEM_SHARED_LIB_FALSE@	-I$(top_srcdir)/libsodium/src/libsodium/include
+@USE_SYSTEM_SHARED_LIB_TRUE@am__append_2 = -lev -lsodium -lm
+@USE_SYSTEM_SHARED_LIB_FALSE@am__append_3 = $(top_builddir)/libev/libev.la \
+@USE_SYSTEM_SHARED_LIB_FALSE@                  $(top_builddir)/libsodium/src/libsodium/libsodium.la
+
+bin_PROGRAMS = ss-server$(EXEEXT) ss-check$(EXEEXT)
+@USE_SYSTEM_SHARED_LIB_TRUE@am__append_4 = -ludns
+@USE_SYSTEM_SHARED_LIB_FALSE@am__append_5 = $(top_builddir)/libudns/libudns.la
+subdir = server
+ACLOCAL_M4 = $(top_srcdir)/aclocal.m4
+am__aclocal_m4_deps = $(top_srcdir)/m4/ax_pthread.m4 \
+	$(top_srcdir)/m4/ax_tls.m4 $(top_srcdir)/m4/inet_ntop.m4 \
+	$(top_srcdir)/m4/libtool.m4 $(top_srcdir)/m4/ltoptions.m4 \
+	$(top_srcdir)/m4/ltsugar.m4 $(top_srcdir)/m4/ltversion.m4 \
+	$(top_srcdir)/m4/lt~obsolete.m4 $(top_srcdir)/m4/mbedtls.m4 \
+	$(top_srcdir)/m4/openssl.m4 $(top_srcdir)/m4/pcre.m4 \
+	$(top_srcdir)/m4/polarssl.m4 \
+	$(top_srcdir)/m4/stack-protector.m4 $(top_srcdir)/m4/zlib.m4 \
+	$(top_srcdir)/libev/libev.m4 $(top_srcdir)/configure.ac
+am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \
+	$(ACLOCAL_M4)
+DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON)
+mkinstalldirs = $(install_sh) -d
+CONFIG_HEADER = $(top_builddir)/config.h
+CONFIG_CLEAN_FILES =
+CONFIG_CLEAN_VPATH_FILES =
+am__installdirs = "$(DESTDIR)$(bindir)"
+PROGRAMS = $(bin_PROGRAMS)
+am_ss_check_OBJECTS = ss_check-check.$(OBJEXT)
+ss_check_OBJECTS = $(am_ss_check_OBJECTS)
+am__DEPENDENCIES_1 =
+am__DEPENDENCIES_2 = $(top_builddir)/libipset/libipset.la \
+	$(top_builddir)/libcork/libcork.la $(am__DEPENDENCIES_1) \
+	$(am__DEPENDENCIES_1) $(am__append_3)
+ss_check_DEPENDENCIES = $(am__DEPENDENCIES_2)
+AM_V_lt = $(am__v_lt_@AM_V@)
+am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@)
+am__v_lt_0 = --silent
+am__v_lt_1 = 
+ss_check_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(ss_check_CFLAGS) \
+	$(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+am__objects_1 = ss_server-http.$(OBJEXT) ss_server-tls.$(OBJEXT) \
+	ss_server-rule.$(OBJEXT)
+am_ss_server_OBJECTS = ss_server-utils.$(OBJEXT) \
+	ss_server-netutils.$(OBJEXT) ss_server-jconf.$(OBJEXT) \
+	ss_server-json.$(OBJEXT) ss_server-encrypt.$(OBJEXT) \
+	ss_server-udprelay.$(OBJEXT) ss_server-cache.$(OBJEXT) \
+	ss_server-acl.$(OBJEXT) ss_server-resolv.$(OBJEXT) \
+	ss_server-server.$(OBJEXT) $(am__objects_1)
+ss_server_OBJECTS = $(am_ss_server_OBJECTS)
+ss_server_DEPENDENCIES = $(am__DEPENDENCIES_2) $(am__DEPENDENCIES_1) \
+	$(am__append_5)
+ss_server_LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(ss_server_CFLAGS) \
+	$(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_P = $(am__v_P_@AM_V@)
+am__v_P_ = $(am__v_P_@AM_DEFAULT_V@)
+am__v_P_0 = false
+am__v_P_1 = :
+AM_V_GEN = $(am__v_GEN_@AM_V@)
+am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@)
+am__v_GEN_0 = @echo "  GEN     " $@;
+am__v_GEN_1 = 
+AM_V_at = $(am__v_at_@AM_V@)
+am__v_at_ = $(am__v_at_@AM_DEFAULT_V@)
+am__v_at_0 = @
+am__v_at_1 = 
+DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir)
+depcomp = $(SHELL) $(top_srcdir)/auto/depcomp
+am__depfiles_maybe = depfiles
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+	$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \
+	$(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \
+	$(AM_CFLAGS) $(CFLAGS)
+AM_V_CC = $(am__v_CC_@AM_V@)
+am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@)
+am__v_CC_0 = @echo "  CC      " $@;
+am__v_CC_1 = 
+CCLD = $(CC)
+LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \
+	$(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \
+	$(AM_LDFLAGS) $(LDFLAGS) -o $@
+AM_V_CCLD = $(am__v_CCLD_@AM_V@)
+am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@)
+am__v_CCLD_0 = @echo "  CCLD    " $@;
+am__v_CCLD_1 = 
+SOURCES = $(ss_check_SOURCES) $(ss_server_SOURCES)
+DIST_SOURCES = $(ss_check_SOURCES) $(ss_server_SOURCES)
+am__can_run_installinfo = \
+  case $$AM_UPDATE_INFO_DIR in \
+    n|no|NO) false;; \
+    *) (install-info --version) >/dev/null 2>&1;; \
+  esac
+am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP)
+# Read a list of newline-separated strings from the standard input,
+# and print each of them once, without duplicates.  Input order is
+# *not* preserved.
+am__uniquify_input = $(AWK) '\
+  BEGIN { nonempty = 0; } \
+  { items[$$0] = 1; nonempty = 1; } \
+  END { if (nonempty) { for (i in items) print i; }; } \
+'
+# Make sure the list of sources is unique.  This is necessary because,
+# e.g., the same source file might be shared among _SOURCES variables
+# for different programs/libraries.
+am__define_uniq_tagged_files = \
+  list='$(am__tagged_files)'; \
+  unique=`for i in $$list; do \
+    if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \
+  done | $(am__uniquify_input)`
+ETAGS = etags
+CTAGS = ctags
+am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/auto/depcomp
+DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST)
+ACLOCAL = @ACLOCAL@
+AMTAR = @AMTAR@
+AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@
+AR = @AR@
+ASCIIDOC = @ASCIIDOC@
+AUTOCONF = @AUTOCONF@
+AUTOHEADER = @AUTOHEADER@
+AUTOMAKE = @AUTOMAKE@
+AWK = @AWK@
+CC = @CC@
+CCDEPMODE = @CCDEPMODE@
+CFLAGS = @CFLAGS@
+CPP = @CPP@
+CPPFLAGS = @CPPFLAGS@
+CYGPATH_W = @CYGPATH_W@
+DEFS = @DEFS@
+DEPDIR = @DEPDIR@
+DLLTOOL = @DLLTOOL@
+DSYMUTIL = @DSYMUTIL@
+DUMPBIN = @DUMPBIN@
+ECHO_C = @ECHO_C@
+ECHO_N = @ECHO_N@
+ECHO_T = @ECHO_T@
+EGREP = @EGREP@
+EXEEXT = @EXEEXT@
+FGREP = @FGREP@
+GREP = @GREP@
+GZIP = @GZIP@
+INET_NTOP_LIB = @INET_NTOP_LIB@
+INSTALL = @INSTALL@
+INSTALL_DATA = @INSTALL_DATA@
+INSTALL_PROGRAM = @INSTALL_PROGRAM@
+INSTALL_SCRIPT = @INSTALL_SCRIPT@
+INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@
+LD = @LD@
+LDFLAGS = @LDFLAGS@
+LIBOBJS = @LIBOBJS@
+LIBPCRE = @LIBPCRE@
+LIBS = @LIBS@
+LIBTOOL = @LIBTOOL@
+LIPO = @LIPO@
+LN_S = @LN_S@
+LTLIBOBJS = @LTLIBOBJS@
+MAINT = @MAINT@
+MAKEINFO = @MAKEINFO@
+MANIFEST_TOOL = @MANIFEST_TOOL@
+MKDIR_P = @MKDIR_P@
+MV = @MV@
+NM = @NM@
+NMEDIT = @NMEDIT@
+OBJDUMP = @OBJDUMP@
+OBJEXT = @OBJEXT@
+OTOOL = @OTOOL@
+OTOOL64 = @OTOOL64@
+PACKAGE = @PACKAGE@
+PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@
+PACKAGE_NAME = @PACKAGE_NAME@
+PACKAGE_STRING = @PACKAGE_STRING@
+PACKAGE_TARNAME = @PACKAGE_TARNAME@
+PACKAGE_URL = @PACKAGE_URL@
+PACKAGE_VERSION = @PACKAGE_VERSION@
+PATH_SEPARATOR = @PATH_SEPARATOR@
+PCRE_CONFIG = @PCRE_CONFIG@
+PTHREAD_CC = @PTHREAD_CC@
+PTHREAD_CFLAGS = @PTHREAD_CFLAGS@
+PTHREAD_LIBS = @PTHREAD_LIBS@
+RANLIB = @RANLIB@
+SED = @SED@
+SET_MAKE = @SET_MAKE@
+SHELL = @SHELL@
+STRIP = @STRIP@
+VERSION = @VERSION@
+XMLTO = @XMLTO@
+abs_builddir = @abs_builddir@
+abs_srcdir = @abs_srcdir@
+abs_top_builddir = @abs_top_builddir@
+abs_top_srcdir = @abs_top_srcdir@
+ac_ct_AR = @ac_ct_AR@
+ac_ct_CC = @ac_ct_CC@
+ac_ct_DUMPBIN = @ac_ct_DUMPBIN@
+am__include = @am__include@
+am__leading_dot = @am__leading_dot@
+am__quote = @am__quote@
+am__tar = @am__tar@
+am__untar = @am__untar@
+ax_pthread_config = @ax_pthread_config@
+bindir = @bindir@
+build = @build@
+build_alias = @build_alias@
+build_cpu = @build_cpu@
+build_os = @build_os@
+build_vendor = @build_vendor@
+builddir = @builddir@
+datadir = @datadir@
+datarootdir = @datarootdir@
+docdir = @docdir@
+dvidir = @dvidir@
+exec_prefix = @exec_prefix@
+host = @host@
+host_alias = @host_alias@
+host_cpu = @host_cpu@
+host_os = @host_os@
+host_vendor = @host_vendor@
+htmldir = @htmldir@
+includedir = @includedir@
+infodir = @infodir@
+install_sh = @install_sh@
+libdir = @libdir@
+libexecdir = @libexecdir@
+localedir = @localedir@
+localstatedir = @localstatedir@
+mandir = @mandir@
+mkdir_p = @mkdir_p@
+oldincludedir = @oldincludedir@
+pcre_pcreh = @pcre_pcreh@
+pcreh = @pcreh@
+pdfdir = @pdfdir@
+prefix = @prefix@
+program_transform_name = @program_transform_name@
+psdir = @psdir@
+runstatedir = @runstatedir@
+sbindir = @sbindir@
+sharedstatedir = @sharedstatedir@
+srcdir = @srcdir@
+subdirs = @subdirs@
+sysconfdir = @sysconfdir@
+target_alias = @target_alias@
+top_build_prefix = @top_build_prefix@
+top_builddir = @top_builddir@
+top_srcdir = @top_srcdir@
+VERSION_INFO = 2:0:0
+AM_CFLAGS = -g -O2 -Wall -Werror -Wno-deprecated-declarations \
+	-fno-strict-aliasing -std=gnu99 -D_GNU_SOURCE \
+	$(PTHREAD_CFLAGS) $(am__append_1) \
+	-I$(top_srcdir)/libipset/include \
+	-I$(top_srcdir)/libcork/include $(LIBPCRE_CFLAGS)
+SS_COMMON_LIBS = $(top_builddir)/libipset/libipset.la \
+	$(top_builddir)/libcork/libcork.la $(INET_NTOP_LIB) \
+	$(LIBPCRE_LIBS) $(am__append_2) $(am__append_3)
+sni_src = http.c \
+          tls.c \
+          rule.c
+
+ss_check_SOURCES = check.c 
+ss_server_SOURCES = utils.c \
+                    netutils.c \
+                    jconf.c \
+                    json.c \
+                    encrypt.c \
+                    udprelay.c \
+                    cache.c \
+                    acl.c \
+                    resolv.c \
+                    server.c \
+                    $(sni_src)
+
+ss_check_LDADD = $(SS_COMMON_LIBS)
+ss_server_LDADD = $(SS_COMMON_LIBS) $(am__append_4) $(am__append_5)
+ss_check_CFLAGS = $(AM_CFLAGS) 
+ss_server_CFLAGS = $(AM_CFLAGS) -DMODULE_REMOTE
+all: all-am
+
+.SUFFIXES:
+.SUFFIXES: .c .lo .o .obj
+$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am  $(am__configure_deps)
+	@for dep in $?; do \
+	  case '$(am__configure_deps)' in \
+	    *$$dep*) \
+	      ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \
+	        && { if test -f $@; then exit 0; else break; fi; }; \
+	      exit 1;; \
+	  esac; \
+	done; \
+	echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign server/Makefile'; \
+	$(am__cd) $(top_srcdir) && \
+	  $(AUTOMAKE) --foreign server/Makefile
+Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status
+	@case '$?' in \
+	  *config.status*) \
+	    cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \
+	  *) \
+	    echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \
+	    cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \
+	esac;
+
+$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+
+$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps)
+	cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh
+$(am__aclocal_m4_deps):
+install-binPROGRAMS: $(bin_PROGRAMS)
+	@$(NORMAL_INSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	if test -n "$$list"; then \
+	  echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \
+	  $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \
+	fi; \
+	for p in $$list; do echo "$$p $$p"; done | \
+	sed 's/$(EXEEXT)$$//' | \
+	while read p p1; do if test -f $$p \
+	 || test -f $$p1 \
+	  ; then echo "$$p"; echo "$$p"; else :; fi; \
+	done | \
+	sed -e 'p;s,.*/,,;n;h' \
+	    -e 's|.*|.|' \
+	    -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \
+	sed 'N;N;N;s,\n, ,g' | \
+	$(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \
+	  { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \
+	    if ($$2 == $$4) files[d] = files[d] " " $$1; \
+	    else { print "f", $$3 "/" $$4, $$1; } } \
+	  END { for (d in files) print "f", d, files[d] }' | \
+	while read type dir files; do \
+	    if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \
+	    test -z "$$files" || { \
+	    echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \
+	    $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \
+	    } \
+	; done
+
+uninstall-binPROGRAMS:
+	@$(NORMAL_UNINSTALL)
+	@list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \
+	files=`for p in $$list; do echo "$$p"; done | \
+	  sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \
+	      -e 's/$$/$(EXEEXT)/' \
+	`; \
+	test -n "$$list" || exit 0; \
+	echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \
+	cd "$(DESTDIR)$(bindir)" && rm -f $$files
+
+clean-binPROGRAMS:
+	@list='$(bin_PROGRAMS)'; test -n "$$list" || exit 0; \
+	echo " rm -f" $$list; \
+	rm -f $$list || exit $$?; \
+	test -n "$(EXEEXT)" || exit 0; \
+	list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \
+	echo " rm -f" $$list; \
+	rm -f $$list
+
+ss-check$(EXEEXT): $(ss_check_OBJECTS) $(ss_check_DEPENDENCIES) $(EXTRA_ss_check_DEPENDENCIES) 
+	@rm -f ss-check$(EXEEXT)
+	$(AM_V_CCLD)$(ss_check_LINK) $(ss_check_OBJECTS) $(ss_check_LDADD) $(LIBS)
+
+ss-server$(EXEEXT): $(ss_server_OBJECTS) $(ss_server_DEPENDENCIES) $(EXTRA_ss_server_DEPENDENCIES) 
+	@rm -f ss-server$(EXEEXT)
+	$(AM_V_CCLD)$(ss_server_LINK) $(ss_server_OBJECTS) $(ss_server_LDADD) $(LIBS)
+
+mostlyclean-compile:
+	-rm -f *.$(OBJEXT)
+
+distclean-compile:
+	-rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_check-check.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-acl.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-cache.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-encrypt.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-http.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-jconf.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-json.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-netutils.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-resolv.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-rule.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-server.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-tls.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-udprelay.Po@am__quote@
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/ss_server-utils.Po@am__quote@
+
+.c.o:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $<
+
+.c.obj:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\
+@am__fastdepCC_TRUE@	$(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'`
+
+.c.lo:
+@am__fastdepCC_TRUE@	$(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\
+@am__fastdepCC_TRUE@	$(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\
+@am__fastdepCC_TRUE@	$(am__mv) $$depbase.Tpo $$depbase.Plo
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $<
+
+ss_check-check.o: check.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_check_CFLAGS) $(CFLAGS) -MT ss_check-check.o -MD -MP -MF $(DEPDIR)/ss_check-check.Tpo -c -o ss_check-check.o `test -f 'check.c' || echo '$(srcdir)/'`check.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_check-check.Tpo $(DEPDIR)/ss_check-check.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='check.c' object='ss_check-check.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_check_CFLAGS) $(CFLAGS) -c -o ss_check-check.o `test -f 'check.c' || echo '$(srcdir)/'`check.c
+
+ss_check-check.obj: check.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_check_CFLAGS) $(CFLAGS) -MT ss_check-check.obj -MD -MP -MF $(DEPDIR)/ss_check-check.Tpo -c -o ss_check-check.obj `if test -f 'check.c'; then $(CYGPATH_W) 'check.c'; else $(CYGPATH_W) '$(srcdir)/check.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_check-check.Tpo $(DEPDIR)/ss_check-check.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='check.c' object='ss_check-check.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_check_CFLAGS) $(CFLAGS) -c -o ss_check-check.obj `if test -f 'check.c'; then $(CYGPATH_W) 'check.c'; else $(CYGPATH_W) '$(srcdir)/check.c'; fi`
+
+ss_server-utils.o: utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-utils.o -MD -MP -MF $(DEPDIR)/ss_server-utils.Tpo -c -o ss_server-utils.o `test -f 'utils.c' || echo '$(srcdir)/'`utils.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-utils.Tpo $(DEPDIR)/ss_server-utils.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='utils.c' object='ss_server-utils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-utils.o `test -f 'utils.c' || echo '$(srcdir)/'`utils.c
+
+ss_server-utils.obj: utils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-utils.obj -MD -MP -MF $(DEPDIR)/ss_server-utils.Tpo -c -o ss_server-utils.obj `if test -f 'utils.c'; then $(CYGPATH_W) 'utils.c'; else $(CYGPATH_W) '$(srcdir)/utils.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-utils.Tpo $(DEPDIR)/ss_server-utils.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='utils.c' object='ss_server-utils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-utils.obj `if test -f 'utils.c'; then $(CYGPATH_W) 'utils.c'; else $(CYGPATH_W) '$(srcdir)/utils.c'; fi`
+
+ss_server-netutils.o: netutils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-netutils.o -MD -MP -MF $(DEPDIR)/ss_server-netutils.Tpo -c -o ss_server-netutils.o `test -f 'netutils.c' || echo '$(srcdir)/'`netutils.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-netutils.Tpo $(DEPDIR)/ss_server-netutils.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='netutils.c' object='ss_server-netutils.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-netutils.o `test -f 'netutils.c' || echo '$(srcdir)/'`netutils.c
+
+ss_server-netutils.obj: netutils.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-netutils.obj -MD -MP -MF $(DEPDIR)/ss_server-netutils.Tpo -c -o ss_server-netutils.obj `if test -f 'netutils.c'; then $(CYGPATH_W) 'netutils.c'; else $(CYGPATH_W) '$(srcdir)/netutils.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-netutils.Tpo $(DEPDIR)/ss_server-netutils.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='netutils.c' object='ss_server-netutils.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-netutils.obj `if test -f 'netutils.c'; then $(CYGPATH_W) 'netutils.c'; else $(CYGPATH_W) '$(srcdir)/netutils.c'; fi`
+
+ss_server-jconf.o: jconf.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-jconf.o -MD -MP -MF $(DEPDIR)/ss_server-jconf.Tpo -c -o ss_server-jconf.o `test -f 'jconf.c' || echo '$(srcdir)/'`jconf.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-jconf.Tpo $(DEPDIR)/ss_server-jconf.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='jconf.c' object='ss_server-jconf.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-jconf.o `test -f 'jconf.c' || echo '$(srcdir)/'`jconf.c
+
+ss_server-jconf.obj: jconf.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-jconf.obj -MD -MP -MF $(DEPDIR)/ss_server-jconf.Tpo -c -o ss_server-jconf.obj `if test -f 'jconf.c'; then $(CYGPATH_W) 'jconf.c'; else $(CYGPATH_W) '$(srcdir)/jconf.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-jconf.Tpo $(DEPDIR)/ss_server-jconf.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='jconf.c' object='ss_server-jconf.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-jconf.obj `if test -f 'jconf.c'; then $(CYGPATH_W) 'jconf.c'; else $(CYGPATH_W) '$(srcdir)/jconf.c'; fi`
+
+ss_server-json.o: json.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-json.o -MD -MP -MF $(DEPDIR)/ss_server-json.Tpo -c -o ss_server-json.o `test -f 'json.c' || echo '$(srcdir)/'`json.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-json.Tpo $(DEPDIR)/ss_server-json.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='json.c' object='ss_server-json.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-json.o `test -f 'json.c' || echo '$(srcdir)/'`json.c
+
+ss_server-json.obj: json.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-json.obj -MD -MP -MF $(DEPDIR)/ss_server-json.Tpo -c -o ss_server-json.obj `if test -f 'json.c'; then $(CYGPATH_W) 'json.c'; else $(CYGPATH_W) '$(srcdir)/json.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-json.Tpo $(DEPDIR)/ss_server-json.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='json.c' object='ss_server-json.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-json.obj `if test -f 'json.c'; then $(CYGPATH_W) 'json.c'; else $(CYGPATH_W) '$(srcdir)/json.c'; fi`
+
+ss_server-encrypt.o: encrypt.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-encrypt.o -MD -MP -MF $(DEPDIR)/ss_server-encrypt.Tpo -c -o ss_server-encrypt.o `test -f 'encrypt.c' || echo '$(srcdir)/'`encrypt.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-encrypt.Tpo $(DEPDIR)/ss_server-encrypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='encrypt.c' object='ss_server-encrypt.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-encrypt.o `test -f 'encrypt.c' || echo '$(srcdir)/'`encrypt.c
+
+ss_server-encrypt.obj: encrypt.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-encrypt.obj -MD -MP -MF $(DEPDIR)/ss_server-encrypt.Tpo -c -o ss_server-encrypt.obj `if test -f 'encrypt.c'; then $(CYGPATH_W) 'encrypt.c'; else $(CYGPATH_W) '$(srcdir)/encrypt.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-encrypt.Tpo $(DEPDIR)/ss_server-encrypt.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='encrypt.c' object='ss_server-encrypt.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-encrypt.obj `if test -f 'encrypt.c'; then $(CYGPATH_W) 'encrypt.c'; else $(CYGPATH_W) '$(srcdir)/encrypt.c'; fi`
+
+ss_server-udprelay.o: udprelay.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-udprelay.o -MD -MP -MF $(DEPDIR)/ss_server-udprelay.Tpo -c -o ss_server-udprelay.o `test -f 'udprelay.c' || echo '$(srcdir)/'`udprelay.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-udprelay.Tpo $(DEPDIR)/ss_server-udprelay.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='udprelay.c' object='ss_server-udprelay.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-udprelay.o `test -f 'udprelay.c' || echo '$(srcdir)/'`udprelay.c
+
+ss_server-udprelay.obj: udprelay.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-udprelay.obj -MD -MP -MF $(DEPDIR)/ss_server-udprelay.Tpo -c -o ss_server-udprelay.obj `if test -f 'udprelay.c'; then $(CYGPATH_W) 'udprelay.c'; else $(CYGPATH_W) '$(srcdir)/udprelay.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-udprelay.Tpo $(DEPDIR)/ss_server-udprelay.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='udprelay.c' object='ss_server-udprelay.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-udprelay.obj `if test -f 'udprelay.c'; then $(CYGPATH_W) 'udprelay.c'; else $(CYGPATH_W) '$(srcdir)/udprelay.c'; fi`
+
+ss_server-cache.o: cache.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-cache.o -MD -MP -MF $(DEPDIR)/ss_server-cache.Tpo -c -o ss_server-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-cache.Tpo $(DEPDIR)/ss_server-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cache.c' object='ss_server-cache.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-cache.o `test -f 'cache.c' || echo '$(srcdir)/'`cache.c
+
+ss_server-cache.obj: cache.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-cache.obj -MD -MP -MF $(DEPDIR)/ss_server-cache.Tpo -c -o ss_server-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-cache.Tpo $(DEPDIR)/ss_server-cache.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='cache.c' object='ss_server-cache.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-cache.obj `if test -f 'cache.c'; then $(CYGPATH_W) 'cache.c'; else $(CYGPATH_W) '$(srcdir)/cache.c'; fi`
+
+ss_server-acl.o: acl.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-acl.o -MD -MP -MF $(DEPDIR)/ss_server-acl.Tpo -c -o ss_server-acl.o `test -f 'acl.c' || echo '$(srcdir)/'`acl.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-acl.Tpo $(DEPDIR)/ss_server-acl.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='acl.c' object='ss_server-acl.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-acl.o `test -f 'acl.c' || echo '$(srcdir)/'`acl.c
+
+ss_server-acl.obj: acl.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-acl.obj -MD -MP -MF $(DEPDIR)/ss_server-acl.Tpo -c -o ss_server-acl.obj `if test -f 'acl.c'; then $(CYGPATH_W) 'acl.c'; else $(CYGPATH_W) '$(srcdir)/acl.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-acl.Tpo $(DEPDIR)/ss_server-acl.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='acl.c' object='ss_server-acl.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-acl.obj `if test -f 'acl.c'; then $(CYGPATH_W) 'acl.c'; else $(CYGPATH_W) '$(srcdir)/acl.c'; fi`
+
+ss_server-resolv.o: resolv.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-resolv.o -MD -MP -MF $(DEPDIR)/ss_server-resolv.Tpo -c -o ss_server-resolv.o `test -f 'resolv.c' || echo '$(srcdir)/'`resolv.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-resolv.Tpo $(DEPDIR)/ss_server-resolv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='resolv.c' object='ss_server-resolv.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-resolv.o `test -f 'resolv.c' || echo '$(srcdir)/'`resolv.c
+
+ss_server-resolv.obj: resolv.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-resolv.obj -MD -MP -MF $(DEPDIR)/ss_server-resolv.Tpo -c -o ss_server-resolv.obj `if test -f 'resolv.c'; then $(CYGPATH_W) 'resolv.c'; else $(CYGPATH_W) '$(srcdir)/resolv.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-resolv.Tpo $(DEPDIR)/ss_server-resolv.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='resolv.c' object='ss_server-resolv.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-resolv.obj `if test -f 'resolv.c'; then $(CYGPATH_W) 'resolv.c'; else $(CYGPATH_W) '$(srcdir)/resolv.c'; fi`
+
+ss_server-server.o: server.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-server.o -MD -MP -MF $(DEPDIR)/ss_server-server.Tpo -c -o ss_server-server.o `test -f 'server.c' || echo '$(srcdir)/'`server.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-server.Tpo $(DEPDIR)/ss_server-server.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='server.c' object='ss_server-server.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-server.o `test -f 'server.c' || echo '$(srcdir)/'`server.c
+
+ss_server-server.obj: server.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-server.obj -MD -MP -MF $(DEPDIR)/ss_server-server.Tpo -c -o ss_server-server.obj `if test -f 'server.c'; then $(CYGPATH_W) 'server.c'; else $(CYGPATH_W) '$(srcdir)/server.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-server.Tpo $(DEPDIR)/ss_server-server.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='server.c' object='ss_server-server.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-server.obj `if test -f 'server.c'; then $(CYGPATH_W) 'server.c'; else $(CYGPATH_W) '$(srcdir)/server.c'; fi`
+
+ss_server-http.o: http.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-http.o -MD -MP -MF $(DEPDIR)/ss_server-http.Tpo -c -o ss_server-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-http.Tpo $(DEPDIR)/ss_server-http.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='http.c' object='ss_server-http.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-http.o `test -f 'http.c' || echo '$(srcdir)/'`http.c
+
+ss_server-http.obj: http.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-http.obj -MD -MP -MF $(DEPDIR)/ss_server-http.Tpo -c -o ss_server-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-http.Tpo $(DEPDIR)/ss_server-http.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='http.c' object='ss_server-http.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-http.obj `if test -f 'http.c'; then $(CYGPATH_W) 'http.c'; else $(CYGPATH_W) '$(srcdir)/http.c'; fi`
+
+ss_server-tls.o: tls.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-tls.o -MD -MP -MF $(DEPDIR)/ss_server-tls.Tpo -c -o ss_server-tls.o `test -f 'tls.c' || echo '$(srcdir)/'`tls.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-tls.Tpo $(DEPDIR)/ss_server-tls.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='tls.c' object='ss_server-tls.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-tls.o `test -f 'tls.c' || echo '$(srcdir)/'`tls.c
+
+ss_server-tls.obj: tls.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-tls.obj -MD -MP -MF $(DEPDIR)/ss_server-tls.Tpo -c -o ss_server-tls.obj `if test -f 'tls.c'; then $(CYGPATH_W) 'tls.c'; else $(CYGPATH_W) '$(srcdir)/tls.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-tls.Tpo $(DEPDIR)/ss_server-tls.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='tls.c' object='ss_server-tls.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-tls.obj `if test -f 'tls.c'; then $(CYGPATH_W) 'tls.c'; else $(CYGPATH_W) '$(srcdir)/tls.c'; fi`
+
+ss_server-rule.o: rule.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-rule.o -MD -MP -MF $(DEPDIR)/ss_server-rule.Tpo -c -o ss_server-rule.o `test -f 'rule.c' || echo '$(srcdir)/'`rule.c
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-rule.Tpo $(DEPDIR)/ss_server-rule.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='rule.c' object='ss_server-rule.o' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-rule.o `test -f 'rule.c' || echo '$(srcdir)/'`rule.c
+
+ss_server-rule.obj: rule.c
+@am__fastdepCC_TRUE@	$(AM_V_CC)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -MT ss_server-rule.obj -MD -MP -MF $(DEPDIR)/ss_server-rule.Tpo -c -o ss_server-rule.obj `if test -f 'rule.c'; then $(CYGPATH_W) 'rule.c'; else $(CYGPATH_W) '$(srcdir)/rule.c'; fi`
+@am__fastdepCC_TRUE@	$(AM_V_at)$(am__mv) $(DEPDIR)/ss_server-rule.Tpo $(DEPDIR)/ss_server-rule.Po
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	$(AM_V_CC)source='rule.c' object='ss_server-rule.obj' libtool=no @AMDEPBACKSLASH@
+@AMDEP_TRUE@@am__fastdepCC_FALSE@	DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@
+@am__fastdepCC_FALSE@	$(AM_V_CC@am__nodep@)$(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(ss_server_CFLAGS) $(CFLAGS) -c -o ss_server-rule.obj `if test -f 'rule.c'; then $(CYGPATH_W) 'rule.c'; else $(CYGPATH_W) '$(srcdir)/rule.c'; fi`
+
+mostlyclean-libtool:
+	-rm -f *.lo
+
+clean-libtool:
+	-rm -rf .libs _libs
+
+ID: $(am__tagged_files)
+	$(am__define_uniq_tagged_files); mkid -fID $$unique
+tags: tags-am
+TAGS: tags
+
+tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	set x; \
+	here=`pwd`; \
+	$(am__define_uniq_tagged_files); \
+	shift; \
+	if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \
+	  test -n "$$unique" || unique=$$empty_fix; \
+	  if test $$# -gt 0; then \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      "$$@" $$unique; \
+	  else \
+	    $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \
+	      $$unique; \
+	  fi; \
+	fi
+ctags: ctags-am
+
+CTAGS: ctags
+ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files)
+	$(am__define_uniq_tagged_files); \
+	test -z "$(CTAGS_ARGS)$$unique" \
+	  || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \
+	     $$unique
+
+GTAGS:
+	here=`$(am__cd) $(top_builddir) && pwd` \
+	  && $(am__cd) $(top_srcdir) \
+	  && gtags -i $(GTAGS_ARGS) "$$here"
+cscopelist: cscopelist-am
+
+cscopelist-am: $(am__tagged_files)
+	list='$(am__tagged_files)'; \
+	case "$(srcdir)" in \
+	  [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \
+	  *) sdir=$(subdir)/$(srcdir) ;; \
+	esac; \
+	for i in $$list; do \
+	  if test -f "$$i"; then \
+	    echo "$(subdir)/$$i"; \
+	  else \
+	    echo "$$sdir/$$i"; \
+	  fi; \
+	done >> $(top_builddir)/cscope.files
+
+distclean-tags:
+	-rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags
+
+distdir: $(DISTFILES)
+	@srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \
+	list='$(DISTFILES)'; \
+	  dist_files=`for file in $$list; do echo $$file; done | \
+	  sed -e "s|^$$srcdirstrip/||;t" \
+	      -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \
+	case $$dist_files in \
+	  */*) $(MKDIR_P) `echo "$$dist_files" | \
+			   sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \
+			   sort -u` ;; \
+	esac; \
+	for file in $$dist_files; do \
+	  if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \
+	  if test -d $$d/$$file; then \
+	    dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \
+	    if test -d "$(distdir)/$$file"; then \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \
+	      cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \
+	      find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \
+	    fi; \
+	    cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \
+	  else \
+	    test -f "$(distdir)/$$file" \
+	    || cp -p $$d/$$file "$(distdir)/$$file" \
+	    || exit 1; \
+	  fi; \
+	done
+check-am: all-am
+check: check-am
+all-am: Makefile $(PROGRAMS)
+installdirs:
+	for dir in "$(DESTDIR)$(bindir)"; do \
+	  test -z "$$dir" || $(MKDIR_P) "$$dir"; \
+	done
+install: install-am
+install-exec: install-exec-am
+install-data: install-data-am
+uninstall: uninstall-am
+
+install-am: all-am
+	@$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am
+
+installcheck: installcheck-am
+install-strip:
+	if test -z '$(STRIP)'; then \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	      install; \
+	else \
+	  $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \
+	    install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \
+	    "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \
+	fi
+mostlyclean-generic:
+
+clean-generic:
+
+distclean-generic:
+	-test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES)
+	-test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES)
+
+maintainer-clean-generic:
+	@echo "This command is intended for maintainers to use"
+	@echo "it deletes files that may require special tools to rebuild."
+clean: clean-am
+
+clean-am: clean-binPROGRAMS clean-generic clean-libtool mostlyclean-am
+
+distclean: distclean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+distclean-am: clean-am distclean-compile distclean-generic \
+	distclean-tags
+
+dvi: dvi-am
+
+dvi-am:
+
+html: html-am
+
+html-am:
+
+info: info-am
+
+info-am:
+
+install-data-am:
+
+install-dvi: install-dvi-am
+
+install-dvi-am:
+
+install-exec-am: install-binPROGRAMS
+
+install-html: install-html-am
+
+install-html-am:
+
+install-info: install-info-am
+
+install-info-am:
+
+install-man:
+
+install-pdf: install-pdf-am
+
+install-pdf-am:
+
+install-ps: install-ps-am
+
+install-ps-am:
+
+installcheck-am:
+
+maintainer-clean: maintainer-clean-am
+	-rm -rf ./$(DEPDIR)
+	-rm -f Makefile
+maintainer-clean-am: distclean-am maintainer-clean-generic
+
+mostlyclean: mostlyclean-am
+
+mostlyclean-am: mostlyclean-compile mostlyclean-generic \
+	mostlyclean-libtool
+
+pdf: pdf-am
+
+pdf-am:
+
+ps: ps-am
+
+ps-am:
+
+uninstall-am: uninstall-binPROGRAMS
+
+.MAKE: install-am install-strip
+
+.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \
+	clean-binPROGRAMS clean-generic clean-libtool cscopelist-am \
+	ctags ctags-am distclean distclean-compile distclean-generic \
+	distclean-libtool distclean-tags distdir dvi dvi-am html \
+	html-am info info-am install install-am install-binPROGRAMS \
+	install-data install-data-am install-dvi install-dvi-am \
+	install-exec install-exec-am install-html install-html-am \
+	install-info install-info-am install-man install-pdf \
+	install-pdf-am install-ps install-ps-am install-strip \
+	installcheck installcheck-am installdirs maintainer-clean \
+	maintainer-clean-generic mostlyclean mostlyclean-compile \
+	mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+	tags tags-am uninstall uninstall-am uninstall-binPROGRAMS
+
+.PRECIOUS: Makefile
+
+
+# Tell versions [3.59,3.63) of GNU make to not export all variables.
+# Otherwise a system limit (for SysV at least) may be exceeded.
+.NOEXPORT:
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..ef6a20e
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,3 @@
+# server
+
+`ss-server` and `ss-check` from https://github.com/ywb94/shadowsocks-libev
diff --git a/server/acl.c b/server/acl.c
new file mode 100644
index 0000000..60d4b72
--- /dev/null
+++ b/server/acl.c
@@ -0,0 +1,597 @@
+/*
+ * acl.c - Manage the ACL (Access Control List)
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <ipset/ipset.h>
+#include <ctype.h>
+
+#include "rule.h"
+#include "utils.h"
+#include "cache.h"
+#include "acl.h"
+
+static struct ip_set white_list_ipv4;
+static struct ip_set white_list_ipv6;
+
+static struct ip_set black_list_ipv4;
+static struct ip_set black_list_ipv6;
+
+static struct cork_dllist black_list_rules;
+static struct cork_dllist white_list_rules;
+
+static int acl_mode = BLACK_LIST;
+
+static struct cache *block_list;
+
+static struct ip_set outbound_block_list_ipv4;
+static struct ip_set outbound_block_list_ipv6;
+static struct cork_dllist outbound_block_list_rules;
+
+#ifdef __linux__
+
+#include <unistd.h>
+#include <stdio.h>
+
+#define NO_FIREWALL_MODE 0
+#define IPTABLES_MODE    1
+#define FIREWALLD_MODE   2
+
+static FILE *shell_stdin;
+static int mode = NO_FIREWALL_MODE;
+
+static char chain_name[64];
+static char *iptables_init_chain =
+    "iptables -N %s; iptables -F %s; iptables -A OUTPUT -p tcp --tcp-flags RST RST -j %s";
+static char *iptables_remove_chain =
+    "iptables -D OUTPUT -p tcp --tcp-flags RST RST -j %s; iptables -F %s; iptables -X %s";
+static char *iptables_add_rule    = "iptables -A %s -d %s -j DROP";
+static char *iptables_remove_rule = "iptables -D %s -d %s -j DROP";
+
+static char *ip6tables_init_chain =
+    "ip6tables -N %s; ip6tables -F %s; ip6tables -A OUTPUT -p tcp --tcp-flags RST RST -j %s";
+static char *ip6tables_remove_chain =
+    "ip6tables -D OUTPUT -p tcp --tcp-flags RST RST -j %s; ip6tables -F %s; ip6tables -X %s";
+static char *ip6tables_add_rule    = "ip6tables -A %s -d %s -j DROP";
+static char *ip6tables_remove_rule = "ip6tables -D %s -d %s -j DROP";
+
+static char *firewalld_init_chain =
+    "firewall-cmd --direct --add-chain ipv4 filter %s; \
+     firewall-cmd --direct --passthrough ipv4 -F %s; \
+     firewall-cmd --direct --passthrough ipv4 -A OUTPUT -p tcp --tcp-flags RST RST -j %s";
+static char *firewalld_remove_chain =
+    "firewall-cmd --direct --passthrough ipv4 -D OUTPUT -p tcp --tcp-flags RST RST -j %s; \
+     firewall-cmd --direct --passthrough ipv4 -F %s; \
+     firewall-cmd --direct --remove-chain ipv4 filter %s";
+static char *firewalld_add_rule    = "firewall-cmd --direct --passthrough ipv4 -A %s -d %s -j DROP";
+static char *firewalld_remove_rule = "firewall-cmd --direct --passthrough ipv4 -D %s -d %s -j DROP";
+
+static char *firewalld6_init_chain =
+    "firewall-cmd --direct --add-chain ipv6 filter %s; \
+     firewall-cmd --direct --passthrough ipv6 -F %s; \
+     firewall-cmd --direct --passthrough ipv6 -A OUTPUT -p tcp --tcp-flags RST RST -j %s";
+static char *firewalld6_remove_chain =
+    "firewall-cmd --direct --passthrough ipv6 -D OUTPUT -p tcp --tcp-flags RST RST -j %s; \
+     firewall-cmd --direct --passthrough ipv6 -F %s; \
+     firewall-cmd --direct --remove-chain ipv6 filter %s";
+static char *firewalld6_add_rule    = "firewall-cmd --direct --passthrough ipv6 -A %s -d %s -j DROP";
+static char *firewalld6_remove_rule = "firewall-cmd --direct --passthrough ipv6 -D %s -d %s -j DROP";
+
+static int
+run_cmd(const char *cmd)
+{
+    int ret = 0;
+    char cmdstring[256];
+
+    sprintf(cmdstring, "%s\n", cmd);
+    size_t len = strlen(cmdstring);
+
+    if (shell_stdin != NULL) {
+        ret = fwrite(cmdstring, 1, len, shell_stdin);
+        fflush(shell_stdin);
+    }
+
+    return ret == len;
+}
+
+static int
+init_firewall()
+{
+    int ret = 0;
+    char cli[256];
+    FILE *fp;
+
+    if (getuid() != 0)
+        return -1;
+
+    sprintf(cli, "firewall-cmd --version 2>&1");
+    fp = popen(cli, "r");
+
+    if (fp == NULL)
+        return -1;
+
+    if (pclose(fp) == 0) {
+        mode = FIREWALLD_MODE;
+    } else {
+        /* Check whether we have permission to operate iptables.
+	 * Note that checking `iptables --version` is insufficient:
+         * eg, running within a child user namespace.
+	 */
+        sprintf(cli, "iptables -L 2>&1");
+        fp = popen(cli, "r");
+        if (fp == NULL)
+            return -1;
+        if (pclose(fp) == 0)
+            mode = IPTABLES_MODE;
+    }
+
+    sprintf(chain_name, "SHADOWSOCKS_LIBEV_%d", getpid());
+
+    if (mode == FIREWALLD_MODE) {
+        sprintf(cli, firewalld6_init_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+        sprintf(cli, firewalld_init_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+    } else if (mode == IPTABLES_MODE) {
+        sprintf(cli, ip6tables_init_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+        sprintf(cli, iptables_init_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+    }
+
+    shell_stdin = popen("/bin/sh", "w");
+
+    return ret;
+}
+
+static int
+reset_firewall()
+{
+    int ret = 0;
+    char cli[256];
+
+    if (getuid() != 0)
+        return -1;
+
+    if (mode == IPTABLES_MODE) {
+        sprintf(cli, ip6tables_remove_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+        sprintf(cli, iptables_remove_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+    } else if (mode == FIREWALLD_MODE) {
+        sprintf(cli, firewalld6_remove_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+        sprintf(cli, firewalld_remove_chain, chain_name, chain_name, chain_name);
+        ret |= system(cli);
+    }
+
+    if (shell_stdin != NULL) {
+        run_cmd("exit 0");
+        pclose(shell_stdin);
+    }
+
+    return ret;
+}
+
+static int
+set_firewall_rule(char *addr, int add)
+{
+    char cli[256];
+    struct cork_ip ip;
+
+    if (getuid() != 0)
+        return -1;
+
+    if (cork_ip_init(&ip, addr))
+        return -1;
+
+    if (add) {
+        if (mode == IPTABLES_MODE)
+            sprintf(cli, ip.version == 4 ? iptables_add_rule : ip6tables_add_rule,
+                    chain_name, addr);
+        else if (mode == FIREWALLD_MODE)
+            sprintf(cli, ip.version == 4 ? firewalld_add_rule : firewalld6_add_rule,
+                    chain_name, addr);
+        return run_cmd(cli);
+    } else {
+        if (mode == IPTABLES_MODE)
+            sprintf(cli, ip.version == 4 ? iptables_remove_rule : ip6tables_remove_rule,
+                    chain_name, addr);
+        else if (mode == FIREWALLD_MODE)
+            sprintf(cli, ip.version == 4 ? firewalld_remove_rule : firewalld6_remove_rule,
+                    chain_name, addr);
+        return run_cmd(cli);
+    }
+
+    return 0;
+}
+
+static void
+free_firewall_rule(void *key, void *element)
+{
+    if (key == NULL)
+        return;
+    char *addr = (char *)key;
+    set_firewall_rule(addr, 0);
+    ss_free(element);
+}
+
+#endif
+
+void
+init_block_list(int firewall)
+{
+    // Initialize cache
+#ifdef __linux__
+    if (firewall)
+        init_firewall();
+    else
+        mode = NO_FIREWALL_MODE;
+    cache_create(&block_list, 256, free_firewall_rule);
+#else
+    cache_create(&block_list, 256, NULL);
+#endif
+}
+
+void
+free_block_list()
+{
+#ifdef __linux__
+    if (mode != NO_FIREWALL_MODE)
+        reset_firewall();
+#endif
+    cache_clear(block_list, 0); // Remove all items
+}
+
+int
+remove_from_block_list(char *addr)
+{
+    size_t addr_len = strlen(addr);
+    return cache_remove(block_list, addr, addr_len);
+}
+
+void
+clear_block_list()
+{
+    cache_clear(block_list, 3600); // Clear items older than 1 hour
+}
+
+int
+check_block_list(char *addr)
+{
+    size_t addr_len = strlen(addr);
+
+    if (cache_key_exist(block_list, addr, addr_len)) {
+        int *count = NULL;
+        cache_lookup(block_list, addr, addr_len, &count);
+
+        if (count != NULL && *count > MAX_TRIES)
+            return 1;
+    }
+
+    return 0;
+}
+
+int
+update_block_list(char *addr, int err_level)
+{
+    size_t addr_len = strlen(addr);
+
+    if (cache_key_exist(block_list, addr, addr_len)) {
+        int *count = NULL;
+        cache_lookup(block_list, addr, addr_len, &count);
+        if (count != NULL) {
+            if (*count > MAX_TRIES)
+                return 1;
+            (*count) += err_level;
+        }
+    } else if (err_level > 0) {
+        int *count = (int *)ss_malloc(sizeof(int));
+        *count = 1;
+        cache_insert(block_list, addr, addr_len, count);
+#ifdef __linux__
+        if (mode != NO_FIREWALL_MODE)
+            set_firewall_rule(addr, 1);
+#endif
+    }
+
+    return 0;
+}
+
+static void
+parse_addr_cidr(const char *str, char *host, int *cidr)
+{
+    int ret = -1, n = 0;
+    char *pch;
+
+    pch = strchr(str, '/');
+    while (pch != NULL) {
+        n++;
+        ret = pch - str;
+        pch = strchr(pch + 1, '/');
+    }
+    if (ret == -1) {
+        strcpy(host, str);
+        *cidr = -1;
+    } else {
+        memcpy(host, str, ret);
+        host[ret] = '\0';
+        *cidr     = atoi(str + ret + 1);
+    }
+}
+
+char *
+trimwhitespace(char *str)
+{
+    char *end;
+
+    // Trim leading space
+    while (isspace(*str))
+        str++;
+
+    if (*str == 0)   // All spaces?
+        return str;
+
+    // Trim trailing space
+    end = str + strlen(str) - 1;
+    while (end > str && isspace(*end))
+        end--;
+
+    // Write new null terminator
+    *(end + 1) = 0;
+
+    return str;
+}
+
+int
+init_acl(const char *path)
+{
+    // initialize ipset
+    ipset_init_library();
+
+    ipset_init(&white_list_ipv4);
+    ipset_init(&white_list_ipv6);
+    ipset_init(&black_list_ipv4);
+    ipset_init(&black_list_ipv6);
+    ipset_init(&outbound_block_list_ipv4);
+    ipset_init(&outbound_block_list_ipv6);
+
+    cork_dllist_init(&black_list_rules);
+    cork_dllist_init(&white_list_rules);
+    cork_dllist_init(&outbound_block_list_rules);
+
+    struct ip_set *list_ipv4  = &black_list_ipv4;
+    struct ip_set *list_ipv6  = &black_list_ipv6;
+    struct cork_dllist *rules = &black_list_rules;
+
+    FILE *f = fopen(path, "r");
+    if (f == NULL) {
+        LOGE("Invalid acl path.");
+        return -1;
+    }
+
+    char buf[257];
+    while (!feof(f))
+        if (fgets(buf, 256, f)) {
+            // Trim the newline
+            int len = strlen(buf);
+            if (len > 0 && buf[len - 1] == '\n') {
+                buf[len - 1] = '\0';
+            }
+
+            char *line = trimwhitespace(buf);
+
+            // Skip comments
+            if (line[0] == '#') {
+                continue;
+            }
+
+            if (strlen(line) == 0) {
+                continue;
+            }
+
+            if (strcmp(line, "[outbound_block_list]") == 0) {
+                list_ipv4 = &outbound_block_list_ipv4;
+                list_ipv6 = &outbound_block_list_ipv6;
+                rules     = &outbound_block_list_rules;
+                continue;
+            } else if (strcmp(line, "[black_list]") == 0
+                       || strcmp(line, "[bypass_list]") == 0) {
+                list_ipv4 = &black_list_ipv4;
+                list_ipv6 = &black_list_ipv6;
+                rules     = &black_list_rules;
+                continue;
+            } else if (strcmp(line, "[white_list]") == 0
+                       || strcmp(line, "[proxy_list]") == 0) {
+                list_ipv4 = &white_list_ipv4;
+                list_ipv6 = &white_list_ipv6;
+                rules     = &white_list_rules;
+                continue;
+            } else if (strcmp(line, "[reject_all]") == 0
+                       || strcmp(line, "[bypass_all]") == 0) {
+                acl_mode = WHITE_LIST;
+                continue;
+            } else if (strcmp(line, "[accept_all]") == 0
+                       || strcmp(line, "[proxy_all]") == 0) {
+                acl_mode = BLACK_LIST;
+                continue;
+            }
+
+            char host[257];
+            int cidr;
+            parse_addr_cidr(line, host, &cidr);
+
+            struct cork_ip addr;
+            int err = cork_ip_init(&addr, host);
+            if (!err) {
+                if (addr.version == 4) {
+                    if (cidr >= 0) {
+                        ipset_ipv4_add_network(list_ipv4, &(addr.ip.v4), cidr);
+                    } else {
+                        ipset_ipv4_add(list_ipv4, &(addr.ip.v4));
+                    }
+                } else if (addr.version == 6) {
+                    if (cidr >= 0) {
+                        ipset_ipv6_add_network(list_ipv6, &(addr.ip.v6), cidr);
+                    } else {
+                        ipset_ipv6_add(list_ipv6, &(addr.ip.v6));
+                    }
+                }
+            } else {
+                rule_t *rule = new_rule();
+                accept_rule_arg(rule, line);
+                init_rule(rule);
+                add_rule(rules, rule);
+            }
+        }
+
+    fclose(f);
+
+    return 0;
+}
+
+void
+free_rules(struct cork_dllist *rules)
+{
+    struct cork_dllist_item *iter;
+    while ((iter = cork_dllist_head(rules)) != NULL) {
+        rule_t *rule = cork_container_of(iter, rule_t, entries);
+        remove_rule(rule);
+    }
+}
+
+void
+free_acl(void)
+{
+    ipset_done(&black_list_ipv4);
+    ipset_done(&black_list_ipv6);
+    ipset_done(&white_list_ipv4);
+    ipset_done(&white_list_ipv6);
+
+    free_rules(&black_list_rules);
+    free_rules(&white_list_rules);
+}
+
+int
+get_acl_mode(void)
+{
+    return acl_mode;
+}
+
+/*
+ * Return 0,  if not match.
+ * Return 1,  if match black list.
+ * Return -1, if match white list.
+ */
+int
+acl_match_host(const char *host)
+{
+    struct cork_ip addr;
+    int ret = 0;
+    int err = cork_ip_init(&addr, host);
+
+    if (err) {
+        int host_len = strlen(host);
+        if (lookup_rule(&black_list_rules, host, host_len) != NULL)
+            ret = 1;
+        else if (lookup_rule(&white_list_rules, host, host_len) != NULL)
+            ret = -1;
+        return ret;
+    }
+
+    if (addr.version == 4) {
+        if (ipset_contains_ipv4(&black_list_ipv4, &(addr.ip.v4)))
+            ret = 1;
+        else if (ipset_contains_ipv4(&white_list_ipv4, &(addr.ip.v4)))
+            ret = -1;
+    } else if (addr.version == 6) {
+        if (ipset_contains_ipv6(&black_list_ipv6, &(addr.ip.v6)))
+            ret = 1;
+        else if (ipset_contains_ipv6(&white_list_ipv6, &(addr.ip.v6)))
+            ret = -1;
+    }
+
+    return ret;
+}
+
+int
+acl_add_ip(const char *ip)
+{
+    struct cork_ip addr;
+    int err = cork_ip_init(&addr, ip);
+    if (err) {
+        return -1;
+    }
+
+    if (addr.version == 4) {
+        ipset_ipv4_add(&black_list_ipv4, &(addr.ip.v4));
+    } else if (addr.version == 6) {
+        ipset_ipv6_add(&black_list_ipv6, &(addr.ip.v6));
+    }
+
+    return 0;
+}
+
+int
+acl_remove_ip(const char *ip)
+{
+    struct cork_ip addr;
+    int err = cork_ip_init(&addr, ip);
+    if (err) {
+        return -1;
+    }
+
+    if (addr.version == 4) {
+        ipset_ipv4_remove(&black_list_ipv4, &(addr.ip.v4));
+    } else if (addr.version == 6) {
+        ipset_ipv6_remove(&black_list_ipv6, &(addr.ip.v6));
+    }
+
+    return 0;
+}
+
+/*
+ * Return 0,  if not match.
+ * Return 1,  if match black list.
+ */
+int
+outbound_block_match_host(const char *host)
+{
+    struct cork_ip addr;
+    int ret = 0;
+    int err = cork_ip_init(&addr, host);
+
+    if (err) {
+        int host_len = strlen(host);
+        if (lookup_rule(&outbound_block_list_rules, host, host_len) != NULL)
+            ret = 1;
+        return ret;
+    }
+
+    if (addr.version == 4) {
+        if (ipset_contains_ipv4(&outbound_block_list_ipv4, &(addr.ip.v4)))
+            ret = 1;
+    } else if (addr.version == 6) {
+        if (ipset_contains_ipv6(&outbound_block_list_ipv6, &(addr.ip.v6)))
+            ret = 1;
+    }
+
+    return ret;
+}
diff --git a/server/acl.h b/server/acl.h
new file mode 100644
index 0000000..d6f18b8
--- /dev/null
+++ b/server/acl.h
@@ -0,0 +1,53 @@
+/*
+ * acl.h - Define the ACL interface
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _ACL_H
+#define _ACL_H
+
+#define BLACK_LIST 0
+#define WHITE_LIST 1
+
+#define MAX_TRIES  64
+#define MALICIOUS  8
+#define SUSPICIOUS 4
+#define BAD        2
+#define MALFORMED  1
+
+int init_acl(const char *path);
+void free_acl(void);
+void clear_block_list(void);
+
+int acl_match_host(const char *ip);
+int acl_add_ip(const char *ip);
+int acl_remove_ip(const char *ip);
+
+int get_acl_mode(void);
+
+void init_block_list(int firewall);
+void free_block_list();
+int check_block_list(char *addr);
+int update_block_list(char *addr, int err_level);
+int remove_from_block_list(char *addr);
+
+int outbound_block_match_host(const char *host);
+
+#endif // _ACL_H
diff --git a/server/auth.c b/server/auth.c
new file mode 100644
index 0000000..a36257a
--- /dev/null
+++ b/server/auth.c
@@ -0,0 +1,993 @@
+
+#include "auth.h"
+
+static int auth_simple_pack_unit_size = 2000;
+typedef int (*hmac_with_key_func)(char *auth, char *msg, int msg_len, uint8_t *auth_key, int key_len);
+typedef int (*hash_func)(char *auth, char *msg, int msg_len);
+
+typedef struct auth_simple_global_data {
+    uint8_t local_client_id[8];
+    uint32_t connection_id;
+}auth_simple_global_data;
+
+typedef struct auth_simple_local_data {
+    int has_sent_header;
+    char * recv_buffer;
+    int recv_buffer_size;
+    uint32_t recv_id;
+    uint32_t pack_id;
+    char * salt;
+    uint8_t * user_key;
+    char uid[4];
+    int user_key_len;
+    hmac_with_key_func hmac;
+    hash_func hash;
+    int hash_len;
+}auth_simple_local_data;
+
+void auth_simple_local_data_init(auth_simple_local_data* local) {
+    local->has_sent_header = 0;
+    local->recv_buffer = (char*)malloc(16384);
+    local->recv_buffer_size = 0;
+    local->recv_id = 1;
+    local->pack_id = 1;
+    local->salt = "";
+    local->user_key = 0;
+    local->user_key_len = 0;
+    local->hmac = 0;
+    local->hash = 0;
+    local->hash_len = 0;
+    local->salt = "";
+}
+
+void * auth_simple_init_data() {
+    auth_simple_global_data *global = (auth_simple_global_data*)malloc(sizeof(auth_simple_global_data));
+    rand_bytes(global->local_client_id, 8);
+    rand_bytes((uint8_t*)&global->connection_id, 4);
+    global->connection_id &= 0xFFFFFF;
+    return global;
+}
+
+obfs * auth_simple_new_obfs() {
+    obfs * self = new_obfs();
+    self->l_data = malloc(sizeof(auth_simple_local_data));
+    auth_simple_local_data_init((auth_simple_local_data*)self->l_data);
+    return self;
+}
+
+obfs * auth_aes128_md5_new_obfs() {
+    obfs * self = new_obfs();
+    self->l_data = malloc(sizeof(auth_simple_local_data));
+    auth_simple_local_data_init((auth_simple_local_data*)self->l_data);
+    ((auth_simple_local_data*)self->l_data)->hmac = ss_md5_hmac_with_key;
+    ((auth_simple_local_data*)self->l_data)->hash = ss_md5_hash_func;
+    ((auth_simple_local_data*)self->l_data)->hash_len = 16;
+    ((auth_simple_local_data*)self->l_data)->salt = "auth_aes128_md5";
+    return self;
+}
+
+obfs * auth_aes128_sha1_new_obfs() {
+    obfs * self = new_obfs();
+    self->l_data = malloc(sizeof(auth_simple_local_data));
+    auth_simple_local_data_init((auth_simple_local_data*)self->l_data);
+    ((auth_simple_local_data*)self->l_data)->hmac = ss_sha1_hmac_with_key;
+    ((auth_simple_local_data*)self->l_data)->hash = ss_sha1_hash_func;
+    ((auth_simple_local_data*)self->l_data)->hash_len = 20;
+    ((auth_simple_local_data*)self->l_data)->salt = "auth_aes128_sha1";
+    return self;
+}
+
+void auth_simple_dispose(obfs *self) {
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    if (local->recv_buffer != NULL) {
+        free(local->recv_buffer);
+        local->recv_buffer = NULL;
+    }
+    if (local->user_key != NULL) {
+        free(local->user_key);
+        local->user_key = NULL;
+    }
+    free(local);
+    self->l_data = NULL;
+    dispose_obfs(self);
+}
+
+int auth_simple_pack_data(char *data, int datalength, char *outdata) {
+    unsigned char rand_len = (xorshift128plus() & 0xF) + 1;
+    int out_size = rand_len + datalength + 6;
+    outdata[0] = out_size >> 8;
+    outdata[1] = out_size;
+    outdata[2] = rand_len;
+    memmove(outdata + rand_len + 2, data, datalength);
+    fillcrc32((unsigned char *)outdata, out_size);
+    return out_size;
+}
+
+void memintcopy_lt(void *mem, uint32_t val) {
+    ((uint8_t *)mem)[0] = val;
+    ((uint8_t *)mem)[1] = val >> 8;
+    ((uint8_t *)mem)[2] = val >> 16;
+    ((uint8_t *)mem)[3] = val >> 24;
+}
+
+int auth_simple_pack_auth_data(auth_simple_global_data *global, char *data, int datalength, char *outdata) {
+    unsigned char rand_len = (xorshift128plus() & 0xF) + 1;
+    int out_size = rand_len + datalength + 6 + 12;
+    outdata[0] = out_size >> 8;
+    outdata[1] = out_size;
+    outdata[2] = rand_len;
+    ++global->connection_id;
+    if (global->connection_id > 0xFF000000) {
+        rand_bytes(global->local_client_id, 8);
+        rand_bytes((uint8_t*)&global->connection_id, 4);
+        global->connection_id &= 0xFFFFFF;
+    }
+    time_t t = time(NULL);
+    memintcopy_lt(outdata + rand_len + 2, t);
+    memmove(outdata + rand_len + 2 + 4, global->local_client_id, 4);
+    memintcopy_lt(outdata + rand_len + 2 + 8, global->connection_id);
+    memmove(outdata + rand_len + 2 + 12, data, datalength);
+    fillcrc32((unsigned char *)outdata, out_size);
+    return out_size;
+}
+
+int auth_simple_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 64);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    if (len > 0 && local->has_sent_header == 0) {
+        int head_size = get_head_size(plaindata, datalength, 30);
+        if (head_size > datalength)
+            head_size = datalength;
+        pack_len = auth_simple_pack_auth_data((auth_simple_global_data *)self->server.g_data, data, head_size, buffer);
+        buffer += pack_len;
+        data += head_size;
+        len -= head_size;
+        local->has_sent_header = 1;
+    }
+    while ( len > auth_simple_pack_unit_size ) {
+        pack_len = auth_simple_pack_data(data, auth_simple_pack_unit_size, buffer);
+        buffer += pack_len;
+        data += auth_simple_pack_unit_size;
+        len -= auth_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = auth_simple_pack_data(data, len, buffer);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int auth_simple_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+        return -1;
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    while (local->recv_buffer_size > 2) {
+        int length = ((int)recv_buffer[0] << 8) | recv_buffer[1];
+        if (length >= 8192 || length < 7) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            return -1;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        int crc = crc32((unsigned char*)recv_buffer, length);
+        if (crc != -1) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            return -1;
+        }
+        int data_size = length - recv_buffer[2] - 6;
+        memmove(buffer, recv_buffer + 2 + recv_buffer[2], data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+
+int auth_sha1_pack_data(char *data, int datalength, char *outdata) {
+    unsigned char rand_len = (xorshift128plus() & 0xF) + 1;
+    int out_size = rand_len + datalength + 6;
+    outdata[0] = out_size >> 8;
+    outdata[1] = out_size;
+    outdata[2] = rand_len;
+    memmove(outdata + rand_len + 2, data, datalength);
+    filladler32((unsigned char *)outdata, out_size);
+    return out_size;
+}
+
+int auth_sha1_pack_auth_data(auth_simple_global_data *global, server_info *server, char *data, int datalength, char *outdata) {
+    unsigned char rand_len = (xorshift128plus() & 0x7F) + 1;
+    int data_offset = rand_len + 4 + 2;
+    int out_size = data_offset + datalength + 12 + OBFS_HMAC_SHA1_LEN;
+    fillcrc32to((unsigned char *)server->key, server->key_len, (unsigned char *)outdata);
+    outdata[4] = out_size >> 8;
+    outdata[5] = out_size;
+    outdata[6] = rand_len;
+    ++global->connection_id;
+    if (global->connection_id > 0xFF000000) {
+        rand_bytes(global->local_client_id, 8);
+        rand_bytes((uint8_t*)&global->connection_id, 4);
+        global->connection_id &= 0xFFFFFF;
+    }
+    time_t t = time(NULL);
+    memintcopy_lt(outdata + data_offset, t);
+    memmove(outdata + data_offset + 4, global->local_client_id, 4);
+    memintcopy_lt(outdata + data_offset + 8, global->connection_id);
+    memmove(outdata + data_offset + 12, data, datalength);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    ss_sha1_hmac(hash, outdata, out_size - OBFS_HMAC_SHA1_LEN, server->iv);
+    memcpy(outdata + out_size - OBFS_HMAC_SHA1_LEN, hash, OBFS_HMAC_SHA1_LEN);
+    return out_size;
+}
+
+int auth_sha1_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 256);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    if (len > 0 && local->has_sent_header == 0) {
+        int head_size = get_head_size(plaindata, datalength, 30);
+        if (head_size > datalength)
+            head_size = datalength;
+        pack_len = auth_sha1_pack_auth_data((auth_simple_global_data *)self->server.g_data, &self->server, data, head_size, buffer);
+        buffer += pack_len;
+        data += head_size;
+        len -= head_size;
+        local->has_sent_header = 1;
+    }
+    while ( len > auth_simple_pack_unit_size ) {
+        pack_len = auth_sha1_pack_data(data, auth_simple_pack_unit_size, buffer);
+        buffer += pack_len;
+        data += auth_simple_pack_unit_size;
+        len -= auth_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = auth_sha1_pack_data(data, len, buffer);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int auth_sha1_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+        return -1;
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    while (local->recv_buffer_size > 2) {
+        int length = ((int)recv_buffer[0] << 8) | recv_buffer[1];
+        if (length >= 8192 || length < 7) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            return -1;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        if (checkadler32((unsigned char*)recv_buffer, length) == 0) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            return -1;
+        }
+        int pos = recv_buffer[2] + 2;
+        int data_size = length - pos - 4;
+        memmove(buffer, recv_buffer + pos, data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int auth_sha1_v2_pack_data(char *data, int datalength, char *outdata) {
+    unsigned int rand_len = (datalength > 1300 ? 0 : datalength > 400 ? (xorshift128plus() & 0x7F) : (xorshift128plus() & 0x3FF)) + 1;
+    int out_size = rand_len + datalength + 6;
+    outdata[0] = out_size >> 8;
+    outdata[1] = out_size;
+    if (rand_len < 128)
+    {
+        outdata[2] = rand_len;
+    }
+    else
+    {
+        outdata[2] = 0xFF;
+        outdata[3] = rand_len >> 8;
+        outdata[4] = rand_len;
+    }
+    memmove(outdata + rand_len + 2, data, datalength);
+    filladler32((unsigned char *)outdata, out_size);
+    return out_size;
+}
+
+int auth_sha1_v2_pack_auth_data(auth_simple_global_data *global, server_info *server, char *data, int datalength, char *outdata) {
+    unsigned int rand_len = (datalength > 1300 ? 0 : datalength > 400 ? (xorshift128plus() & 0x7F) : (xorshift128plus() & 0x3FF)) + 1;
+    int data_offset = rand_len + 4 + 2;
+    int out_size = data_offset + datalength + 12 + OBFS_HMAC_SHA1_LEN;
+    const char* salt = "auth_sha1_v2";
+    int salt_len = strlen(salt);
+    unsigned char *crc_salt = (unsigned char*)malloc(salt_len + server->key_len);
+    memcpy(crc_salt, salt, salt_len);
+    memcpy(crc_salt + salt_len, server->key, server->key_len);
+    fillcrc32to(crc_salt, salt_len + server->key_len, (unsigned char *)outdata);
+    free(crc_salt);
+    outdata[4] = out_size >> 8;
+    outdata[5] = out_size;
+    if (rand_len < 128)
+    {
+        outdata[6] = rand_len;
+    }
+    else
+    {
+        outdata[6] = 0xFF;
+        outdata[7] = rand_len >> 8;
+        outdata[8] = rand_len;
+    }
+    ++global->connection_id;
+    if (global->connection_id > 0xFF000000) {
+        rand_bytes(global->local_client_id, 8);
+        rand_bytes((uint8_t*)&global->connection_id, 4);
+        global->connection_id &= 0xFFFFFF;
+    }
+    memmove(outdata + data_offset, global->local_client_id, 8);
+    memintcopy_lt(outdata + data_offset + 8, global->connection_id);
+    memmove(outdata + data_offset + 12, data, datalength);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    ss_sha1_hmac(hash, outdata, out_size - OBFS_HMAC_SHA1_LEN, server->iv);
+    memcpy(outdata + out_size - OBFS_HMAC_SHA1_LEN, hash, OBFS_HMAC_SHA1_LEN);
+    return out_size;
+}
+
+int auth_sha1_v2_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 4096);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    if (len > 0 && local->has_sent_header == 0) {
+        int head_size = get_head_size(plaindata, datalength, 30);
+        if (head_size > datalength)
+            head_size = datalength;
+        pack_len = auth_sha1_v2_pack_auth_data((auth_simple_global_data *)self->server.g_data, &self->server, data, head_size, buffer);
+        buffer += pack_len;
+        data += head_size;
+        len -= head_size;
+        local->has_sent_header = 1;
+    }
+    while ( len > auth_simple_pack_unit_size ) {
+        pack_len = auth_sha1_v2_pack_data(data, auth_simple_pack_unit_size, buffer);
+        buffer += pack_len;
+        data += auth_simple_pack_unit_size;
+        len -= auth_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = auth_sha1_v2_pack_data(data, len, buffer);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int auth_sha1_v2_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+        return -1;
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    char error = 0;
+    while (local->recv_buffer_size > 2) {
+        int length = ((int)recv_buffer[0] << 8) | recv_buffer[1];
+        if (length >= 8192 || length < 7) {
+            local->recv_buffer_size = 0;
+            error = 1;
+            break;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        if (checkadler32((unsigned char*)recv_buffer, length) == 0) {
+            local->recv_buffer_size = 0;
+            error = 1;
+            break;
+        }
+        int pos = recv_buffer[2];
+        if (pos < 255)
+        {
+            pos += 2;
+        }
+        else
+        {
+            pos = ((recv_buffer[3] << 8) | recv_buffer[4]) + 2;
+        }
+        int data_size = length - pos - 4;
+        memmove(buffer, recv_buffer + pos, data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len;
+    if (error == 0) {
+        len = buffer - out_buffer;
+        if (*capacity < len) {
+            *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+            plaindata = *pplaindata;
+        }
+        memmove(plaindata, out_buffer, len);
+    } else {
+        len = -1;
+    }
+    free(out_buffer);
+    return len;
+}
+
+int auth_sha1_v4_pack_data(char *data, int datalength, char *outdata) {
+    unsigned int rand_len = (datalength > 1300 ? 0 : datalength > 400 ? (xorshift128plus() & 0x7F) : (xorshift128plus() & 0x3FF)) + 1;
+    int out_size = rand_len + datalength + 8;
+    outdata[0] = out_size >> 8;
+    outdata[1] = out_size;
+    uint32_t crc_val = crc32((unsigned char*)outdata, 2);
+    outdata[2] = crc_val;
+    outdata[3] = crc_val >> 8;
+    if (rand_len < 128)
+    {
+        outdata[4] = rand_len;
+    }
+    else
+    {
+        outdata[4] = 0xFF;
+        outdata[5] = rand_len >> 8;
+        outdata[6] = rand_len;
+    }
+    memmove(outdata + rand_len + 4, data, datalength);
+    filladler32((unsigned char *)outdata, out_size);
+    return out_size;
+}
+
+int auth_sha1_v4_pack_auth_data(auth_simple_global_data *global, server_info *server, char *data, int datalength, char *outdata) {
+    unsigned int rand_len = (datalength > 1300 ? 0 : datalength > 400 ? (xorshift128plus() & 0x7F) : (xorshift128plus() & 0x3FF)) + 1;
+    int data_offset = rand_len + 4 + 2;
+    int out_size = data_offset + datalength + 12 + OBFS_HMAC_SHA1_LEN;
+    const char* salt = "auth_sha1_v4";
+    int salt_len = strlen(salt);
+    unsigned char *crc_salt = (unsigned char*)malloc(salt_len + server->key_len + 2);
+    crc_salt[0] = outdata[0] = out_size >> 8;
+    crc_salt[1] = outdata[1] = out_size;
+
+    memcpy(crc_salt + 2, salt, salt_len);
+    memcpy(crc_salt + salt_len + 2, server->key, server->key_len);
+    fillcrc32to(crc_salt, salt_len + server->key_len + 2, (unsigned char *)outdata + 2);
+    free(crc_salt);
+    if (rand_len < 128)
+    {
+        outdata[6] = rand_len;
+    }
+    else
+    {
+        outdata[6] = 0xFF;
+        outdata[7] = rand_len >> 8;
+        outdata[8] = rand_len;
+    }
+    ++global->connection_id;
+    if (global->connection_id > 0xFF000000) {
+        rand_bytes(global->local_client_id, 8);
+        rand_bytes((uint8_t*)&global->connection_id, 4);
+        global->connection_id &= 0xFFFFFF;
+    }
+    time_t t = time(NULL);
+    memintcopy_lt(outdata + data_offset, t);
+    memmove(outdata + data_offset + 4, global->local_client_id, 4);
+    memintcopy_lt(outdata + data_offset + 8, global->connection_id);
+    memmove(outdata + data_offset + 12, data, datalength);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    ss_sha1_hmac(hash, outdata, out_size - OBFS_HMAC_SHA1_LEN, server->iv);
+    memcpy(outdata + out_size - OBFS_HMAC_SHA1_LEN, hash, OBFS_HMAC_SHA1_LEN);
+    return out_size;
+}
+
+int auth_sha1_v4_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 4096);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    if (len > 0 && local->has_sent_header == 0) {
+        int head_size = get_head_size(plaindata, datalength, 30);
+        if (head_size > datalength)
+            head_size = datalength;
+        pack_len = auth_sha1_v4_pack_auth_data((auth_simple_global_data *)self->server.g_data, &self->server, data, head_size, buffer);
+        buffer += pack_len;
+        data += head_size;
+        len -= head_size;
+        local->has_sent_header = 1;
+    }
+    while ( len > auth_simple_pack_unit_size ) {
+        pack_len = auth_sha1_v4_pack_data(data, auth_simple_pack_unit_size, buffer);
+        buffer += pack_len;
+        data += auth_simple_pack_unit_size;
+        len -= auth_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = auth_sha1_v4_pack_data(data, len, buffer);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int auth_sha1_v4_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+        return -1;
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    char error = 0;
+    while (local->recv_buffer_size > 4) {
+        uint32_t crc_val = crc32((unsigned char*)recv_buffer, 2);
+        if ((((uint32_t)recv_buffer[3] << 8) | recv_buffer[2]) != (crc_val & 0xffff)) {
+            local->recv_buffer_size = 0;
+            error = 1;
+            break;
+        }
+        int length = ((int)recv_buffer[0] << 8) | recv_buffer[1];
+        if (length >= 8192 || length < 7) {
+            local->recv_buffer_size = 0;
+            error = 1;
+            break;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        if (checkadler32((unsigned char*)recv_buffer, length) == 0) {
+            local->recv_buffer_size = 0;
+            error = 1;
+            break;
+        }
+        int pos = recv_buffer[4];
+        if (pos < 255)
+        {
+            pos += 4;
+        }
+        else
+        {
+            pos = (((int)recv_buffer[5] << 8) | recv_buffer[6]) + 4;
+        }
+        int data_size = length - pos - 4;
+        memmove(buffer, recv_buffer + pos, data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len;
+    if (error == 0) {
+        len = buffer - out_buffer;
+        if (*capacity < len) {
+            *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+            plaindata = *pplaindata;
+        }
+        memmove(plaindata, out_buffer, len);
+    } else {
+        len = -1;
+    }
+    free(out_buffer);
+    return len;
+}
+
+
+int auth_aes128_sha1_pack_data(char *data, int datalength, char *outdata, auth_simple_local_data *local, server_info *server) {
+    unsigned int rand_len = (datalength > 1200 ? 0 : local->pack_id > 4 ? (xorshift128plus() & 0x20) : datalength > 900 ? (xorshift128plus() & 0x80) : (xorshift128plus() & 0x200)) + 1;
+    int out_size = rand_len + datalength + 8;
+    memcpy(outdata + rand_len + 4, data, datalength);
+    outdata[0] = out_size;
+    outdata[1] = out_size >> 8;
+    uint8_t key_len = local->user_key_len + 4;
+    uint8_t *key = (uint8_t*)malloc(key_len);
+    memcpy(key, local->user_key, local->user_key_len);
+    memintcopy_lt(key + key_len - 4, local->pack_id);
+
+    {
+        uint8_t rnd_data[rand_len];
+        rand_bytes(rnd_data, rand_len);
+        memcpy(outdata + 4, rnd_data, rand_len);
+    }
+
+    {
+        char hash[20];
+        local->hmac(hash, outdata, 2, key, key_len);
+        memcpy(outdata + 2, hash, 2);
+    }
+
+    if (rand_len < 128)
+    {
+        outdata[4] = rand_len;
+    }
+    else
+    {
+        outdata[4] = 0xFF;
+        outdata[5] = rand_len;
+        outdata[6] = rand_len >> 8;
+    }
+    ++local->pack_id;
+
+    {
+        char hash[20];
+        local->hmac(hash, outdata, out_size - 4, key, key_len);
+        memcpy(outdata + out_size - 4, hash, 4);
+    }
+    free(key);
+
+    return out_size;
+}
+
+int auth_aes128_sha1_pack_auth_data(auth_simple_global_data *global, server_info *server, auth_simple_local_data *local, char *data, int datalength, char *outdata) {
+    unsigned int rand_len = (datalength > 400 ? (xorshift128plus() & 0x200) : (xorshift128plus() & 0x400));
+    int data_offset = rand_len + 16 + 4 + 4 + 7;
+    int out_size = data_offset + datalength + 4;
+
+    char encrypt[24];
+    char encrypt_data[16];
+
+    uint8_t *key = (uint8_t*)malloc(server->iv_len + server->key_len);
+    uint8_t key_len = server->iv_len + server->key_len;
+    memcpy(key, server->iv, server->iv_len);
+    memcpy(key + server->iv_len, server->key, server->key_len);
+
+    {
+        uint8_t rnd_data[rand_len];
+        rand_bytes(rnd_data, rand_len);
+        memcpy(outdata + data_offset - rand_len, rnd_data, rand_len);
+    }
+
+    ++global->connection_id;
+    if (global->connection_id > 0xFF000000) {
+        rand_bytes(global->local_client_id, 8);
+        rand_bytes((uint8_t*)&global->connection_id, 4);
+        global->connection_id &= 0xFFFFFF;
+    }
+    time_t t = time(NULL);
+    memintcopy_lt(encrypt, t);
+    memcpy(encrypt + 4, global->local_client_id, 4);
+    memintcopy_lt(encrypt + 8, global->connection_id);
+    encrypt[12] = out_size;
+    encrypt[13] = out_size >> 8;
+    encrypt[14] = rand_len;
+    encrypt[15] = rand_len >> 8;
+
+    {
+
+        if (local->user_key == NULL) {
+            if(server->param != NULL && server->param[0] != 0) {
+                char *param = server->param;
+                char *delim = strchr(param, ':');
+                if(delim != NULL) {
+                    char uid_str[16] = {};
+                    strncpy(uid_str, param, delim - param);
+                    char key_str[128];
+                    strcpy(key_str, delim + 1);
+                    long uid_long = strtol(uid_str, NULL, 10);
+                    memintcopy_lt(local->uid, uid_long);
+
+                    char hash[21] = {0};
+                    local->hash(hash, key_str, strlen(key_str));
+
+                    local->user_key_len = local->hash_len;
+                    local->user_key = (uint8_t*)malloc(local->user_key_len);
+                    memcpy(local->user_key, hash, local->hash_len);
+                }
+            }
+            if (local->user_key == NULL) {
+                rand_bytes((uint8_t *)local->uid, 4);
+
+                local->user_key_len = server->key_len;
+                local->user_key = (uint8_t*)malloc(local->user_key_len);
+                memcpy(local->user_key, server->key, local->user_key_len);
+            }
+        }
+
+        char encrypt_key_base64[256] = {0};
+        unsigned char encrypt_key[local->user_key_len];
+        memcpy(encrypt_key, local->user_key, local->user_key_len);
+        base64_encode(encrypt_key, local->user_key_len, encrypt_key_base64);
+
+        int base64_len;
+        base64_len = (local->user_key_len + 2) / 3 * 4;
+        memcpy(encrypt_key_base64 + base64_len, local->salt, strlen(local->salt));
+
+        char enc_key[16];
+        int enc_key_len = base64_len + strlen(local->salt);
+        bytes_to_key_with_size(encrypt_key_base64, enc_key_len, (uint8_t*)enc_key, 16);
+        ss_aes_128_cbc(encrypt, encrypt_data, enc_key);
+        memcpy(encrypt + 4, encrypt_data, 16);
+        memcpy(encrypt, local->uid, 4);
+    }
+
+    {
+        char hash[20];
+        local->hmac(hash, encrypt, 20, key, key_len);
+        memcpy(encrypt + 20, hash, 4);
+    }
+
+    {
+        uint8_t rnd[1];
+        rand_bytes(rnd, 1);
+        memcpy(outdata, rnd, 1);
+        char hash[20];
+        local->hmac(hash, (char *)rnd, 1, key, key_len);
+        memcpy(outdata + 1, hash, 6);
+    }
+
+    memcpy(outdata + 7, encrypt, 24);
+    memcpy(outdata + data_offset, data, datalength);
+
+    {
+        char hash[20];
+        local->hmac(hash, outdata, out_size - 4, local->user_key, local->user_key_len);
+        memmove(outdata + out_size - 4, hash, 4);
+    }
+    free(key);
+
+    return out_size;
+}
+
+int auth_aes128_sha1_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 4096);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    if (len > 0 && local->has_sent_header == 0) {
+        int head_size = 1200;
+        if (head_size > datalength)
+            head_size = datalength;
+        pack_len = auth_aes128_sha1_pack_auth_data((auth_simple_global_data *)self->server.g_data, &self->server, local, data, head_size, buffer);
+        buffer += pack_len;
+        data += head_size;
+        len -= head_size;
+        local->has_sent_header = 1;
+    }
+    while ( len > auth_simple_pack_unit_size ) {
+        pack_len = auth_aes128_sha1_pack_data(data, auth_simple_pack_unit_size, buffer, local, &self->server);
+        buffer += pack_len;
+        data += auth_simple_pack_unit_size;
+        len -= auth_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = auth_aes128_sha1_pack_data(data, len, buffer, local, &self->server);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int auth_aes128_sha1_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    //server_info *server = (server_info*)&self->server;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+        return -1;
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    int key_len = local->user_key_len + 4;
+    uint8_t *key = (uint8_t*)malloc(key_len);
+    memcpy(key, local->user_key, local->user_key_len);
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    char error = 0;
+    while (local->recv_buffer_size > 4) {
+        memintcopy_lt(key + key_len - 4, local->recv_id);
+
+        {
+            char hash[20];
+            local->hmac(hash, (char*)recv_buffer, 2, key, key_len);
+
+            if (memcmp(hash, recv_buffer + 2, 2)) {
+                local->recv_buffer_size = 0;
+                error = 1;
+                break;
+            }
+        }
+
+        int length = ((int)recv_buffer[1] << 8) + recv_buffer[0];
+        if (length >= 8192 || length < 8) {
+            local->recv_buffer_size = 0;
+            error = 1;
+            break;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        {
+            char hash[20];
+            local->hmac(hash, (char *)recv_buffer, length - 4, key, key_len);
+            if (memcmp(hash, recv_buffer + length - 4, 4))
+            {
+                local->recv_buffer_size = 0;
+                error = 1;
+                break;
+            }
+        }
+
+        ++local->recv_id;
+        int pos = recv_buffer[4];
+        if (pos < 255)
+        {
+            pos += 4;
+        }
+        else
+        {
+            pos = (((int)recv_buffer[6] << 8) | recv_buffer[5]) + 4;
+        }
+        int data_size = length - pos - 4;
+        memmove(buffer, recv_buffer + pos, data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len;
+    if (error == 0) {
+        len = buffer - out_buffer;
+        if (*capacity < len) {
+            *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+            plaindata = *pplaindata;
+        }
+        memmove(plaindata, out_buffer, len);
+    } else {
+        len = -1;
+    }
+    free(out_buffer);
+    free(key);
+    return len;
+}
+
+int auth_aes128_sha1_client_udp_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength + 8);
+
+    if (local->user_key == NULL) {
+        if(self->server.param != NULL && self->server.param[0] != 0) {
+            char *param = self->server.param;
+            char *delim = strchr(param, ':');
+            if(delim != NULL) {
+                char uid_str[16] = {};
+                strncpy(uid_str, param, delim - param);
+                char key_str[128];
+                strcpy(key_str, delim + 1);
+                long uid_long = strtol(uid_str, NULL, 10);
+                memintcopy_lt(local->uid, uid_long);
+
+                char hash[21] = {0};
+                local->hash(hash, key_str, strlen(key_str));
+
+                local->user_key_len = local->hash_len;
+                local->user_key = (uint8_t*)malloc(local->user_key_len);
+                memcpy(local->user_key, hash, local->hash_len);
+            }
+        }
+        if (local->user_key == NULL) {
+            rand_bytes((uint8_t *)local->uid, 4);
+
+            local->user_key_len = self->server.key_len;
+            local->user_key = (uint8_t*)malloc(local->user_key_len);
+            memcpy(local->user_key, self->server.key, local->user_key_len);
+        }
+    }
+
+    int outlength = datalength + 8;
+    memmove(out_buffer, plaindata, datalength);
+    memmove(out_buffer + datalength, local->uid, 4);
+
+    {
+        char hash[20];
+        local->hmac(hash, out_buffer, outlength - 4, local->user_key, local->user_key_len);
+        memmove(out_buffer + outlength - 4, hash, 4);
+    }
+
+    if (*capacity < outlength) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = outlength * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, outlength);
+
+    free(out_buffer);
+    return outlength;
+}
+
+int auth_aes128_sha1_client_udp_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity) {
+    if (datalength <= 4)
+        return 0;
+
+    char *plaindata = *pplaindata;
+    auth_simple_local_data *local = (auth_simple_local_data*)self->l_data;
+
+    char hash[20];
+    local->hmac(hash, plaindata, datalength - 4, self->server.key, self->server.key_len);
+
+    if (memcmp(hash, plaindata + datalength - 4, 4))
+    {
+        return 0;
+    }
+
+    return datalength - 4;
+}
diff --git a/server/auth.h b/server/auth.h
new file mode 100644
index 0000000..f7730df
--- /dev/null
+++ b/server/auth.h
@@ -0,0 +1,30 @@
+/*
+ * auth.h - Define shadowsocksR server's buffers and callbacks
+ *
+ * Copyright (C) 2015 - 2016, Break Wa11 <mmgac001@gmail.com>
+ */
+
+#ifndef _AUTH_H
+#define _AUTH_H
+
+void * auth_simple_init_data();
+obfs * auth_simple_new_obfs();
+void auth_simple_dispose(obfs *self);
+
+int auth_simple_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int auth_simple_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+
+int auth_sha1_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int auth_sha1_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+int auth_sha1_v2_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int auth_sha1_v2_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+int auth_sha1_v4_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int auth_sha1_v4_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+int auth_aes128_sha1_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int auth_aes128_sha1_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+#endif // _AUTH_H
diff --git a/server/base64.c b/server/base64.c
new file mode 100644
index 0000000..7cf9552
--- /dev/null
+++ b/server/base64.c
@@ -0,0 +1,119 @@
+#include "base64.h"
+
+/* BASE 64 encode table */
+static const char base64en[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+#define BASE64_PAD  '='
+
+#define BASE64DE_FIRST  '+'
+#define BASE64DE_LAST   'z'
+
+/* ASCII order for BASE 64 decode, -1 in unused character */
+static const signed char base64de[] = {
+        -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+        -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+        -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+        -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+        -1,  -1,  -1,  -1,  -1,  -1,  -1,  -1,
+    /*                '+', ',', '-', '.', '/', */
+        -1,  -1,  -1,  62,  -1,  -1,  -1,  63,
+    /* '0', '1', '2', '3', '4', '5', '6', '7', */
+        52,  53,  54,  55,  56,  57,  58,  59,
+    /* '8', '9', ':', ';', '<', '=', '>', '?', */
+        60,  61,  -1,  -1,  -1,  -1,  -1,  -1,
+    /* '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', */
+        -1,   0,   1,   2,   3,   4,   5,   6,
+    /* 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', */
+         7,   8,   9,  10,  11,  12,  13,  14,
+    /* 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', */
+        15,  16,  17,  18,  19,  20,  21,  22,
+    /* 'X', 'Y', 'Z', '[', '\', ']', '^', '_', */
+        23,  24,  25,  -1,  -1,  -1,  -1,  -1,
+    /* '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', */
+        -1,  26,  27,  28,  29,  30,  31,  32,
+    /* 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', */
+        33,  34,  35,  36,  37,  38,  39,  40,
+    /* 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', */
+        41,  42,  43,  44,  45,  46,  47,  48,
+    /* 'x', 'y', 'z', */
+        49,  50,  51,
+};
+
+int
+base64_encode(const unsigned char *in, unsigned int inlen, char *out)
+{
+    unsigned int i, j;
+
+    for (i = j = 0; i < inlen; i++) {
+        int s = i % 3;          /* from 6/gcd(6, 8) */
+
+        switch (s) {
+        case 0:
+            out[j++] = base64en[(in[i] >> 2) & 0x3F];
+            continue;
+        case 1:
+            out[j++] = base64en[((in[i-1] & 0x3) << 4) + ((in[i] >> 4) & 0xF)];
+            continue;
+        case 2:
+            out[j++] = base64en[((in[i-1] & 0xF) << 2) + ((in[i] >> 6) & 0x3)];
+            out[j++] = base64en[in[i] & 0x3F];
+        }
+    }
+
+    /* move back */
+    i -= 1;
+
+    /* check the last and add padding */
+    if ((i % 3) == 0) {
+        out[j++] = base64en[(in[i] & 0x3) << 4];
+        out[j++] = BASE64_PAD;
+        out[j++] = BASE64_PAD;
+    } else if ((i % 3) == 1) {
+        out[j++] = base64en[(in[i] & 0xF) << 2];
+        out[j++] = BASE64_PAD;
+    }
+
+    return BASE64_OK;
+}
+
+int
+base64_decode(const char *in, unsigned int inlen, unsigned char *out)
+{
+    unsigned int i, j;
+
+    for (i = j = 0; i < inlen; i++) {
+        int c;
+        int s = i % 4;          /* from 8/gcd(6, 8) */
+
+        if (in[i] == '=')
+            return BASE64_OK;
+
+        if (in[i] < BASE64DE_FIRST || in[i] > BASE64DE_LAST ||
+            (c = base64de[(int)in[i]]) == -1)
+            return BASE64_INVALID;
+
+        switch (s) {
+        case 0:
+            out[j] = ((unsigned int)c << 2) & 0xFF;
+            continue;
+        case 1:
+            out[j++] += ((unsigned int)c >> 4) & 0x3;
+
+            /* if not last char with padding */
+            if (i < (inlen - 3) || in[inlen - 2] != '=')
+                out[j] = ((unsigned int)c & 0xF) << 4; 
+            continue;
+        case 2:
+            out[j++] += ((unsigned int)c >> 2) & 0xF;
+
+            /* if not last char with padding */
+            if (i < (inlen - 2) || in[inlen - 1] != '=')
+                out[j] =  ((unsigned int)c & 0x3) << 6;
+            continue;
+        case 3:
+            out[j++] += (unsigned char)c;
+        }
+    }
+
+    return BASE64_OK;
+}
diff --git a/server/base64.h b/server/base64.h
new file mode 100644
index 0000000..6432ba3
--- /dev/null
+++ b/server/base64.h
@@ -0,0 +1,16 @@
+#ifndef __BASE64_H__
+#define __BASE64_H__
+
+enum {BASE64_OK = 0, BASE64_INVALID};
+
+#define BASE64_ENCODE_OUT_SIZE(s)	(((s) + 2) / 3 * 4)
+#define BASE64_DECODE_OUT_SIZE(s)	(((s)) / 4 * 3)
+
+int
+base64_encode(const unsigned char *in, unsigned int inlen, char *out);
+
+int
+base64_decode(const char *in, unsigned int inlen, unsigned char *out);
+
+
+#endif /* __BASE64_H__ */
diff --git a/server/cache.c b/server/cache.c
new file mode 100644
index 0000000..c1a2995
--- /dev/null
+++ b/server/cache.c
@@ -0,0 +1,308 @@
+/*
+ * cache.c - Manage the connection cache for UDPRELAY
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Original Author:  Oliver Lorenz (ol), olli@olorenz.org, https://olorenz.org
+ * License:  This is licensed under the same terms as uthash itself
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+
+#include "cache.h"
+#include "utils.h"
+
+#ifdef __MINGW32__
+#include "win32.h"
+#endif
+
+/** Creates a new cache object
+ *
+ *  @param dst
+ *  Where the newly allocated cache object will be stored in
+ *
+ *  @param capacity
+ *  The maximum number of elements this cache object can hold
+ *
+ *  @return EINVAL if dst is NULL, ENOMEM if malloc fails, 0 otherwise
+ */
+int
+cache_create(struct cache **dst, const size_t capacity,
+             void (*free_cb)(void *key, void *element))
+{
+    struct cache *new = NULL;
+
+    if (!dst) {
+        return EINVAL;
+    }
+
+    if ((new = malloc(sizeof(*new))) == NULL) {
+        return ENOMEM;
+    }
+
+    new->max_entries = capacity;
+    new->entries     = NULL;
+    new->free_cb     = free_cb;
+    *dst             = new;
+    return 0;
+}
+
+/** Frees an allocated cache object
+ *
+ *  @param cache
+ *  The cache object to free
+ *
+ *  @param keep_data
+ *  Whether to free contained data or just delete references to it
+ *
+ *  @return EINVAL if cache is NULL, 0 otherwise
+ */
+int
+cache_delete(struct cache *cache, int keep_data)
+{
+    struct cache_entry *entry, *tmp;
+
+    if (!cache) {
+        return EINVAL;
+    }
+
+    if (keep_data) {
+        HASH_CLEAR(hh, cache->entries);
+    } else {
+        HASH_ITER(hh, cache->entries, entry, tmp){
+            HASH_DEL(cache->entries, entry);
+            if (entry->data != NULL) {
+                if (cache->free_cb) {
+                    cache->free_cb(entry->key, entry->data);
+                } else {
+                    ss_free(entry->data);
+                }
+            }
+            ss_free(entry->key);
+            ss_free(entry);
+        }
+    }
+
+    ss_free(cache);
+    return 0;
+}
+
+/** Clear old cache object
+ *
+ *  @param cache
+ *  The cache object to clear
+ *
+ *  @param age
+ *  Clear only objects older than the age (sec)
+ *
+ *  @return EINVAL if cache is NULL, 0 otherwise
+ */
+int
+cache_clear(struct cache *cache, ev_tstamp age)
+{
+    struct cache_entry *entry, *tmp;
+
+    if (!cache) {
+        return EINVAL;
+    }
+
+    ev_tstamp now = ev_time();
+
+    HASH_ITER(hh, cache->entries, entry, tmp){
+        if (now - entry->ts > age) {
+            HASH_DEL(cache->entries, entry);
+            if (entry->data != NULL) {
+                if (cache->free_cb) {
+                    cache->free_cb(entry->key, entry->data);
+                } else {
+                    ss_free(entry->data);
+                }
+            }
+            ss_free(entry->key);
+            ss_free(entry);
+        }
+    }
+
+    return 0;
+}
+
+/** Removes a cache entry
+ *
+ *  @param cache
+ *  The cache object
+ *
+ *  @param key
+ *  The key of the entry to remove
+ *
+ *  @param key_len
+ *  The length of key
+ *
+ *  @return EINVAL if cache is NULL, 0 otherwise
+ */
+int
+cache_remove(struct cache *cache, char *key, size_t key_len)
+{
+    struct cache_entry *tmp;
+
+    if (!cache || !key) {
+        return EINVAL;
+    }
+
+    HASH_FIND(hh, cache->entries, key, key_len, tmp);
+
+    if (tmp) {
+        HASH_DEL(cache->entries, tmp);
+        if (tmp->data != NULL) {
+            if (cache->free_cb) {
+                cache->free_cb(tmp->key, tmp->data);
+            } else {
+                ss_free(tmp->data);
+            }
+        }
+        ss_free(tmp->key);
+        ss_free(tmp);
+    }
+
+    return 0;
+}
+
+/** Checks if a given key is in the cache
+ *
+ *  @param cache
+ *  The cache object
+ *
+ *  @param key
+ *  The key to look-up
+ *
+ *  @param key_len
+ *  The length of key
+ *
+ *  @param result
+ *  Where to store the result if key is found.
+ *
+ *  A warning: Even though result is just a pointer,
+ *  you have to call this function with a **ptr,
+ *  otherwise this will blow up in your face.
+ *
+ *  @return EINVAL if cache is NULL, 0 otherwise
+ */
+int
+cache_lookup(struct cache *cache, char *key, size_t key_len, void *result)
+{
+    struct cache_entry *tmp = NULL;
+    char **dirty_hack       = result;
+
+    if (!cache || !key || !result) {
+        return EINVAL;
+    }
+
+    HASH_FIND(hh, cache->entries, key, key_len, tmp);
+    if (tmp) {
+        HASH_DELETE(hh, cache->entries, tmp);
+        tmp->ts = ev_time();
+        HASH_ADD_KEYPTR(hh, cache->entries, tmp->key, key_len, tmp);
+        *dirty_hack = tmp->data;
+    } else {
+        *dirty_hack = result = NULL;
+    }
+
+    return 0;
+}
+
+int
+cache_key_exist(struct cache *cache, char *key, size_t key_len)
+{
+    struct cache_entry *tmp = NULL;
+
+    if (!cache || !key) {
+        return 0;
+    }
+
+    HASH_FIND(hh, cache->entries, key, key_len, tmp);
+    if (tmp) {
+        HASH_DELETE(hh, cache->entries, tmp);
+        tmp->ts = ev_time();
+        HASH_ADD_KEYPTR(hh, cache->entries, tmp->key, key_len, tmp);
+        return 1;
+    } else {
+        return 0;
+    }
+
+    return 0;
+}
+
+/** Inserts a given <key, value> pair into the cache
+ *
+ *  @param cache
+ *  The cache object
+ *
+ *  @param key
+ *  The key that identifies <value>
+ *
+ *  @param key_len
+ *  The length of key
+ *
+ *  @param data
+ *  Data associated with <key>
+ *
+ *  @return EINVAL if cache is NULL, ENOMEM if malloc fails, 0 otherwise
+ */
+int
+cache_insert(struct cache *cache, char *key, size_t key_len, void *data)
+{
+    struct cache_entry *entry     = NULL;
+    struct cache_entry *tmp_entry = NULL;
+
+    if (!cache) {
+        return EINVAL;
+    }
+
+    if ((entry = malloc(sizeof(*entry))) == NULL) {
+        return ENOMEM;
+    }
+
+    entry->key = ss_malloc(key_len + 1);
+    memcpy(entry->key, key, key_len);
+    entry->key[key_len] = 0;
+
+    entry->data = data;
+    entry->ts   = ev_time();
+    HASH_ADD_KEYPTR(hh, cache->entries, entry->key, key_len, entry);
+
+    if (HASH_COUNT(cache->entries) >= cache->max_entries) {
+        HASH_ITER(hh, cache->entries, entry, tmp_entry){
+            HASH_DELETE(hh, cache->entries, entry);
+            if (entry->data != NULL) {
+                if (cache->free_cb) {
+                    cache->free_cb(entry->key, entry->data);
+                } else {
+                    ss_free(entry->data);
+                }
+            }
+            ss_free(entry->key);
+            ss_free(entry);
+            break;
+        }
+    }
+
+    return 0;
+}
diff --git a/server/cache.h b/server/cache.h
new file mode 100644
index 0000000..0ec98f5
--- /dev/null
+++ b/server/cache.h
@@ -0,0 +1,62 @@
+/*
+ * cache.h - Define the cache manager interface
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Original Author:  Oliver Lorenz (ol), olli@olorenz.org, https://olorenz.org
+ * License:  This is licensed under the same terms as uthash itself
+ */
+
+#ifndef _CACHE_
+#define _CACHE_
+
+#include "uthash.h"
+#include "ev.h"
+
+/**
+ * A cache entry
+ */
+struct cache_entry {
+    char *key;         /**<The key */
+    void *data;        /**<Payload */
+    ev_tstamp ts;    /**<Timestamp */
+    UT_hash_handle hh; /**<Hash Handle for uthash */
+};
+
+/**
+ * A cache object
+ */
+struct cache {
+    size_t max_entries;              /**<Amount of entries this cache object can hold */
+    struct cache_entry *entries;     /**<Head pointer for uthash */
+    void (*free_cb) (void *key, void *element); /**<Callback function to free cache entries */
+};
+
+int cache_create(struct cache **dst, const size_t capacity,
+                        void (*free_cb)(void *key, void *element));
+int cache_delete(struct cache *cache, int keep_data);
+int cache_clear(struct cache *cache, ev_tstamp age);
+int cache_lookup(struct cache *cache, char *key, size_t key_len, void *result);
+int cache_insert(struct cache *cache, char *key, size_t key_len, void *data);
+int cache_remove(struct cache *cache, char *key, size_t key_len);
+int cache_key_exist(struct cache *cache, char *key, size_t key_len);
+
+#endif
diff --git a/server/check.c b/server/check.c
new file mode 100644
index 0000000..ad035e0
--- /dev/null
+++ b/server/check.c
@@ -0,0 +1,236 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <time.h>
+#include<arpa/inet.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <string.h>
+
+//#define __DEBUG__  
+#ifdef __DEBUG__  
+#define DEBUG(format,...) printf("File: "__FILE__", Line: %05d: "format"/n", __LINE__, ##__VA_ARGS__)  
+#else  
+#define DEBUG(format,...)  
+#endif  
+
+static sigjmp_buf jmpbuf;
+static void alarm_func()
+{
+    siglongjmp(jmpbuf, 1);
+}
+
+static struct hostent *timeGethostbyname(const char *domain, int timeout)
+{
+    struct hostent *ipHostent = NULL;
+    signal(SIGALRM, alarm_func);
+    if(sigsetjmp(jmpbuf, 1) != 0)
+    {
+        alarm(0);//timout
+        signal(SIGALRM, SIG_IGN);
+        return NULL;
+    }
+    alarm(timeout);//setting alarm
+    ipHostent = gethostbyname(domain);
+    signal(SIGALRM, SIG_IGN);
+    return ipHostent;
+}
+
+
+#define MY_HTTP_DEFAULT_PORT 80
+#define BUFFER_SIZE 1024
+#define HTTP_POST "POST /%s HTTP/1.1\r\nHOST: %s:%d\r\nAccept: */*\r\n"\
+    "Content-Type:application/x-www-form-urlencoded\r\nContent-Length: %d\r\n\r\n%s"
+#define HTTP_GET "GET /%s HTTP/1.1\r\nHOST: %s:%d\r\nAccept: */*\r\n\r\n"
+
+static int http_parse_url(const char *url,char *host,char *file,int *port)
+{
+    char *ptr1,*ptr2;
+    int len = 0;
+    if(!url || !host || !file || !port){
+        return 1;
+    }
+
+    ptr1 = (char *)url;
+
+    if(!strncmp(ptr1,"http://",strlen("http://"))){
+        ptr1 += strlen("http://");
+    }else{
+        return 1;
+    }
+
+    ptr2 = strchr(ptr1,'/');
+    if(ptr2){
+        len = strlen(ptr1) - strlen(ptr2);
+        memcpy(host,ptr1,len);
+        host[len] = '\0';
+        if(*(ptr2 + 1)){
+            memcpy(file,ptr2 + 1,strlen(ptr2) - 1 );
+            file[strlen(ptr2) - 1] = '\0';
+        }
+    }else{
+        memcpy(host,ptr1,strlen(ptr1));
+        host[strlen(ptr1)] = '\0';
+    }
+    //get host and ip
+    ptr1 = strchr(host,':');
+    if(ptr1){
+        *ptr1++ = '\0';
+        *port = atoi(ptr1);
+    }else{
+        *port = MY_HTTP_DEFAULT_PORT;
+    }
+
+    return 0;
+}
+
+
+static int http_tcpclient_recv(int socket,char *lpbuff){
+    int recvnum = 0;
+
+    recvnum = recv(socket, lpbuff,BUFFER_SIZE*4,0);
+
+    return recvnum;
+}
+
+static int http_tcpclient_send(int socket,char *buff,int size){
+    int sent=0,tmpres=0;
+
+    while(sent < size){
+        tmpres = send(socket,buff+sent,size-sent,0);
+        if(tmpres == -1){
+            return 1;
+        }
+        sent += tmpres;
+    }
+    return sent;
+}
+
+
+
+
+
+int  http_get(const char *url,int socket_fd)
+{
+    char lpbuf[BUFFER_SIZE*4] = {'\0'};
+
+    char host_addr[BUFFER_SIZE] = {'\0'};
+    char file[BUFFER_SIZE] = {'\0'};
+    int port = 0;
+
+
+    if(!url){
+       DEBUG("      failed!\n");
+        return 1;
+    }
+
+    if(http_parse_url(url,host_addr,file,&port)){
+       DEBUG("http_parse_url failed!\n");
+        return 1;
+    }
+    DEBUG("url:  %s\thost_addr : %s\tfile:%s\t,%d\n",url,host_addr,file,port);
+
+
+    if(socket_fd < 0){
+       DEBUG("http_tcpclient_create failed\n");
+        return 1;
+    }
+
+    sprintf(lpbuf,HTTP_GET,file,host_addr,port);
+
+    if(http_tcpclient_send(socket_fd,lpbuf,strlen(lpbuf)) < 0){
+       DEBUG("http_tcpclient_send failed..\n");
+        return 1;
+    }
+	  DEBUG("request:\n%s\n",lpbuf);
+
+    if(http_tcpclient_recv(socket_fd,lpbuf) <= 0){
+       DEBUG("http_tcpclient_recv failed\n");
+        close(socket_fd);
+        return 1;
+    }
+    DEBUG("rec:\n%s\n",lpbuf);
+    close(socket_fd);
+
+    //return http_parse_result(lpbuf);
+return 0;
+}
+
+
+
+int main(int argc, char *argv[])
+{
+        int fd,http_flag=0,http_ret=1;
+        struct sockaddr_in addr;
+        struct hostent *host;
+        struct timeval timeo = {3, 0};
+        socklen_t len = sizeof(timeo);
+        
+        char http_url[100]="http://";
+
+   
+
+        fd = socket(AF_INET, SOCK_STREAM, 0);
+        if (argc >= 4)
+                 timeo.tv_sec = atoi(argv[3]);
+        if (argc>=5)
+         http_flag=1;
+         
+        if((host=timeGethostbyname(argv[1],timeo.tv_sec)) == NULL) {
+        DEBUG("gethostbyname err\n");
+        return 1;
+    }
+  if (setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &timeo, len) == -1)
+    {
+
+      DEBUG("setsockopt send err\n");
+       return 1;
+    }
+            
+  if (setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &timeo, len) == -1)
+    {
+
+      DEBUG("setsockopt recv err\n");
+       return 1;
+    }
+  
+         addr.sin_family = AF_INET;
+         addr.sin_addr = *((struct in_addr *)host->h_addr);
+         //addr.sin_addr.s_addr = inet_addr(argv[1]);
+         addr.sin_port = htons(atoi(argv[2]));
+if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
+    {  
+      if (errno == EINPROGRESS) 
+       {
+         DEBUG("timeout err\n");
+          return 1;
+        }  
+       DEBUG("connect err\n");
+        return 1;
+    }
+if(http_flag==0)
+{
+ close(fd);
+ return 0;
+}
+strcat(http_url,argv[1]);
+http_ret=http_get(http_url,fd);
+if(http_ret==1)
+{
+DEBUG("recv err");
+ return 1;
+}
+else
+{
+DEBUG("recv ok");
+
+ return 0;
+}
+
+}
\ No newline at end of file
diff --git a/server/common.h b/server/common.h
new file mode 100644
index 0000000..000f084
--- /dev/null
+++ b/server/common.h
@@ -0,0 +1,58 @@
+/*
+ * common.h - Provide global definitions
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _COMMON_H
+#define _COMMON_H
+
+#define DEFAULT_CONF_PATH "/etc/shadowsocks-libev/config.json"
+
+#ifndef SOL_TCP
+#define SOL_TCP IPPROTO_TCP
+#endif
+
+#if defined(MODULE_TUNNEL) || defined(MODULE_REDIR)
+#define MODULE_LOCAL
+#endif
+
+int init_udprelay(const char *server_host, const char *server_port,
+#ifdef MODULE_LOCAL
+                  const struct sockaddr *remote_addr, const int remote_addr_len,
+#ifdef MODULE_TUNNEL
+                  const ss_addr_t tunnel_addr,
+#endif
+#endif
+                  int mtu, int method, int auth, int timeout, const char *iface, const char *protocol, const char *protocol_param);
+
+void free_udprelay(void);
+
+#ifdef ANDROID
+int protect_socket(int fd);
+int send_traffic_stat(uint64_t tx, uint64_t rx);
+#endif
+
+#define STAGE_ERROR     -1  /* Error detected                   */
+#define STAGE_INIT       0  /* Initial stage                    */
+#define STAGE_HANDSHAKE  1  /* Handshake with client            */
+#define STAGE_PARSE      2  /* Parse the header                 */
+#define STAGE_RESOLVE    4  /* Resolve the hostname             */
+#define STAGE_STREAM     5  /* Stream between client and server */
+
+#endif // _COMMON_H
diff --git a/server/crc32.c b/server/crc32.c
new file mode 100644
index 0000000..6d328d2
--- /dev/null
+++ b/server/crc32.c
@@ -0,0 +1,97 @@
+static uint32_t crc32_table[256] = {0};
+
+void init_crc32_table(void) {
+    uint32_t c, i, j;
+    if (crc32_table[0] == 0) {
+        for (i = 0; i < 256; i++) {
+            c = i;
+            for (j = 0; j < 8; j++) {
+                if (c & 1)
+                    c = 0xedb88320L ^ (c >> 1);
+                else
+                    c = c >> 1;
+            }
+            crc32_table[i] = c;
+        }
+    }
+}
+
+uint32_t crc32(unsigned char *buffer, unsigned int size) {
+    uint32_t crc = 0xFFFFFFFF;
+    unsigned int i;
+    for (i = 0; i < size; i++) {
+        crc = crc32_table[(crc ^ buffer[i]) & 0xFF] ^ (crc >> 8);
+    }
+    return crc ^ 0xFFFFFFFF;
+}
+
+void fillcrc32to(unsigned char *buffer, unsigned int size, unsigned char *outbuffer) {
+    uint32_t crc = 0xFFFFFFFF;
+    unsigned int i;
+    for (i = 0; i < size; i++) {
+        crc = crc32_table[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8);
+    }
+    crc ^= 0xFFFFFFFF;
+    outbuffer[0] = crc;
+    outbuffer[1] = crc >> 8;
+    outbuffer[2] = crc >> 16;
+    outbuffer[3] = crc >> 24;
+}
+
+void fillcrc32(unsigned char *buffer, unsigned int size) {
+    uint32_t crc = 0xFFFFFFFF;
+    unsigned int i;
+    size -= 4;
+    for (i = 0; i < size; i++) {
+        crc = crc32_table[(crc ^ buffer[i]) & 0xff] ^ (crc >> 8);
+    }
+    buffer += size;
+    buffer[0] = crc;
+    buffer[1] = crc >> 8;
+    buffer[2] = crc >> 16;
+    buffer[3] = crc >> 24;
+}
+
+void adler32_short(unsigned char *buffer, unsigned int size, uint32_t *a, uint32_t *b) {
+    for (int i = 0; i < size; i++) {
+        *a += buffer[i];
+        *b += *a;
+    }
+    *a %= 65521;
+    *b %= 65521;
+}
+
+#define NMAX 5552
+uint32_t adler32(unsigned char *buffer, unsigned int size) {
+    uint32_t a = 1;
+    uint32_t b = 0;
+    while ( size >= NMAX ) {
+        adler32_short(buffer, NMAX, &a, &b);
+        buffer += NMAX;
+        size -= NMAX;
+    }
+    adler32_short(buffer, size, &a, &b);
+    return (b << 16) + a;
+}
+#undef NMAX
+
+void filladler32(unsigned char *buffer, unsigned int size) {
+    size -= 4;
+    uint32_t checksum = adler32(buffer, size);
+    buffer += size;
+    buffer[0] = checksum;
+    buffer[1] = checksum >> 8;
+    buffer[2] = checksum >> 16;
+    buffer[3] = checksum >> 24;
+}
+
+int checkadler32(unsigned char *buffer, unsigned int size) {
+    size -= 4;
+    uint32_t checksum = adler32(buffer, size);
+    buffer += size;
+    return checksum == (((uint32_t)buffer[3] << 24)
+            | ((uint32_t)buffer[2] << 16)
+            | ((uint32_t)buffer[1] << 8)
+            | (uint32_t)buffer[0]);
+}
+
diff --git a/server/encrypt.c b/server/encrypt.c
new file mode 100644
index 0000000..37dd5cd
--- /dev/null
+++ b/server/encrypt.c
@@ -0,0 +1,1645 @@
+/*
+ * encrypt.c - Manage the global encryptor
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdint.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(USE_CRYPTO_OPENSSL)
+
+#include <openssl/md5.h>
+#include <openssl/rand.h>
+#include <openssl/hmac.h>
+#include <openssl/aes.h>
+
+#elif defined(USE_CRYPTO_POLARSSL)
+
+#include <polarssl/md5.h>
+#include <polarssl/sha1.h>
+#include <polarssl/aes.h>
+#include <polarssl/entropy.h>
+#include <polarssl/ctr_drbg.h>
+#include <polarssl/version.h>
+#define CIPHER_UNSUPPORTED "unsupported"
+
+#include <time.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <wincrypt.h>
+#else
+#include <stdio.h>
+#endif
+
+#elif defined(USE_CRYPTO_MBEDTLS)
+
+#include <mbedtls/md5.h>
+#include <mbedtls/entropy.h>
+#include <mbedtls/ctr_drbg.h>
+#include <mbedtls/version.h>
+#include <mbedtls/aes.h>
+#define CIPHER_UNSUPPORTED "unsupported"
+
+#include <time.h>
+#ifdef _WIN32
+#include <windows.h>
+#include <wincrypt.h>
+#else
+#include <stdio.h>
+#endif
+
+#endif
+
+#include <sodium.h>
+
+#ifndef __MINGW32__
+#include <arpa/inet.h>
+#endif
+
+#include "cache.h"
+#include "encrypt.h"
+#include "utils.h"
+
+#define OFFSET_ROL(p, o) ((uint64_t)(*(p + o)) << (8 * o))
+
+static uint8_t *enc_table;
+static uint8_t *dec_table;
+static uint8_t enc_key[MAX_KEY_LENGTH];
+static int enc_key_len;
+static int enc_iv_len;
+static int enc_method;
+
+static struct cache *iv_cache;
+
+#ifdef DEBUG
+static void
+dump(char *tag, char *text, int len)
+{
+    int i;
+    printf("%s: ", tag);
+    for (i = 0; i < len; i++)
+        printf("0x%02x ", (uint8_t)text[i]);
+    printf("\n");
+}
+
+#endif
+
+static const char *supported_ciphers[CIPHER_NUM] = {
+    "table",
+    "rc4",
+    "rc4-md5-6",
+    "rc4-md5",
+    "aes-128-cfb",
+    "aes-192-cfb",
+    "aes-256-cfb",
+    "aes-128-ctr",
+    "aes-192-ctr",
+    "aes-256-ctr",
+    "bf-cfb",
+    "camellia-128-cfb",
+    "camellia-192-cfb",
+    "camellia-256-cfb",
+    "cast5-cfb",
+    "des-cfb",
+    "idea-cfb",
+    "rc2-cfb",
+    "seed-cfb",
+    "salsa20",
+    "chacha20",
+    "chacha20-ietf"
+};
+
+#ifdef USE_CRYPTO_POLARSSL
+static const char *supported_ciphers_polarssl[CIPHER_NUM] = {
+    "table",
+    "ARC4-128",
+    "ARC4-128",
+    "ARC4-128",
+    "AES-128-CFB128",
+    "AES-192-CFB128",
+    "AES-256-CFB128",
+    "AES-128-CTR",
+    "AES-192-CTR",
+    "AES-256-CTR",
+    "BLOWFISH-CFB64",
+    "CAMELLIA-128-CFB128",
+    "CAMELLIA-192-CFB128",
+    "CAMELLIA-256-CFB128",
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    "salsa20",
+    "chacha20",
+    "chacha20-ietf"
+};
+#endif
+
+#ifdef USE_CRYPTO_MBEDTLS
+static const char *supported_ciphers_mbedtls[CIPHER_NUM] = {
+    "table",
+    "ARC4-128",
+    "ARC4-128",
+    "ARC4-128",
+    "AES-128-CFB128",
+    "AES-192-CFB128",
+    "AES-256-CFB128",
+    "AES-128-CTR",
+    "AES-192-CTR",
+    "AES-256-CTR",
+    "BLOWFISH-CFB64",
+    "CAMELLIA-128-CFB128",
+    "CAMELLIA-192-CFB128",
+    "CAMELLIA-256-CFB128",
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    CIPHER_UNSUPPORTED,
+    "salsa20",
+    "chacha20",
+    "chacha20-ietf"
+};
+#endif
+
+#ifdef USE_CRYPTO_APPLECC
+static const CCAlgorithm supported_ciphers_applecc[CIPHER_NUM] = {
+    kCCAlgorithmInvalid,
+    kCCAlgorithmRC4,
+    kCCAlgorithmRC4,
+    kCCAlgorithmRC4,
+    kCCAlgorithmAES,
+    kCCAlgorithmAES,
+    kCCAlgorithmAES,
+    kCCAlgorithmAES,
+    kCCAlgorithmAES,
+    kCCAlgorithmAES,
+    kCCAlgorithmBlowfish,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmCAST,
+    kCCAlgorithmDES,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmRC2,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid
+};
+
+static const CCMode supported_modes_applecc[CIPHER_NUM] = {
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCModeRC4,
+    kCCModeRC4,
+    kCCModeCFB,
+    kCCModeCFB,
+    kCCModeCFB,
+    kCCModeCTR,
+    kCCModeCTR,
+    kCCModeCTR,
+    kCCModeCFB,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCModeCFB,
+    kCCModeCFB,
+    kCCModeCFB,
+    kCCModeCFB,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid,
+    kCCAlgorithmInvalid
+};
+#endif
+
+static const int supported_ciphers_iv_size[CIPHER_NUM] = {
+    0,  0,  6, 16, 16, 16, 16, 16, 16, 16,  8, 16, 16, 16,  8,  8,  8,  8, 16,  8,  8, 12
+};
+
+static const int supported_ciphers_key_size[CIPHER_NUM] = {
+    0, 16, 16, 16, 16, 24, 32, 16, 24, 32, 16, 16, 24, 32, 16,  8, 16, 16, 16, 32, 32, 32
+};
+
+static int
+safe_memcmp(const void *s1, const void *s2, size_t n)
+{
+    const unsigned char *_s1 = (const unsigned char *)s1;
+    const unsigned char *_s2 = (const unsigned char *)s2;
+    int ret                  = 0;
+    size_t i;
+    for (i = 0; i < n; i++)
+        ret |= _s1[i] ^ _s2[i];
+    return !!ret;
+}
+
+int
+balloc(buffer_t *ptr, size_t capacity)
+{
+    sodium_memzero(ptr, sizeof(buffer_t));
+    ptr->array    = ss_malloc(capacity);
+    ptr->capacity = capacity;
+    return capacity;
+}
+
+int
+brealloc(buffer_t *ptr, size_t len, size_t capacity)
+{
+    if (ptr == NULL)
+        return -1;
+    size_t real_capacity = max(len, capacity);
+    if (ptr->capacity < real_capacity) {
+        ptr->array    = ss_realloc(ptr->array, real_capacity);
+        ptr->capacity = real_capacity;
+    }
+    return real_capacity;
+}
+
+void
+bfree(buffer_t *ptr)
+{
+    if (ptr == NULL)
+        return;
+    ptr->idx      = 0;
+    ptr->len      = 0;
+    ptr->capacity = 0;
+    if (ptr->array != NULL) {
+        ss_free(ptr->array);
+    }
+}
+
+static int
+crypto_stream_xor_ic(uint8_t *c, const uint8_t *m, uint64_t mlen,
+                     const uint8_t *n, uint64_t ic, const uint8_t *k,
+                     int method)
+{
+    switch (method) {
+    case SALSA20:
+        return crypto_stream_salsa20_xor_ic(c, m, mlen, n, ic, k);
+    case CHACHA20:
+        return crypto_stream_chacha20_xor_ic(c, m, mlen, n, ic, k);
+    case CHACHA20IETF:
+        return crypto_stream_chacha20_ietf_xor_ic(c, m, mlen, n, (uint32_t)ic, k);
+    }
+    // always return 0
+    return 0;
+}
+
+static int
+random_compare(const void *_x, const void *_y, uint32_t i,
+               uint64_t a)
+{
+    uint8_t x = *((uint8_t *)_x);
+    uint8_t y = *((uint8_t *)_y);
+    return a % (x + i) - a % (y + i);
+}
+
+static void
+merge(uint8_t *left, int llength, uint8_t *right,
+      int rlength, uint32_t salt, uint64_t key)
+{
+    uint8_t *ltmp = (uint8_t *)malloc(llength * sizeof(uint8_t));
+    uint8_t *rtmp = (uint8_t *)malloc(rlength * sizeof(uint8_t));
+
+    uint8_t *ll = ltmp;
+    uint8_t *rr = rtmp;
+
+    uint8_t *result = left;
+
+    memcpy(ltmp, left, llength * sizeof(uint8_t));
+    memcpy(rtmp, right, rlength * sizeof(uint8_t));
+
+    while (llength > 0 && rlength > 0) {
+        if (random_compare(ll, rr, salt, key) <= 0) {
+            *result = *ll;
+            ++ll;
+            --llength;
+        } else {
+            *result = *rr;
+            ++rr;
+            --rlength;
+        }
+        ++result;
+    }
+
+    if (llength > 0) {
+        while (llength > 0) {
+            *result = *ll;
+            ++result;
+            ++ll;
+            --llength;
+        }
+    } else {
+        while (rlength > 0) {
+            *result = *rr;
+            ++result;
+            ++rr;
+            --rlength;
+        }
+    }
+
+    ss_free(ltmp);
+    ss_free(rtmp);
+}
+
+static void
+merge_sort(uint8_t array[], int length,
+           uint32_t salt, uint64_t key)
+{
+    uint8_t middle;
+    uint8_t *left, *right;
+    int llength;
+
+    if (length <= 1) {
+        return;
+    }
+
+    middle = length / 2;
+
+    llength = length - middle;
+
+    left  = array;
+    right = array + llength;
+
+    merge_sort(left, llength, salt, key);
+    merge_sort(right, middle, salt, key);
+    merge(left, llength, right, middle, salt, key);
+}
+
+int
+enc_get_iv_len()
+{
+    return enc_iv_len;
+}
+
+uint8_t* enc_get_key()
+{
+    return enc_key;
+}
+
+int enc_get_key_len()
+{
+    return enc_key_len;
+}
+
+unsigned char *enc_md5(const unsigned char *d, size_t n, unsigned char *md)
+{
+#if defined(USE_CRYPTO_OPENSSL)
+    return MD5(d, n, md);
+#elif defined(USE_CRYPTO_POLARSSL)
+    static unsigned char m[16];
+    if (md == NULL) {
+        md = m;
+    }
+    md5(d, n, md);
+    return md;
+#elif defined(USE_CRYPTO_MBEDTLS)
+    static unsigned char m[16];
+    if (md == NULL) {
+        md = m;
+    }
+    mbedtls_md5(d, n, md);
+    return md;
+#endif
+}
+
+void
+enc_table_init(const char *pass)
+{
+    uint32_t i;
+    uint64_t key = 0;
+    uint8_t *digest;
+
+    enc_table = ss_malloc(256);
+    dec_table = ss_malloc(256);
+
+    digest = enc_md5((const uint8_t *)pass, strlen(pass), NULL);
+
+    for (i = 0; i < 8; i++)
+        key += OFFSET_ROL(digest, i);
+
+    for (i = 0; i < 256; ++i)
+        enc_table[i] = i;
+    for (i = 1; i < 1024; ++i)
+        merge_sort(enc_table, 256, i, key);
+    for (i = 0; i < 256; ++i)
+        // gen decrypt table from encrypt table
+        dec_table[enc_table[i]] = i;
+}
+
+int
+cipher_iv_size(const cipher_t *cipher)
+{
+#if defined(USE_CRYPTO_OPENSSL)
+    if (cipher->info == NULL)
+        return cipher->iv_len;
+    else
+        return EVP_CIPHER_iv_length(cipher->info);
+#elif defined(USE_CRYPTO_POLARSSL) || defined(USE_CRYPTO_MBEDTLS)
+    if (cipher == NULL) {
+        return 0;
+    }
+    return cipher->info->iv_size;
+#endif
+}
+
+int
+cipher_key_size(const cipher_t *cipher)
+{
+#if defined(USE_CRYPTO_OPENSSL)
+    if (cipher->info == NULL)
+        return cipher->key_len;
+    else
+        return EVP_CIPHER_key_length(cipher->info);
+#elif defined(USE_CRYPTO_POLARSSL)
+    if (cipher == NULL) {
+        return 0;
+    }
+    /* Override PolarSSL 32 bit default key size with sane 128 bit default */
+    if (cipher->info->base != NULL && POLARSSL_CIPHER_ID_BLOWFISH ==
+        cipher->info->base->cipher) {
+        return 128 / 8;
+    }
+    return cipher->info->key_length / 8;
+#elif defined(USE_CRYPTO_MBEDTLS)
+    /*
+     * Semi-API changes (technically public, morally private)
+     * Renamed a few headers to include _internal in the name. Those headers are
+     * not supposed to be included by users.
+     * Changed md_info_t into an opaque structure (use md_get_xxx() accessors).
+     * Changed pk_info_t into an opaque structure.
+     * Changed cipher_base_t into an opaque structure.
+     */
+    if (cipher == NULL) {
+        return 0;
+    }
+    /* From Version 1.2.7 released 2013-04-13 Default Blowfish keysize is now 128-bits */
+    return cipher->info->key_bitlen / 8;
+#endif
+}
+
+void
+bytes_to_key_with_size(const char *pass, size_t len, uint8_t *md, size_t md_size)
+{
+    uint8_t result[128];
+    enc_md5((const unsigned char *)pass, len, result);
+    memcpy(md, result, 16);
+    int i = 16;
+    for (; i < md_size; i += 16) {
+        memcpy(result + 16, pass, len);
+        enc_md5(result, 16 + len, result);
+        memcpy(md + i, result, 16);
+    }
+}
+
+int
+bytes_to_key(const cipher_t *cipher, const digest_type_t *md,
+             const uint8_t *pass, uint8_t *key)
+{
+    size_t datal;
+    datal = strlen((const char *)pass);
+
+#if defined(USE_CRYPTO_OPENSSL)
+
+    MD5_CTX c;
+    unsigned char md_buf[MAX_MD_SIZE];
+    int nkey;
+    int addmd;
+    unsigned int i, j, mds;
+
+    mds  = 16;
+    nkey = cipher_key_size(cipher);
+    if (pass == NULL)
+        return nkey;
+    memset(&c, 0, sizeof(MD5_CTX));
+
+    for (j = 0, addmd = 0; j < nkey; addmd++) {
+        MD5_Init(&c);
+        if (addmd) {
+            MD5_Update(&c, md_buf, mds);
+        }
+        MD5_Update(&c, pass, datal);
+        MD5_Final(md_buf, &c);
+
+        for (i = 0; i < mds; i++, j++) {
+            if (j >= nkey)
+                break;
+            key[j] = md_buf[i];
+        }
+    }
+
+    return nkey;
+
+#elif defined(USE_CRYPTO_POLARSSL)
+    md_context_t c;
+    unsigned char md_buf[MAX_MD_SIZE];
+    int nkey;
+    int addmd;
+    unsigned int i, j, mds;
+
+    nkey = cipher_key_size(cipher);
+    mds  = md_get_size(md);
+    memset(&c, 0, sizeof(md_context_t));
+
+    if (pass == NULL)
+        return nkey;
+    if (md_init_ctx(&c, md))
+        return 0;
+
+    for (j = 0, addmd = 0; j < nkey; addmd++) {
+        md_starts(&c);
+        if (addmd) {
+            md_update(&c, md_buf, mds);
+        }
+        md_update(&c, pass, datal);
+        md_finish(&c, md_buf);
+
+        for (i = 0; i < mds; i++, j++) {
+            if (j >= nkey)
+                break;
+            key[j] = md_buf[i];
+        }
+    }
+
+    md_free_ctx(&c);
+    return nkey;
+
+#elif defined(USE_CRYPTO_MBEDTLS)
+
+    mbedtls_md_context_t c;
+    unsigned char md_buf[MAX_MD_SIZE];
+    int nkey;
+    int addmd;
+    unsigned int i, j, mds;
+
+    nkey = cipher_key_size(cipher);
+    mds  = mbedtls_md_get_size(md);
+    memset(&c, 0, sizeof(mbedtls_md_context_t));
+
+    if (pass == NULL)
+        return nkey;
+    if (mbedtls_md_setup(&c, md, 1))
+        return 0;
+
+    for (j = 0, addmd = 0; j < nkey; addmd++) {
+        mbedtls_md_starts(&c);
+        if (addmd) {
+            mbedtls_md_update(&c, md_buf, mds);
+        }
+        mbedtls_md_update(&c, pass, datal);
+        mbedtls_md_finish(&c, &(md_buf[0]));
+
+        for (i = 0; i < mds; i++, j++) {
+            if (j >= nkey)
+                break;
+            key[j] = md_buf[i];
+        }
+    }
+
+    mbedtls_md_free(&c);
+    return nkey;
+#endif
+}
+
+int
+rand_bytes(uint8_t *output, int len)
+{
+    randombytes_buf(output, len);
+    // always return success
+    return 0;
+}
+
+const cipher_kt_t *
+get_cipher_type(int method)
+{
+    if (method <= TABLE || method >= CIPHER_NUM) {
+        LOGE("get_cipher_type(): Illegal method");
+        return NULL;
+    }
+
+    if (method == RC4_MD5 || method == RC4_MD5_6) {
+        method = RC4;
+    }
+
+    if (method >= SALSA20) {
+        return NULL;
+    }
+
+    const char *ciphername = supported_ciphers[method];
+#if defined(USE_CRYPTO_OPENSSL)
+    return EVP_get_cipherbyname(ciphername);
+#elif defined(USE_CRYPTO_POLARSSL)
+    const char *polarname = supported_ciphers_polarssl[method];
+    if (strcmp(polarname, CIPHER_UNSUPPORTED) == 0) {
+        LOGE("Cipher %s currently is not supported by PolarSSL library",
+             ciphername);
+        return NULL;
+    }
+    return cipher_info_from_string(polarname);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    const char *mbedtlsname = supported_ciphers_mbedtls[method];
+    if (strcmp(mbedtlsname, CIPHER_UNSUPPORTED) == 0) {
+        LOGE("Cipher %s currently is not supported by mbed TLS library",
+             ciphername);
+        return NULL;
+    }
+    return mbedtls_cipher_info_from_string(mbedtlsname);
+#endif
+}
+
+const digest_type_t *
+get_digest_type(const char *digest)
+{
+    if (digest == NULL) {
+        LOGE("get_digest_type(): Digest name is null");
+        return NULL;
+    }
+
+#if defined(USE_CRYPTO_OPENSSL)
+    return EVP_get_digestbyname(digest);
+#elif defined(USE_CRYPTO_POLARSSL)
+    return md_info_from_string(digest);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    return mbedtls_md_info_from_string(digest);
+#endif
+}
+
+void
+cipher_context_init(cipher_ctx_t *ctx, int method, int enc)
+{
+    if (method <= TABLE || method >= CIPHER_NUM) {
+        LOGE("cipher_context_init(): Illegal method");
+        return;
+    }
+
+    if (method >= SALSA20) {
+        enc_iv_len = supported_ciphers_iv_size[method];
+        return;
+    }
+
+    const char *ciphername = supported_ciphers[method];
+#if defined(USE_CRYPTO_APPLECC)
+    cipher_cc_t *cc = &ctx->cc;
+    cc->cryptor = NULL;
+    cc->cipher  = supported_ciphers_applecc[method];
+    if (cc->cipher == kCCAlgorithmInvalid) {
+        cc->valid = kCCContextInvalid;
+    } else {
+        cc->valid = kCCContextValid;
+        if (cc->cipher == kCCAlgorithmRC4) {
+            cc->mode    = supported_modes_applecc[method];
+            cc->padding = ccNoPadding;
+        } else {
+            cc->mode = supported_modes_applecc[method];
+            if (cc->mode == kCCModeCTR) {
+                cc->padding = ccNoPadding;
+            } else {
+                cc->padding = ccPKCS7Padding;
+            }
+        }
+        return;
+    }
+#endif
+
+    const cipher_kt_t *cipher = get_cipher_type(method);
+
+#if defined(USE_CRYPTO_OPENSSL)
+    ctx->evp = EVP_CIPHER_CTX_new();
+    cipher_evp_t *evp = ctx->evp;
+
+    if (cipher == NULL) {
+        LOGE("Cipher %s not found in OpenSSL library", ciphername);
+        FATAL("Cannot initialize cipher");
+    }
+    if (!EVP_CipherInit_ex(evp, cipher, NULL, NULL, NULL, enc)) {
+        LOGE("Cannot initialize cipher %s", ciphername);
+        exit(EXIT_FAILURE);
+    }
+    if (!EVP_CIPHER_CTX_set_key_length(evp, enc_key_len)) {
+        EVP_CIPHER_CTX_cleanup(evp);
+        LOGE("Invalid key length: %d", enc_key_len);
+        exit(EXIT_FAILURE);
+    }
+    if (method > RC4_MD5) {
+        EVP_CIPHER_CTX_set_padding(evp, 1);
+    }
+#elif defined(USE_CRYPTO_POLARSSL)
+    ctx->evp = (cipher_evp_t *)ss_malloc(sizeof(cipher_evp_t));
+    cipher_evp_t *evp = ctx->evp;
+
+    if (cipher == NULL) {
+        LOGE("Cipher %s not found in PolarSSL library", ciphername);
+        FATAL("Cannot initialize PolarSSL cipher");
+    }
+    if (cipher_init_ctx(evp, cipher) != 0) {
+        FATAL("Cannot initialize PolarSSL cipher context");
+    }
+#elif defined(USE_CRYPTO_MBEDTLS)
+    ctx->evp = (cipher_evp_t *)ss_malloc(sizeof(cipher_evp_t));
+    cipher_evp_t *evp = ctx->evp;
+
+    if (cipher == NULL) {
+        LOGE("Cipher %s not found in mbed TLS library", ciphername);
+        FATAL("Cannot initialize mbed TLS cipher");
+    }
+    mbedtls_cipher_init(evp);
+    if (mbedtls_cipher_setup(evp, cipher) != 0) {
+        FATAL("Cannot initialize mbed TLS cipher context");
+    }
+#endif
+}
+
+void
+cipher_context_set_iv(cipher_ctx_t *ctx, uint8_t *iv, size_t iv_len,
+                      int enc)
+{
+    const unsigned char *true_key;
+
+    if (iv == NULL) {
+        LOGE("cipher_context_set_iv(): IV is null");
+        return;
+    }
+
+    if (!enc) {
+        memcpy(ctx->iv, iv, iv_len);
+    }
+
+    if (enc_method >= SALSA20) {
+        return;
+    }
+
+    if (enc_method == RC4_MD5 || enc_method == RC4_MD5_6) {
+        unsigned char key_iv[32];
+        memcpy(key_iv, enc_key, 16);
+        memcpy(key_iv + 16, iv, iv_len);
+        true_key = enc_md5(key_iv, 16 + iv_len, NULL);
+        iv_len   = 0;
+    } else {
+        true_key = enc_key;
+    }
+
+#ifdef USE_CRYPTO_APPLECC
+    cipher_cc_t *cc = &ctx->cc;
+    if (cc->valid == kCCContextValid) {
+        memcpy(cc->iv, iv, iv_len);
+        memcpy(cc->key, true_key, enc_key_len);
+        cc->iv_len  = iv_len;
+        cc->key_len = enc_key_len;
+        cc->encrypt = enc ? kCCEncrypt : kCCDecrypt;
+        if (cc->cryptor != NULL) {
+            CCCryptorRelease(cc->cryptor);
+            cc->cryptor = NULL;
+        }
+
+        CCCryptorStatus ret;
+        ret = CCCryptorCreateWithMode(
+            cc->encrypt,
+            cc->mode,
+            cc->cipher,
+            cc->padding,
+            cc->iv, cc->key, cc->key_len,
+            NULL, 0, 0, kCCModeOptionCTR_BE,
+            &cc->cryptor);
+        if (ret != kCCSuccess) {
+            if (cc->cryptor != NULL) {
+                CCCryptorRelease(cc->cryptor);
+                cc->cryptor = NULL;
+            }
+            FATAL("Cannot set CommonCrypto key and IV");
+        }
+        return;
+    }
+#endif
+
+    cipher_evp_t *evp = ctx->evp;
+    if (evp == NULL) {
+        LOGE("cipher_context_set_iv(): Cipher context is null");
+        return;
+    }
+#if defined(USE_CRYPTO_OPENSSL)
+    if (!EVP_CipherInit_ex(evp, NULL, NULL, true_key, iv, enc)) {
+        EVP_CIPHER_CTX_cleanup(evp);
+        FATAL("Cannot set key and IV");
+    }
+#elif defined(USE_CRYPTO_POLARSSL)
+    // XXX: PolarSSL 1.3.11: cipher_free_ctx deprecated, Use cipher_free() instead.
+    if (cipher_setkey(evp, true_key, enc_key_len * 8, enc) != 0) {
+        cipher_free_ctx(evp);
+        FATAL("Cannot set PolarSSL cipher key");
+    }
+#if POLARSSL_VERSION_NUMBER >= 0x01030000
+    if (cipher_set_iv(evp, iv, iv_len) != 0) {
+        cipher_free_ctx(evp);
+        FATAL("Cannot set PolarSSL cipher IV");
+    }
+    if (cipher_reset(evp) != 0) {
+        cipher_free_ctx(evp);
+        FATAL("Cannot finalize PolarSSL cipher context");
+    }
+#else
+    if (cipher_reset(evp, iv) != 0) {
+        cipher_free_ctx(evp);
+        FATAL("Cannot set PolarSSL cipher IV");
+    }
+#endif
+#elif defined(USE_CRYPTO_MBEDTLS)
+    if (mbedtls_cipher_setkey(evp, true_key, enc_key_len * 8, enc) != 0) {
+        mbedtls_cipher_free(evp);
+        FATAL("Cannot set mbed TLS cipher key");
+    }
+
+    if (mbedtls_cipher_set_iv(evp, iv, iv_len) != 0) {
+        mbedtls_cipher_free(evp);
+        FATAL("Cannot set mbed TLS cipher IV");
+    }
+    if (mbedtls_cipher_reset(evp) != 0) {
+        mbedtls_cipher_free(evp);
+        FATAL("Cannot finalize mbed TLS cipher context");
+    }
+#endif
+
+#ifdef DEBUG
+    dump("IV", (char *)iv, iv_len);
+#endif
+}
+
+void
+cipher_context_release(cipher_ctx_t *ctx)
+{
+    if (enc_method >= SALSA20) {
+        return;
+    }
+
+#ifdef USE_CRYPTO_APPLECC
+    cipher_cc_t *cc = &ctx->cc;
+    if (cc->cryptor != NULL) {
+        CCCryptorRelease(cc->cryptor);
+        cc->cryptor = NULL;
+    }
+    if (cc->valid == kCCContextValid) {
+        return;
+    }
+#endif
+
+#if defined(USE_CRYPTO_OPENSSL)
+    EVP_CIPHER_CTX_free(ctx->evp);
+#elif defined(USE_CRYPTO_POLARSSL)
+// NOTE: cipher_free_ctx deprecated in PolarSSL 1.3.11
+    cipher_free_ctx(ctx->evp);
+    ss_free(ctx->evp);
+#elif defined(USE_CRYPTO_MBEDTLS)
+// NOTE: cipher_free_ctx deprecated
+    mbedtls_cipher_free(ctx->evp);
+    ss_free(ctx->evp);
+#endif
+}
+
+static int
+cipher_context_update(cipher_ctx_t *ctx, uint8_t *output, size_t *olen,
+                      const uint8_t *input, size_t ilen)
+{
+#ifdef USE_CRYPTO_APPLECC
+    cipher_cc_t *cc = &ctx->cc;
+    if (cc->valid == kCCContextValid) {
+        CCCryptorStatus ret;
+        ret = CCCryptorUpdate(cc->cryptor, input, ilen, output,
+                              ilen, olen);
+        return (ret == kCCSuccess) ? 1 : 0;
+    }
+#endif
+    cipher_evp_t *evp = ctx->evp;
+#if defined(USE_CRYPTO_OPENSSL)
+    int err = 0, tlen = *olen;
+    err = EVP_CipherUpdate(evp, (uint8_t *)output, &tlen,
+                           (const uint8_t *)input, ilen);
+    *olen = tlen;
+    return err;
+#elif defined(USE_CRYPTO_POLARSSL)
+    return !cipher_update(evp, (const uint8_t *)input, ilen,
+                          (uint8_t *)output, olen);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    return !mbedtls_cipher_update(evp, (const uint8_t *)input, ilen,
+                                  (uint8_t *)output, olen);
+#endif
+}
+int ss_md5_hmac(char *auth, char *msg, int msg_len, uint8_t *iv)
+{
+    uint8_t hash[MD5_BYTES];
+    uint8_t auth_key[MAX_IV_LENGTH + MAX_KEY_LENGTH];
+    memcpy(auth_key, iv, enc_iv_len);
+    memcpy(auth_key + enc_iv_len, enc_key, enc_key_len);
+
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_md5(), auth_key, enc_iv_len + enc_key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5), auth_key, enc_iv_len + enc_key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#else
+    md5_hmac(auth_key, enc_iv_len + enc_key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#endif
+
+    memcpy(auth, hash, MD5_BYTES);
+
+    return 0;
+}
+
+int ss_md5_hmac_with_key(char *auth, char *msg, int msg_len, uint8_t *auth_key, int key_len)
+{
+    uint8_t hash[MD5_BYTES];
+
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_md5(), auth_key, key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_MD5), auth_key, key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#else
+    md5_hmac(auth_key, key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#endif
+
+    memcpy(auth, hash, MD5_BYTES);
+
+    return 0;
+}
+
+int ss_md5_hash_func(char *auth, char *msg, int msg_len)
+{
+    uint8_t hash[MD5_BYTES];
+
+#if defined(USE_CRYPTO_OPENSSL)
+    MD5((uint8_t *)msg, msg_len, (uint8_t *)hash);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_MD5), (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#else
+    md5((uint8_t *)msg, msg_len, (uint8_t *)hash);
+#endif
+
+    memcpy(auth, hash, MD5_BYTES);
+
+    return 0;
+}
+
+int ss_sha1_hmac(char *auth, char *msg, int msg_len, uint8_t *iv)
+{
+    uint8_t hash[SHA1_BYTES];
+    uint8_t auth_key[MAX_IV_LENGTH + MAX_KEY_LENGTH];
+    memcpy(auth_key, iv, enc_iv_len);
+    memcpy(auth_key + enc_iv_len, enc_key, enc_key_len);
+
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_sha1(), auth_key, enc_iv_len + enc_key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), auth_key, enc_iv_len + enc_key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#else
+    sha1_hmac(auth_key, enc_iv_len + enc_key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#endif
+
+    memcpy(auth, hash, SHA1_BYTES);
+
+    return 0;
+}
+
+int ss_sha1_hmac_with_key(char *auth, char *msg, int msg_len, uint8_t *auth_key, int key_len)
+{
+    uint8_t hash[SHA1_BYTES];
+
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_sha1(), auth_key, key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), auth_key, key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#else
+    sha1_hmac(auth_key, key_len, (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#endif
+
+    memcpy(auth, hash, SHA1_BYTES);
+
+    return 0;
+}
+
+int ss_sha1_hash_func(char *auth, char *msg, int msg_len)
+{
+    uint8_t hash[SHA1_BYTES];
+#if defined(USE_CRYPTO_OPENSSL)
+    SHA1((uint8_t *)msg, msg_len, (uint8_t *)hash);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), (uint8_t *)msg, msg_len, (uint8_t *)hash);
+#else
+    sha1((uint8_t *)msg, msg_len, (uint8_t *)hash);
+#endif
+
+    memcpy(auth, hash, SHA1_BYTES);
+
+    return 0;
+}
+
+int ss_aes_128_cbc(char *encrypt, char *out_data, char *key)
+{
+    unsigned char iv[16] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
+
+#if defined(USE_CRYPTO_OPENSSL)
+    AES_KEY aes;
+    AES_set_encrypt_key((unsigned char*)key, 128, &aes);
+    AES_cbc_encrypt((const unsigned char *)encrypt, (unsigned char *)out_data, 16, &aes, iv, AES_ENCRYPT);
+
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_aes_context aes;
+
+    unsigned char output[16];
+
+    mbedtls_aes_setkey_enc( &aes, (unsigned char *)key, 128 );
+    mbedtls_aes_crypt_cbc( &aes, MBEDTLS_AES_ENCRYPT, 16, iv, (unsigned char *)encrypt, output );
+
+    memcpy(out_data, output, 16);
+#else
+
+    aes_context aes;
+
+    unsigned char output[16];
+
+    aes_setkey_enc( &aes, (unsigned char *)key, 128 );
+    aes_crypt_cbc( &aes, AES_ENCRYPT, 16, iv, (unsigned char *)encrypt, output );
+
+    memcpy(out_data, output, 16);
+#endif
+
+    return 0;
+}
+
+int ss_onetimeauth(buffer_t *buf, uint8_t *iv, size_t capacity)
+{
+    uint8_t hash[ONETIMEAUTH_BYTES * 2];
+    uint8_t auth_key[MAX_IV_LENGTH + MAX_KEY_LENGTH];
+    memcpy(auth_key, iv, enc_iv_len);
+    memcpy(auth_key + enc_iv_len, enc_key, enc_key_len);
+
+    brealloc(buf, ONETIMEAUTH_BYTES + buf->len, capacity);
+
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_sha1(), auth_key, enc_iv_len + enc_key_len, (uint8_t *)buf->array, buf->len, (uint8_t *)hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(
+                        MBEDTLS_MD_SHA1), auth_key, enc_iv_len + enc_key_len, (uint8_t *)buf->array, buf->len,
+                    (uint8_t *)hash);
+#else
+    sha1_hmac(auth_key, enc_iv_len + enc_key_len, (uint8_t *)buf->array, buf->len, (uint8_t *)hash);
+#endif
+
+    memcpy(buf->array + buf->len, hash, ONETIMEAUTH_BYTES);
+    buf->len += ONETIMEAUTH_BYTES;
+
+    return 0;
+}
+
+int
+ss_onetimeauth_verify(buffer_t *buf, uint8_t *iv)
+{
+    uint8_t hash[ONETIMEAUTH_BYTES * 2];
+    uint8_t auth_key[MAX_IV_LENGTH + MAX_KEY_LENGTH];
+    memcpy(auth_key, iv, enc_iv_len);
+    memcpy(auth_key + enc_iv_len, enc_key, enc_key_len);
+    size_t len = buf->len - ONETIMEAUTH_BYTES;
+
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_sha1(), auth_key, enc_iv_len + enc_key_len, (uint8_t *)buf->array, len, hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(
+                        MBEDTLS_MD_SHA1), auth_key, enc_iv_len + enc_key_len, (uint8_t *)buf->array, len, hash);
+#else
+    sha1_hmac(auth_key, enc_iv_len + enc_key_len, (uint8_t *)buf->array, len, hash);
+#endif
+
+    return safe_memcmp(buf->array + len, hash, ONETIMEAUTH_BYTES);
+}
+
+int
+ss_encrypt_all(buffer_t *plain, int method, int auth, size_t capacity)
+{
+    if (method > TABLE) {
+        cipher_ctx_t evp;
+        cipher_context_init(&evp, method, 1);
+
+        size_t iv_len = enc_iv_len;
+        int err       = 1;
+
+        static buffer_t tmp = { 0, 0, 0, NULL };
+        brealloc(&tmp, iv_len + plain->len, capacity);
+        buffer_t *cipher = &tmp;
+        cipher->len = plain->len;
+
+        uint8_t iv[MAX_IV_LENGTH];
+
+        rand_bytes(iv, iv_len);
+        cipher_context_set_iv(&evp, iv, iv_len, 1);
+        memcpy(cipher->array, iv, iv_len);
+
+        if (auth) {
+            ss_onetimeauth(plain, iv, capacity);
+            cipher->len = plain->len;
+        }
+
+        if (method >= SALSA20) {
+            crypto_stream_xor_ic((uint8_t *)(cipher->array + iv_len),
+                                 (const uint8_t *)plain->array, (uint64_t)(plain->len),
+                                 (const uint8_t *)iv,
+                                 0, enc_key, method);
+        } else {
+            err = cipher_context_update(&evp, (uint8_t *)(cipher->array + iv_len),
+                                        &cipher->len, (const uint8_t *)plain->array,
+                                        plain->len);
+        }
+
+        if (!err) {
+            bfree(plain);
+            cipher_context_release(&evp);
+            return -1;
+        }
+
+#ifdef DEBUG
+        dump("PLAIN", plain->array, plain->len);
+        dump("CIPHER", cipher->array + iv_len, cipher->len);
+#endif
+
+        cipher_context_release(&evp);
+
+        brealloc(plain, iv_len + cipher->len, capacity);
+        memcpy(plain->array, cipher->array, iv_len + cipher->len);
+        plain->len = iv_len + cipher->len;
+
+        return 0;
+    } else {
+        char *begin = plain->array;
+        char *ptr   = plain->array;
+        while (ptr < begin + plain->len) {
+            *ptr = (char)enc_table[(uint8_t)*ptr];
+            ptr++;
+        }
+        return 0;
+    }
+}
+
+int
+ss_encrypt(buffer_t *plain, enc_ctx_t *ctx, size_t capacity)
+{
+    if (ctx != NULL) {
+        static buffer_t tmp = { 0, 0, 0, NULL };
+
+        int err       = 1;
+        size_t iv_len = 0;
+        if (!ctx->init) {
+            iv_len = enc_iv_len;
+        }
+
+        brealloc(&tmp, iv_len + plain->len, capacity);
+        buffer_t *cipher = &tmp;
+        cipher->len = plain->len;
+
+        if (!ctx->init) {
+            cipher_context_set_iv(&ctx->evp, ctx->evp.iv, iv_len, 1);
+            memcpy(cipher->array, ctx->evp.iv, iv_len);
+            ctx->counter = 0;
+            ctx->init    = 1;
+        }
+
+        if (enc_method >= SALSA20) {
+            int padding = ctx->counter % SODIUM_BLOCK_SIZE;
+            brealloc(cipher, iv_len + (padding + cipher->len) * 2, capacity);
+            if (padding) {
+                brealloc(plain, plain->len + padding, capacity);
+                memmove(plain->array + padding, plain->array, plain->len);
+                sodium_memzero(plain->array, padding);
+            }
+            crypto_stream_xor_ic((uint8_t *)(cipher->array + iv_len),
+                                 (const uint8_t *)plain->array,
+                                 (uint64_t)(plain->len + padding),
+                                 (const uint8_t *)ctx->evp.iv,
+                                 ctx->counter / SODIUM_BLOCK_SIZE, enc_key,
+                                 enc_method);
+            ctx->counter += plain->len;
+            if (padding) {
+                memmove(cipher->array + iv_len,
+                        cipher->array + iv_len + padding, cipher->len);
+            }
+        } else {
+            err =
+                cipher_context_update(&ctx->evp,
+                                      (uint8_t *)(cipher->array + iv_len),
+                                      &cipher->len, (const uint8_t *)plain->array,
+                                      plain->len);
+            if (!err) {
+                return -1;
+            }
+        }
+
+#ifdef DEBUG
+        dump("PLAIN", plain->array, plain->len);
+        dump("CIPHER", cipher->array + iv_len, cipher->len);
+#endif
+
+        brealloc(plain, iv_len + cipher->len, capacity);
+        memcpy(plain->array, cipher->array, iv_len + cipher->len);
+        plain->len = iv_len + cipher->len;
+
+        return 0;
+    } else {
+        char *begin = plain->array;
+        char *ptr   = plain->array;
+        while (ptr < begin + plain->len) {
+            *ptr = (char)enc_table[(uint8_t)*ptr];
+            ptr++;
+        }
+        return 0;
+    }
+}
+
+int
+ss_decrypt_all(buffer_t *cipher, int method, int auth, size_t capacity)
+{
+    if (method > TABLE) {
+        size_t iv_len = enc_iv_len;
+        int ret       = 1;
+
+        if (cipher->len <= iv_len) {
+            return -1;
+        }
+
+        cipher_ctx_t evp;
+        cipher_context_init(&evp, method, 0);
+
+        static buffer_t tmp = { 0, 0, 0, NULL };
+        brealloc(&tmp, cipher->len, capacity);
+        buffer_t *plain = &tmp;
+        plain->len = cipher->len - iv_len;
+
+        uint8_t iv[MAX_IV_LENGTH];
+        memcpy(iv, cipher->array, iv_len);
+        cipher_context_set_iv(&evp, iv, iv_len, 0);
+
+        if (method >= SALSA20) {
+            crypto_stream_xor_ic((uint8_t *)plain->array,
+                                 (const uint8_t *)(cipher->array + iv_len),
+                                 (uint64_t)(cipher->len - iv_len),
+                                 (const uint8_t *)iv, 0, enc_key, method);
+        } else {
+            ret = cipher_context_update(&evp, (uint8_t *)plain->array, &plain->len,
+                                        (const uint8_t *)(cipher->array + iv_len),
+                                        cipher->len - iv_len);
+        }
+
+        if (auth || (plain->array[0] & ONETIMEAUTH_FLAG)) {
+            if (plain->len > ONETIMEAUTH_BYTES) {
+                ret = !ss_onetimeauth_verify(plain, iv);
+                if (ret) {
+                    plain->len -= ONETIMEAUTH_BYTES;
+                }
+            } else {
+                ret = 0;
+            }
+        }
+
+        if (!ret) {
+            bfree(cipher);
+            cipher_context_release(&evp);
+            return -1;
+        }
+
+#ifdef DEBUG
+        dump("PLAIN", plain->array, plain->len);
+        dump("CIPHER", cipher->array + iv_len, cipher->len - iv_len);
+#endif
+
+        cipher_context_release(&evp);
+
+        brealloc(cipher, plain->len, capacity);
+        memcpy(cipher->array, plain->array, plain->len);
+        cipher->len = plain->len;
+
+        return 0;
+    } else {
+        char *begin = cipher->array;
+        char *ptr   = cipher->array;
+        while (ptr < begin + cipher->len) {
+            *ptr = (char)dec_table[(uint8_t)*ptr];
+            ptr++;
+        }
+        return 0;
+    }
+}
+
+int
+ss_decrypt(buffer_t *cipher, enc_ctx_t *ctx, size_t capacity)
+{
+    if (ctx != NULL) {
+        static buffer_t tmp = { 0, 0, 0, NULL };
+
+        size_t iv_len = 0;
+        int err       = 1;
+
+        brealloc(&tmp, cipher->len, capacity);
+        buffer_t *plain = &tmp;
+        plain->len = cipher->len;
+
+        if (!ctx->init) {
+            uint8_t iv[MAX_IV_LENGTH];
+            iv_len      = enc_iv_len;
+            plain->len -= iv_len;
+
+            memcpy(iv, cipher->array, iv_len);
+            cipher_context_set_iv(&ctx->evp, iv, iv_len, 0);
+            ctx->counter = 0;
+            ctx->init    = 1;
+
+            if (enc_method > RC4) {
+                if (cache_key_exist(iv_cache, (char *)iv, iv_len)) {
+                    bfree(cipher);
+                    return -1;
+                } else {
+                    cache_insert(iv_cache, (char *)iv, iv_len, NULL);
+                }
+            }
+        }
+
+        if (enc_method >= SALSA20) {
+            int padding = ctx->counter % SODIUM_BLOCK_SIZE;
+            brealloc(plain, (plain->len + padding) * 2, capacity);
+
+            if (padding) {
+                brealloc(cipher, cipher->len + padding, capacity);
+                memmove(cipher->array + iv_len + padding, cipher->array + iv_len,
+                        cipher->len - iv_len);
+                sodium_memzero(cipher->array + iv_len, padding);
+            }
+            crypto_stream_xor_ic((uint8_t *)plain->array,
+                                 (const uint8_t *)(cipher->array + iv_len),
+                                 (uint64_t)(cipher->len - iv_len + padding),
+                                 (const uint8_t *)ctx->evp.iv,
+                                 ctx->counter / SODIUM_BLOCK_SIZE, enc_key,
+                                 enc_method);
+            ctx->counter += cipher->len - iv_len;
+            if (padding) {
+                memmove(plain->array, plain->array + padding, plain->len);
+            }
+        } else {
+            err = cipher_context_update(&ctx->evp, (uint8_t *)plain->array, &plain->len,
+                                        (const uint8_t *)(cipher->array + iv_len),
+                                        cipher->len - iv_len);
+        }
+
+        if (!err) {
+            bfree(cipher);
+            return -1;
+        }
+
+#ifdef DEBUG
+        dump("PLAIN", plain->array, plain->len);
+        dump("CIPHER", cipher->array + iv_len, cipher->len - iv_len);
+#endif
+
+        brealloc(cipher, plain->len, capacity);
+        memcpy(cipher->array, plain->array, plain->len);
+        cipher->len = plain->len;
+
+        return 0;
+    } else {
+        char *begin = cipher->array;
+        char *ptr   = cipher->array;
+        while (ptr < begin + cipher->len) {
+            *ptr = (char)dec_table[(uint8_t)*ptr];
+            ptr++;
+        }
+        return 0;
+    }
+}
+
+void
+enc_ctx_init(int method, enc_ctx_t *ctx, int enc)
+{
+    sodium_memzero(ctx, sizeof(enc_ctx_t));
+    cipher_context_init(&ctx->evp, method, enc);
+
+    if (enc) {
+        rand_bytes(ctx->evp.iv, enc_iv_len);
+    }
+}
+
+void
+enc_key_init(int method, const char *pass)
+{
+    if (method <= TABLE || method >= CIPHER_NUM) {
+        LOGE("enc_key_init(): Illegal method");
+        return;
+    }
+
+    // Initialize cache
+    cache_create(&iv_cache, 256, NULL);
+
+#if defined(USE_CRYPTO_OPENSSL)
+    OpenSSL_add_all_algorithms();
+#else
+    cipher_kt_t cipher_info;
+#endif
+
+    cipher_t cipher;
+    memset(&cipher, 0, sizeof(cipher_t));
+
+    // Initialize sodium for random generator
+    if (sodium_init() == -1) {
+        FATAL("Failed to initialize sodium");
+    }
+
+    if (method == SALSA20 || method == CHACHA20 || method == CHACHA20IETF) {
+#if defined(USE_CRYPTO_OPENSSL)
+        cipher.info    = NULL;
+        cipher.key_len = supported_ciphers_key_size[method];
+        cipher.iv_len  = supported_ciphers_iv_size[method];
+#endif
+#if defined(USE_CRYPTO_POLARSSL)
+        cipher.info             = &cipher_info;
+        cipher.info->base       = NULL;
+        cipher.info->key_length = supported_ciphers_key_size[method] * 8;
+        cipher.info->iv_size    = supported_ciphers_iv_size[method];
+#endif
+#if defined(USE_CRYPTO_MBEDTLS)
+        // XXX: key_length changed to key_bitlen in mbed TLS 2.0.0
+        cipher.info             = &cipher_info;
+        cipher.info->base       = NULL;
+        cipher.info->key_bitlen = supported_ciphers_key_size[method] * 8;
+        cipher.info->iv_size    = supported_ciphers_iv_size[method];
+#endif
+    } else {
+        cipher.info = (cipher_kt_t *)get_cipher_type(method);
+    }
+
+    if (cipher.info == NULL && cipher.key_len == 0) {
+        do {
+#if defined(USE_CRYPTO_POLARSSL) && defined(USE_CRYPTO_APPLECC)
+            if (supported_ciphers_applecc[method] != kCCAlgorithmInvalid) {
+                cipher_info.base       = NULL;
+                cipher_info.key_length = supported_ciphers_key_size[method] * 8;
+                cipher_info.iv_size    = supported_ciphers_iv_size[method];
+                cipher.info            = (cipher_kt_t *)&cipher_info;
+                break;
+            }
+#endif
+#if defined(USE_CRYPTO_MBEDTLS) && defined(USE_CRYPTO_APPLECC)
+            // XXX: key_length changed to key_bitlen in mbed TLS 2.0.0
+            if (supported_ciphers_applecc[method] != kCCAlgorithmInvalid) {
+                cipher_info.base       = NULL;
+                cipher_info.key_bitlen = supported_ciphers_key_size[method] * 8;
+                cipher_info.iv_size    = supported_ciphers_iv_size[method];
+                cipher.info            = (cipher_kt_t *)&cipher_info;
+                break;
+            }
+#endif
+            LOGE("Cipher %s not found in crypto library", supported_ciphers[method]);
+            FATAL("Cannot initialize cipher");
+        } while (0);
+    }
+
+    const digest_type_t *md = get_digest_type("MD5");
+    if (md == NULL) {
+        FATAL("MD5 Digest not found in crypto library");
+    }
+
+    enc_key_len = bytes_to_key(&cipher, md, (const uint8_t *)pass, enc_key);
+
+    if (enc_key_len == 0) {
+        FATAL("Cannot generate key and IV");
+    }
+    if (method == RC4_MD5 || method == RC4_MD5_6) {
+        enc_iv_len = supported_ciphers_iv_size[method];
+    } else {
+        enc_iv_len = cipher_iv_size(&cipher);
+    }
+    enc_method = method;
+}
+
+int
+enc_init(const char *pass, const char *method)
+{
+    int m = TABLE;
+    if (method != NULL) {
+        for (m = TABLE; m < CIPHER_NUM; m++)
+            if (strcmp(method, supported_ciphers[m]) == 0) {
+                break;
+            }
+        if (m >= CIPHER_NUM) {
+            LOGE("Invalid cipher name: %s, use rc4-md5 instead", method);
+            m = RC4_MD5;
+        }
+    }
+    if (m == TABLE) {
+        enc_table_init(pass);
+    } else {
+        enc_key_init(m, pass);
+    }
+    return m;
+}
+
+int
+ss_check_hash(buffer_t *buf, chunk_t *chunk, enc_ctx_t *ctx, size_t capacity)
+{
+    int i, j, k;
+    ssize_t blen  = buf->len;
+    uint32_t cidx = chunk->idx;
+
+    brealloc(chunk->buf, chunk->len + blen, capacity);
+    brealloc(buf, chunk->len + blen, capacity);
+
+    for (i = 0, j = 0, k = 0; i < blen; i++) {
+        chunk->buf->array[cidx++] = buf->array[k++];
+
+        if (cidx == CLEN_BYTES) {
+            uint16_t clen = ntohs(*((uint16_t *)chunk->buf->array));
+            brealloc(chunk->buf, clen + AUTH_BYTES, capacity);
+            chunk->len = clen;
+        }
+
+        if (cidx == chunk->len + AUTH_BYTES) {
+            // Compare hash
+            uint8_t hash[ONETIMEAUTH_BYTES * 2];
+            uint8_t key[MAX_IV_LENGTH + sizeof(uint32_t)];
+
+            uint32_t c = htonl(chunk->counter);
+            memcpy(key, ctx->evp.iv, enc_iv_len);
+            memcpy(key + enc_iv_len, &c, sizeof(uint32_t));
+#if defined(USE_CRYPTO_OPENSSL)
+            HMAC(EVP_sha1(), key, enc_iv_len + sizeof(uint32_t),
+                 (uint8_t *)chunk->buf->array + AUTH_BYTES, chunk->len, hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+            mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA1), key, enc_iv_len + sizeof(uint32_t),
+                            (uint8_t *)chunk->buf->array + AUTH_BYTES, chunk->len, hash);
+#else
+            sha1_hmac(key, enc_iv_len + sizeof(uint32_t),
+                      (uint8_t *)chunk->buf->array + AUTH_BYTES, chunk->len, hash);
+#endif
+
+            if (safe_memcmp(hash, chunk->buf->array + CLEN_BYTES, ONETIMEAUTH_BYTES) != 0) {
+                return 0;
+            }
+
+            // Copy chunk back to buffer
+            memmove(buf->array + j + chunk->len, buf->array + k, blen - i - 1);
+            memcpy(buf->array + j, chunk->buf->array + AUTH_BYTES, chunk->len);
+
+            // Reset the base offset
+            j   += chunk->len;
+            k    = j;
+            cidx = 0;
+            chunk->counter++;
+        }
+    }
+
+    buf->len   = j;
+    chunk->idx = cidx;
+    return 1;
+}
+
+int
+ss_gen_hash(buffer_t *buf, uint32_t *counter, enc_ctx_t *ctx, size_t capacity)
+{
+    ssize_t blen       = buf->len;
+    uint16_t chunk_len = htons((uint16_t)blen);
+    uint8_t hash[ONETIMEAUTH_BYTES * 2];
+    uint8_t key[MAX_IV_LENGTH + sizeof(uint32_t)];
+    uint32_t c = htonl(*counter);
+
+    brealloc(buf, AUTH_BYTES + blen, capacity);
+    memcpy(key, ctx->evp.iv, enc_iv_len);
+    memcpy(key + enc_iv_len, &c, sizeof(uint32_t));
+#if defined(USE_CRYPTO_OPENSSL)
+    HMAC(EVP_sha1(), key, enc_iv_len + sizeof(uint32_t), (uint8_t *)buf->array, blen, hash, NULL);
+#elif defined(USE_CRYPTO_MBEDTLS)
+    mbedtls_md_hmac(mbedtls_md_info_from_type(
+                        MBEDTLS_MD_SHA1), key, enc_iv_len + sizeof(uint32_t), (uint8_t *)buf->array, blen, hash);
+#else
+    sha1_hmac(key, enc_iv_len + sizeof(uint32_t), (uint8_t *)buf->array, blen, hash);
+#endif
+
+    memmove(buf->array + AUTH_BYTES, buf->array, blen);
+    memcpy(buf->array + CLEN_BYTES, hash, ONETIMEAUTH_BYTES);
+    memcpy(buf->array, &chunk_len, CLEN_BYTES);
+
+    *counter = *counter + 1;
+    buf->len = blen + AUTH_BYTES;
+
+    return 0;
+}
diff --git a/server/encrypt.h b/server/encrypt.h
new file mode 100644
index 0000000..3bb7940
--- /dev/null
+++ b/server/encrypt.h
@@ -0,0 +1,222 @@
+/*
+ * encrypt.h - Define the enryptor's interface
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _ENCRYPT_H
+#define _ENCRYPT_H
+
+#ifndef __MINGW32__
+#include <sys/socket.h>
+#else
+
+#ifdef max
+#undef max
+#endif
+
+#ifdef min
+#undef min
+#endif
+
+#endif
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#if defined(USE_CRYPTO_OPENSSL)
+
+#include <openssl/evp.h>
+#include <openssl/sha.h>
+#include <openssl/md5.h>
+typedef EVP_CIPHER cipher_kt_t;
+typedef EVP_CIPHER_CTX cipher_evp_t;
+typedef EVP_MD digest_type_t;
+#define MAX_KEY_LENGTH EVP_MAX_KEY_LENGTH
+#define MAX_IV_LENGTH EVP_MAX_IV_LENGTH
+#define MAX_MD_SIZE EVP_MAX_MD_SIZE
+
+#elif defined(USE_CRYPTO_POLARSSL)
+
+#include <polarssl/cipher.h>
+#include <polarssl/md.h>
+typedef cipher_info_t cipher_kt_t;
+typedef cipher_context_t cipher_evp_t;
+typedef md_info_t digest_type_t;
+#define MAX_KEY_LENGTH 64
+#define MAX_IV_LENGTH POLARSSL_MAX_IV_LENGTH
+#define MAX_MD_SIZE POLARSSL_MD_MAX_SIZE
+
+#elif defined(USE_CRYPTO_MBEDTLS)
+
+#include <mbedtls/cipher.h>
+#include <mbedtls/md.h>
+typedef mbedtls_cipher_info_t cipher_kt_t;
+typedef mbedtls_cipher_context_t cipher_evp_t;
+typedef mbedtls_md_info_t digest_type_t;
+#define MAX_KEY_LENGTH 64
+#define MAX_IV_LENGTH MBEDTLS_MAX_IV_LENGTH
+#define MAX_MD_SIZE MBEDTLS_MD_MAX_SIZE
+
+/* we must have MBEDTLS_CIPHER_MODE_CFB defined */
+#if !defined(MBEDTLS_CIPHER_MODE_CFB)
+#error Cipher Feedback mode a.k.a CFB not supported by your mbed TLS.
+#endif
+
+#endif
+
+#ifdef USE_CRYPTO_APPLECC
+
+#include <CommonCrypto/CommonCrypto.h>
+
+#define kCCAlgorithmInvalid UINT32_MAX
+#define kCCContextValid 0
+#define kCCContextInvalid -1
+
+typedef struct {
+    CCCryptorRef cryptor;
+    int valid;
+    CCOperation encrypt;
+    CCAlgorithm cipher;
+    CCMode mode;
+    CCPadding padding;
+    uint8_t iv[MAX_IV_LENGTH];
+    uint8_t key[MAX_KEY_LENGTH];
+    size_t iv_len;
+    size_t key_len;
+} cipher_cc_t;
+
+#endif
+
+typedef struct {
+    cipher_evp_t *evp;
+#ifdef USE_CRYPTO_APPLECC
+    cipher_cc_t cc;
+#endif
+    uint8_t iv[MAX_IV_LENGTH];
+} cipher_ctx_t;
+
+typedef struct {
+    cipher_kt_t *info;
+    size_t iv_len;
+    size_t key_len;
+} cipher_t;
+
+#ifdef HAVE_STDINT_H
+#include <stdint.h>
+#elif HAVE_INTTYPES_H
+#include <inttypes.h>
+#endif
+
+#define SODIUM_BLOCK_SIZE   64
+
+enum crpher_index {
+    NONE = -1,
+    TABLE = 0,
+    RC4,
+    RC4_MD5_6,
+    RC4_MD5,
+    AES_128_CFB,
+    AES_192_CFB,
+    AES_256_CFB,
+    AES_128_CTR,
+    AES_192_CTR,
+    AES_256_CTR,
+    BF_CFB,
+    CAMELLIA_128_CFB,
+    CAMELLIA_192_CFB,
+    CAMELLIA_256_CFB,
+    CAST5_CFB,
+    DES_CFB,
+    IDEA_CFB,
+    RC2_CFB,
+    SEED_CFB,
+    SALSA20,
+    CHACHA20,
+    CHACHA20IETF,
+    CIPHER_NUM,
+};
+
+#define ONETIMEAUTH_FLAG 0x10
+#define ADDRTYPE_MASK 0xEF
+
+#define ONETIMEAUTH_BYTES 10U
+#define MD5_BYTES 16U
+#define SHA1_BYTES 20U
+#define CLEN_BYTES 2U
+#define AUTH_BYTES (ONETIMEAUTH_BYTES + CLEN_BYTES)
+
+#define min(a, b) (((a) < (b)) ? (a) : (b))
+#define max(a, b) (((a) > (b)) ? (a) : (b))
+
+typedef struct buffer {
+    size_t idx;
+    size_t len;
+    size_t capacity;
+    char   *array;
+} buffer_t;
+
+typedef struct chunk {
+    uint32_t idx;
+    uint32_t len;
+    uint32_t counter;
+    buffer_t *buf;
+} chunk_t;
+
+typedef struct enc_ctx {
+    uint8_t init;
+    uint64_t counter;
+    cipher_ctx_t evp;
+} enc_ctx_t;
+
+void bytes_to_key_with_size(const char *pass, size_t len, uint8_t *md, size_t md_size);
+
+int ss_encrypt_all(buffer_t *plaintext, int method, int auth, size_t capacity);
+int ss_decrypt_all(buffer_t *ciphertext, int method, int auth, size_t capacity);
+int ss_encrypt(buffer_t *plaintext, enc_ctx_t *ctx, size_t capacity);
+int ss_decrypt(buffer_t *ciphertext, enc_ctx_t *ctx, size_t capacity);
+
+void enc_ctx_init(int method, enc_ctx_t *ctx, int enc);
+int enc_init(const char *pass, const char *method);
+int enc_get_iv_len(void);
+uint8_t* enc_get_key(void);
+int enc_get_key_len(void);
+void cipher_context_release(cipher_ctx_t *evp);
+unsigned char *enc_md5(const unsigned char *d, size_t n, unsigned char *md);
+
+int ss_md5_hmac(char *auth, char *msg, int msg_len, uint8_t *iv);
+int ss_md5_hmac_with_key(char *auth, char *msg, int msg_len, uint8_t *auth_key, int key_len);
+int ss_md5_hash_func(char *auth, char *msg, int msg_len);
+int ss_sha1_hmac(char *auth, char *msg, int msg_len, uint8_t *iv);
+int ss_sha1_hmac_with_key(char *auth, char *msg, int msg_len, uint8_t *auth_key, int key_len);
+int ss_sha1_hash_func(char *auth, char *msg, int msg_len);
+int ss_aes_128_cbc(char *encrypt, char *out_data, char *key);
+int ss_onetimeauth(buffer_t *buf, uint8_t *iv, size_t capacity);
+int ss_onetimeauth_verify(buffer_t *buf, uint8_t *iv);
+
+int ss_check_hash(buffer_t *buf, chunk_t *chunk, enc_ctx_t *ctx, size_t capacity);
+int ss_gen_hash(buffer_t *buf, uint32_t *counter, enc_ctx_t *ctx, size_t capacity);
+
+int balloc(buffer_t *ptr, size_t capacity);
+int brealloc(buffer_t *ptr, size_t len, size_t capacity);
+void bfree(buffer_t *ptr);
+
+#endif // _ENCRYPT_H
diff --git a/server/http.c b/server/http.c
new file mode 100644
index 0000000..3bd4a32
--- /dev/null
+++ b/server/http.c
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h> /* malloc() */
+#include <string.h> /* strncpy() */
+#include <strings.h> /* strncasecmp() */
+#include <ctype.h> /* isblank() */
+
+#include "http.h"
+#include "protocol.h"
+
+#define SERVER_NAME_LEN 256
+
+static int parse_http_header(const char *, size_t, char **);
+static int get_header(const char *, const char *, int, char **);
+static int next_header(const char **, int *);
+
+static const protocol_t http_protocol_st = {
+    .default_port =                 80,
+    .parse_packet = &parse_http_header,
+};
+const protocol_t *const http_protocol = &http_protocol_st;
+
+/*
+ * Parses a HTTP request for the Host: header
+ *
+ * Returns:
+ *  >=0  - length of the hostname and updates *hostname
+ *         caller is responsible for freeing *hostname
+ *  -1   - Incomplete request
+ *  -2   - No Host header included in this request
+ *  -3   - Invalid hostname pointer
+ *  -4   - malloc failure
+ *  < -4 - Invalid HTTP request
+ *
+ */
+static int
+parse_http_header(const char *data, size_t data_len, char **hostname)
+{
+    int result, i;
+
+    if (hostname == NULL)
+        return -3;
+
+    if (data_len == 0)
+        return -1;
+
+    result = get_header("Host:", data, data_len, hostname);
+    if (result < 0)
+        return result;
+
+    /*
+     *  if the user specifies the port in the request, it is included here.
+     *  Host: example.com:80
+     *  so we trim off port portion
+     */
+    for (i = result - 1; i >= 0; i--)
+        if ((*hostname)[i] == ':') {
+            (*hostname)[i] = '\0';
+            result         = i;
+            break;
+        }
+
+    return result;
+}
+
+static int
+get_header(const char *header, const char *data, int data_len, char **value)
+{
+    int len, header_len;
+
+    header_len = strlen(header);
+
+    /* loop through headers stopping at first blank line */
+    while ((len = next_header(&data, &data_len)) != 0)
+        if (len > header_len && strncasecmp(header, data, header_len) == 0) {
+            /* Eat leading whitespace */
+            while (header_len < len && isblank(data[header_len]))
+                header_len++;
+
+            *value = malloc(len - header_len + 1);
+            if (*value == NULL)
+                return -4;
+
+            strncpy(*value, data + header_len, len - header_len);
+            (*value)[len - header_len] = '\0';
+
+            return len - header_len;
+        }
+
+    /* If there is no data left after reading all the headers then we do not
+     * have a complete HTTP request, there must be a blank line */
+    if (data_len == 0)
+        return -1;
+
+    return -2;
+}
+
+static int
+next_header(const char **data, int *len)
+{
+    int header_len;
+
+    /* perhaps we can optimize this to reuse the value of header_len, rather
+     * than scanning twice.
+     * Walk our data stream until the end of the header */
+    while (*len > 2 && (*data)[0] != '\r' && (*data)[1] != '\n') {
+        (*len)--;
+        (*data)++;
+    }
+
+    /* advanced past the <CR><LF> pair */
+    *data += 2;
+    *len  -= 2;
+
+    /* Find the length of the next header */
+    header_len = 0;
+    while (*len > header_len + 1
+           && (*data)[header_len] != '\r'
+           && (*data)[header_len + 1] != '\n')
+        header_len++;
+
+    return header_len;
+}
diff --git a/server/http.h b/server/http.h
new file mode 100644
index 0000000..914815a
--- /dev/null
+++ b/server/http.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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 HTTP_H
+#define HTTP_H
+
+#include <stdio.h>
+#include "protocol.h"
+
+const protocol_t *const http_protocol;
+
+#endif
diff --git a/server/http_simple.c b/server/http_simple.c
new file mode 100644
index 0000000..c1e34ee
--- /dev/null
+++ b/server/http_simple.c
@@ -0,0 +1,625 @@
+
+#include "http_simple.h"
+
+static char* g_useragent[] = {
+    "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/40.0",
+    "Mozilla/5.0 (Windows NT 6.3; WOW64; rv:40.0) Gecko/20100101 Firefox/44.0",
+    "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36",
+    "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.11 (KHTML, like Gecko) Ubuntu/11.10 Chromium/27.0.1453.93 Chrome/27.0.1453.93 Safari/537.36",
+    "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:35.0) Gecko/20100101 Firefox/35.0",
+    "Mozilla/5.0 (compatible; WOW64; MSIE 10.0; Windows NT 6.2)",
+    "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.20.25 (KHTML, like Gecko) Version/5.0.4 Safari/533.20.27",
+    "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C)",
+    "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko",
+    "Mozilla/5.0 (Linux; Android 4.4; Nexus 5 Build/BuildID) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/30.0.0.0 Mobile Safari/537.36",
+    "Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
+    "Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3",
+};
+
+static int g_useragent_index = -1;
+
+typedef struct http_simple_local_data {
+    int has_sent_header;
+    int has_recv_header;
+    char *encode_buffer;
+    int host_matched;
+    char *recv_buffer;
+    int recv_buffer_size;
+}http_simple_local_data;
+
+void http_simple_local_data_init(http_simple_local_data* local) {
+    local->has_sent_header = 0;
+    local->has_recv_header = 0;
+    local->encode_buffer = NULL;
+
+    local->recv_buffer = malloc(0);
+    local->recv_buffer_size = 0;
+
+    local->host_matched = 0;
+
+    if (g_useragent_index == -1) {
+        g_useragent_index = xorshift128plus() % (sizeof(g_useragent) / sizeof(*g_useragent));
+    }
+}
+
+obfs * http_simple_new_obfs() {
+    obfs * self = new_obfs();
+    self->l_data = malloc(sizeof(http_simple_local_data));
+    http_simple_local_data_init((http_simple_local_data*)self->l_data);
+    return self;
+}
+
+void http_simple_dispose(obfs *self) {
+    http_simple_local_data *local = (http_simple_local_data*)self->l_data;
+    if (local->encode_buffer != NULL) {
+        free(local->encode_buffer);
+        local->encode_buffer = NULL;
+    }
+    free(local);
+    dispose_obfs(self);
+}
+
+char http_simple_hex(char c) {
+    if (c < 10) return c + '0';
+    return c - 10 + 'a';
+}
+
+int get_data_from_http_header(char *data, char **outdata) {
+    char *delim = "\r\n";
+    char *delim_hex = "%";
+    int outlength = 0;
+
+    char *buf = *outdata;
+    char *p_line;
+    p_line = strtok(data, delim);
+
+    //while(p_line)
+    {
+        char *p_hex;
+
+        p_hex = strtok(p_line, delim_hex);
+
+        while((p_hex = strtok(NULL, delim_hex)))
+        {
+            char hex = 0;
+
+            if(strlen(p_hex) <= 0)
+            {
+                continue;
+            }
+
+            if(strlen(p_hex) > 2)
+            {
+                char *c_hex = (char*)malloc(2);
+                memcpy(c_hex, p_hex, 2);
+                hex = (char)strtol(c_hex, NULL, 16);
+                free(c_hex);
+            }
+            else
+            {
+                hex = (char)strtol(p_hex, NULL, 16);
+            }
+
+            outlength += 1;
+            buf = (char*)realloc(buf, outlength);
+            buf[outlength - 1] = hex;
+        }
+
+        //p_line = strtok(p_line, delim);
+    }
+    return outlength;
+}
+
+void get_host_from_http_header(char *data, char **host) {
+    char* data_begin = strstr(data, "Host: ");
+
+    if(data_begin == NULL)
+    {
+        return;
+    }
+
+    data_begin += 6;
+    char* data_end = strstr(data_begin, "\r\n");
+    char* data_end_port = strstr(data_begin, ":");
+
+    int host_length = 0;
+
+    if(data_end_port != NULL)
+    {
+        host_length = data_end_port - data_begin;
+    }
+    else
+    {
+        host_length = data_end - data_begin;
+    }
+
+    if(host_length <= 0)
+    {
+        return;
+    }
+
+    memset(*host, 0x00, 1024);
+    memcpy(*host, data_begin, host_length);
+}
+
+void http_simple_encode_head(http_simple_local_data *local, char *data, int datalength) {
+    if (local->encode_buffer == NULL) {
+        local->encode_buffer = (char*)malloc(datalength * 3 + 1);
+    }
+    int pos = 0;
+    for (; pos < datalength; ++pos) {
+        local->encode_buffer[pos * 3] = '%';
+        local->encode_buffer[pos * 3 + 1] = http_simple_hex(((unsigned char)data[pos] >> 4));
+        local->encode_buffer[pos * 3 + 2] = http_simple_hex(data[pos] & 0xF);
+    }
+    local->encode_buffer[pos * 3] = 0;
+}
+
+int http_simple_client_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity) {
+    char *encryptdata = *pencryptdata;
+    http_simple_local_data *local = (http_simple_local_data*)self->l_data;
+    if (local->has_sent_header) {
+        return datalength;
+    }
+    char hosts[1024];
+    char * phost[128];
+    int host_num = 0;
+    int pos;
+    char hostport[128];
+    int head_size = self->server.head_len + (xorshift128plus() & 0x3F);
+    int outlength;
+    char * out_buffer = (char*)malloc(datalength + 2048);
+    char * body_buffer = NULL;
+    if (head_size > datalength)
+        head_size = datalength;
+    http_simple_encode_head(local, encryptdata, head_size);
+    if (self->server.param && strlen(self->server.param) == 0)
+        self->server.param = NULL;
+    strncpy(hosts, self->server.param ? self->server.param : self->server.host, sizeof hosts);
+    phost[host_num++] = hosts;
+    for (pos = 0; hosts[pos]; ++pos) {
+        if (hosts[pos] == ',') {
+            phost[host_num++] = &hosts[pos + 1];
+            hosts[pos] = 0;
+        } else if (hosts[pos] == '#') {
+            char * body_pointer = &hosts[pos + 1];
+            char * p;
+            int trans_char = 0;
+            p = body_buffer = (char*)malloc(2048);
+            for ( ; *body_pointer; ++body_pointer) {
+                if (*body_pointer == '\\') {
+                    trans_char = 1;
+                    continue;
+                } else if (*body_pointer == '\n') {
+                    *p = '\r';
+                    *++p = '\n';
+                    continue;
+                }
+                if (trans_char) {
+                    if (*body_pointer == '\\' ) {
+                        *p = '\\';
+                    } else if (*body_pointer == 'n' ) {
+                        *p = '\r';
+                        *++p = '\n';
+                    } else {
+                        *p = '\\';
+                        *p = *body_pointer;
+                    }
+                    trans_char = 0;
+                } else {
+                    *p = *body_pointer;
+                }
+                ++p;
+            }
+            *p = 0;
+            hosts[pos] = 0;
+            break;
+        }
+    }
+    host_num = xorshift128plus() % host_num;
+    if (self->server.port == 80)
+        sprintf(hostport, "%s", phost[host_num]);
+    else
+        sprintf(hostport, "%s:%d", phost[host_num], self->server.port);
+    if (body_buffer) {
+        sprintf(out_buffer,
+            "GET /%s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "%s\r\n\r\n",
+            local->encode_buffer,
+            hostport,
+            body_buffer);
+    } else {
+        sprintf(out_buffer,
+            "GET /%s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "User-Agent: %s\r\n"
+            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+            "Accept-Language: en-US,en;q=0.8\r\n"
+            "Accept-Encoding: gzip, deflate\r\n"
+            "DNT: 1\r\n"
+            "Connection: keep-alive\r\n"
+            "\r\n",
+            local->encode_buffer,
+            hostport,
+            g_useragent[g_useragent_index]
+            );
+    }
+    //LOGI("http header: %s", out_buffer);
+    outlength = strlen(out_buffer);
+    memmove(out_buffer + outlength, encryptdata + head_size, datalength - head_size);
+    outlength += datalength - head_size;
+    local->has_sent_header = 1;
+    if (*capacity < outlength) {
+        *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+        encryptdata = *pencryptdata;
+    }
+    memmove(encryptdata, out_buffer, outlength);
+    free(out_buffer);
+    if (body_buffer != NULL)
+        free(body_buffer);
+    if (local->encode_buffer != NULL) {
+        free(local->encode_buffer);
+        local->encode_buffer = NULL;
+    }
+    return outlength;
+}
+
+int http_simple_server_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity) {
+    char *encryptdata = *pencryptdata;
+    http_simple_local_data *local = (http_simple_local_data*)self->l_data;
+    if (local->has_sent_header) {
+        return datalength;
+    }
+    int outlength;
+    char * out_buffer = (char*)malloc(datalength + 2048);
+
+    time_t now;
+    struct tm *tm_now;
+    char    datetime[200];
+
+    time(&now);
+    tm_now = localtime(&now);
+    strftime(datetime, 200, "%a, %d %b %Y %H:%M:%S GMT", tm_now);
+
+    sprintf(out_buffer,
+        "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Encoding: gzip\r\nContent-Type: text/html\r\nDate: "
+        "%s"
+        "\r\nServer: nginx\r\nVary: Accept-Encoding\r\n\r\n",
+        datetime);
+
+    outlength = strlen(out_buffer);
+    memmove(out_buffer + outlength, encryptdata, datalength);
+    outlength += datalength;
+
+    local->has_sent_header = 1;
+    if (*capacity < outlength) {
+        *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+        encryptdata = *pencryptdata;
+    }
+    memmove(encryptdata, out_buffer, outlength);
+    free(out_buffer);
+    return outlength;
+}
+
+int http_simple_client_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback) {
+    char *encryptdata = *pencryptdata;
+    http_simple_local_data *local = (http_simple_local_data*)self->l_data;
+    *needsendback = 0;
+    if (local->has_recv_header) {
+        return datalength;
+    }
+    char* data_begin = strstr(encryptdata, "\r\n\r\n");
+    if (data_begin) {
+        int outlength;
+        data_begin += 4;
+        local->has_recv_header = 1;
+        outlength = datalength - (data_begin - encryptdata);
+        memmove(encryptdata, data_begin, outlength);
+        return outlength;
+    } else {
+        return 0;
+    }
+}
+
+int http_simple_server_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback) {
+    char *encryptdata = *pencryptdata;
+    http_simple_local_data *local = (http_simple_local_data*)self->l_data;
+    *needsendback = 0;
+    if (local->has_recv_header) {
+        return datalength;
+    }
+
+    if(datalength != 0)
+    {
+        local->recv_buffer = (char*)realloc(local->recv_buffer, local->recv_buffer_size + datalength);
+        memmove(local->recv_buffer + local->recv_buffer_size, encryptdata, datalength);
+        local->recv_buffer_size += datalength;
+
+        int outlength = local->recv_buffer_size;
+        if (*capacity < outlength) {
+            *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+            encryptdata = *pencryptdata;
+        }
+        memcpy(encryptdata, local->recv_buffer, local->recv_buffer_size);
+    }
+
+    if(local->recv_buffer_size > 10)
+    {
+        if(strstr(local->recv_buffer, "GET /") == local->recv_buffer || strstr(local->recv_buffer, "POST /") == local->recv_buffer)
+        {
+            if(local->recv_buffer_size > 65536)
+            {
+                free(local->recv_buffer);
+                local->recv_buffer = malloc(0);
+                local->recv_buffer_size = 0;
+                local->has_sent_header = 1;
+                local->has_recv_header = 1;
+                LOGE("http_simple: over size");
+                return -1;
+            }
+        }
+        else
+        {
+            free(local->recv_buffer);
+            local->recv_buffer = malloc(0);
+            local->recv_buffer_size = 0;
+            local->has_sent_header = 1;
+            local->has_recv_header = 1;
+            LOGE("http_simple: not match begin");
+            return -1;
+        }
+    }
+    else
+    {
+        LOGE("http_simple: too short");
+        local->has_sent_header = 1;
+        local->has_recv_header = 1;
+        return -1;
+    }
+
+    char* data_begin = strstr(encryptdata, "\r\n\r\n");
+    if (data_begin) {
+        int outlength;
+        char *ret_buf = (char*)malloc(*capacity);
+        memset(ret_buf, 0x00, *capacity);
+        int ret_buf_len = 0;
+        ret_buf_len = get_data_from_http_header(encryptdata, &ret_buf);
+
+        if (self->server.param && strlen(self->server.param) == 0)
+        {
+            self->server.param = NULL;
+        }
+        else
+        {
+            if(local->host_matched == 0)
+            {
+                char *host = (char*)malloc(1024);
+                get_host_from_http_header(local->recv_buffer, &host);
+                char hosts[1024];
+                char * phost[128];
+                int host_num = 0;
+                int pos = 0;
+                int is_match = 0;
+                char * body_buffer = NULL;
+                strncpy(hosts, self->server.param, sizeof hosts);
+                phost[host_num++] = hosts;
+
+                for (pos = 0; hosts[pos]; ++pos) {
+                    if (hosts[pos] == ',') {
+                        phost[host_num++] = &hosts[pos + 1];
+                        hosts[pos] = 0;
+                    } else if (hosts[pos] == '#') {
+                        char * body_pointer = &hosts[pos + 1];
+                        char * p;
+                        int trans_char = 0;
+                        p = body_buffer = (char*)malloc(2048);
+                        for ( ; *body_pointer; ++body_pointer) {
+                            if (*body_pointer == '\\') {
+                                trans_char = 1;
+                                continue;
+                            } else if (*body_pointer == '\n') {
+                                *p = '\r';
+                                *++p = '\n';
+                                continue;
+                            }
+                            if (trans_char) {
+                                if (*body_pointer == '\\' ) {
+                                    *p = '\\';
+                                } else if (*body_pointer == 'n' ) {
+                                    *p = '\r';
+                                    *++p = '\n';
+                                } else {
+                                    *p = '\\';
+                                    *p = *body_pointer;
+                                }
+                                trans_char = 0;
+                            } else {
+                                *p = *body_pointer;
+                            }
+                            ++p;
+                        }
+                        *p = 0;
+                        hosts[pos] = 0;
+                        break;
+                    }
+                }
+
+
+                for(pos = 0; pos < host_num; pos++)
+                {
+                    if(strcmp(phost[pos], host) == 0)
+                    {
+                        is_match = 1;
+                        local->host_matched = 1;
+                    }
+                }
+
+                if(is_match == 0)
+                {
+                    free(local->recv_buffer);
+                    local->recv_buffer = malloc(0);
+                    local->recv_buffer_size = 0;
+                    local->has_sent_header = 1;
+                    local->has_recv_header = 1;
+                    LOGE("http_simple: not match host, host: %s", host);
+                    return -1;
+                }
+
+                free(host);
+            }
+        }
+
+        if(ret_buf_len <= 0)
+        {
+            return -1;
+        }
+
+        data_begin += 4;
+        local->has_recv_header = 1;
+
+        ret_buf = (char*)realloc(ret_buf, ret_buf_len + datalength - (data_begin - encryptdata));
+        outlength = ret_buf_len + datalength - (data_begin - encryptdata);
+
+        memcpy(ret_buf + ret_buf_len, data_begin, datalength - (data_begin - encryptdata));
+
+        if (*capacity < outlength) {
+            *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+            encryptdata = *pencryptdata;
+        }
+
+        memcpy(encryptdata, ret_buf, outlength);
+        free(ret_buf);
+        return outlength;
+    } else {
+        return 0;
+    }
+}
+
+void boundary(char result[])
+{
+    char *str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    int i,lstr;
+    char ss[3] = {0};
+    lstr = strlen(str);
+    srand((unsigned int)time((time_t *)NULL));
+    for(i = 0; i < 32; ++i)
+    {
+        sprintf(ss, "%c", str[(rand()%lstr)]);
+        strcat(result, ss);
+    }
+}
+
+int http_post_client_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity) {
+    char *encryptdata = *pencryptdata;
+    http_simple_local_data *local = (http_simple_local_data*)self->l_data;
+    if (local->has_sent_header) {
+        return datalength;
+    }
+    char hosts[1024];
+    char * phost[128];
+    int host_num = 0;
+    int pos;
+    char hostport[128];
+    int head_size = self->server.head_len + (xorshift128plus() & 0x3F);
+    int outlength;
+    char * out_buffer = (char*)malloc(datalength + 2048);
+    char * body_buffer = NULL;
+    if (head_size > datalength)
+        head_size = datalength;
+    http_simple_encode_head(local, encryptdata, head_size);
+    if (self->server.param && strlen(self->server.param) == 0)
+        self->server.param = NULL;
+    strncpy(hosts, self->server.param ? self->server.param : self->server.host, sizeof hosts);
+    phost[host_num++] = hosts;
+    for (pos = 0; hosts[pos]; ++pos) {
+        if (hosts[pos] == ',') {
+            phost[host_num++] = &hosts[pos + 1];
+            hosts[pos] = 0;
+        } else if (hosts[pos] == '#') {
+            char * body_pointer = &hosts[pos + 1];
+            char * p;
+            int trans_char = 0;
+            p = body_buffer = (char*)malloc(2048);
+            for ( ; *body_pointer; ++body_pointer) {
+                if (*body_pointer == '\\') {
+                    trans_char = 1;
+                    continue;
+                } else if (*body_pointer == '\n') {
+                    *p = '\r';
+                    *++p = '\n';
+                    continue;
+                }
+                if (trans_char) {
+                    if (*body_pointer == '\\' ) {
+                        *p = '\\';
+                    } else if (*body_pointer == 'n' ) {
+                        *p = '\r';
+                        *++p = '\n';
+                    } else {
+                        *p = '\\';
+                        *p = *body_pointer;
+                    }
+                    trans_char = 0;
+                } else {
+                    *p = *body_pointer;
+                }
+                ++p;
+            }
+            *p = 0;
+            hosts[pos] = 0;
+            break;
+        }
+    }
+    host_num = xorshift128plus() % host_num;
+    if (self->server.port == 80)
+        sprintf(hostport, "%s", phost[host_num]);
+    else
+        sprintf(hostport, "%s:%d", phost[host_num], self->server.port);
+    if (body_buffer) {
+        sprintf(out_buffer,
+            "POST /%s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "%s\r\n\r\n",
+            local->encode_buffer,
+            hostport,
+            body_buffer);
+    } else {
+        char result[33] = {0};
+        boundary(result);
+        sprintf(out_buffer,
+            "POST /%s HTTP/1.1\r\n"
+            "Host: %s\r\n"
+            "User-Agent: %s\r\n"
+            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
+            "Accept-Language: en-US,en;q=0.8\r\n"
+            "Accept-Encoding: gzip, deflate\r\n"
+            "Content-Type: multipart/form-data; boundary=%s\r\n"
+            "DNT: 1\r\n"
+            "Connection: keep-alive\r\n"
+            "\r\n",
+            local->encode_buffer,
+            hostport,
+            g_useragent[g_useragent_index],
+            result
+            );
+    }
+    //LOGI("http header: %s", out_buffer);
+    outlength = strlen(out_buffer);
+    memmove(out_buffer + outlength, encryptdata + head_size, datalength - head_size);
+    outlength += datalength - head_size;
+    local->has_sent_header = 1;
+    if (*capacity < outlength) {
+        *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+        encryptdata = *pencryptdata;
+    }
+    memmove(encryptdata, out_buffer, outlength);
+    free(out_buffer);
+    if (body_buffer != NULL)
+        free(body_buffer);
+    if (local->encode_buffer != NULL) {
+        free(local->encode_buffer);
+        local->encode_buffer = NULL;
+    }
+    return outlength;
+}
diff --git a/server/http_simple.h b/server/http_simple.h
new file mode 100644
index 0000000..cce24cc
--- /dev/null
+++ b/server/http_simple.h
@@ -0,0 +1,21 @@
+/*
+ * http_simple.h - Define shadowsocksR server's buffers and callbacks
+ *
+ * Copyright (C) 2015 - 2016, Break Wa11 <mmgac001@gmail.com>
+ */
+
+#ifndef _HTTP_SIMPLE_H
+#define _HTTP_SIMPLE_H
+
+obfs * http_simple_new_obfs();
+void http_simple_dispose(obfs *self);
+
+int http_simple_client_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity);
+int http_simple_client_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback);
+
+int http_post_client_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity);
+
+int http_simple_server_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity);
+int http_simple_server_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback);
+
+#endif // _HTTP_SIMPLE_H
diff --git a/server/jconf.c b/server/jconf.c
new file mode 100644
index 0000000..494aa5f
--- /dev/null
+++ b/server/jconf.c
@@ -0,0 +1,260 @@
+/*
+ * jconf.c - Parse the JSON format config file
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <time.h>
+
+#include "utils.h"
+#include "jconf.h"
+#include "json.h"
+#include "string.h"
+
+#include <libcork/core.h>
+
+#define check_json_value_type(value, expected_type, message) \
+    do { \
+        if ((value)->type != (expected_type)) \
+            FATAL((message)); \
+    } while(0)
+
+static char *
+to_string(const json_value *value)
+{
+    if (value->type == json_string) {
+        return ss_strndup(value->u.string.ptr, value->u.string.length);
+    } else if (value->type == json_integer) {
+        return strdup(ss_itoa(value->u.integer));
+    } else if (value->type == json_null) {
+        return "null";
+    } else {
+        LOGE("%d", value->type);
+        FATAL("Invalid config format.");
+    }
+    return 0;
+}
+
+void
+free_addr(ss_addr_t *addr)
+{
+    ss_free(addr->host);
+    ss_free(addr->port);
+}
+
+void
+parse_addr(const char *str, ss_addr_t *addr)
+{
+    int ipv6 = 0, ret = -1, n = 0;
+    char *pch;
+
+    struct cork_ip ip;
+    if (cork_ip_init(&ip, str) != -1) {
+        addr->host = strdup(str);
+        addr->port = NULL;
+        return;
+    }
+
+    pch = strchr(str, ':');
+    while (pch != NULL) {
+        n++;
+        ret = pch - str;
+        pch = strchr(pch + 1, ':');
+    }
+    if (n > 1) {
+        ipv6 = 1;
+        if (str[ret - 1] != ']') {
+            ret = -1;
+        }
+    }
+
+    if (ret == -1) {
+        if (ipv6) {
+            addr->host = ss_strndup(str + 1, strlen(str) - 2);
+        } else {
+            addr->host = strdup(str);
+        }
+        addr->port = NULL;
+    } else {
+        if (ipv6) {
+            addr->host = ss_strndup(str + 1, ret - 2);
+        } else {
+            addr->host = ss_strndup(str, ret);
+        }
+        addr->port = strdup(str + ret + 1);
+    }
+}
+
+jconf_t *
+read_jconf(const char *file)
+{
+    static jconf_t conf;
+
+    memset(&conf, 0, sizeof(jconf_t));
+
+    char *buf;
+    json_value *obj;
+
+    FILE *f = fopen(file, "rb");
+    if (f == NULL) {
+        FATAL("Invalid config path.");
+    }
+
+    fseek(f, 0, SEEK_END);
+    long pos = ftell(f);
+    fseek(f, 0, SEEK_SET);
+
+    if (pos >= MAX_CONF_SIZE) {
+        FATAL("Too large config file.");
+    }
+
+    buf = ss_malloc(pos + 1);
+    if (buf == NULL) {
+        FATAL("No enough memory.");
+    }
+
+    int nread = fread(buf, pos, 1, f);
+    if (!nread) {
+        FATAL("Failed to read the config file.");
+    }
+    fclose(f);
+
+    buf[pos] = '\0'; // end of string
+
+    json_settings settings = { 0UL, 0, NULL, NULL, NULL };
+    char error_buf[512];
+    obj = json_parse_ex(&settings, buf, pos, error_buf);
+
+    if (obj == NULL) {
+        FATAL(error_buf);
+    }
+
+    if (obj->type == json_object) {
+        unsigned int i, j;
+        for (i = 0; i < obj->u.object.length; i++) {
+            char *name        = obj->u.object.values[i].name;
+            json_value *value = obj->u.object.values[i].value;
+            if (strcmp(name, "server") == 0) {
+                if (value->type == json_array) {
+                    for (j = 0; j < value->u.array.length; j++) {
+                        if (j >= MAX_REMOTE_NUM) {
+                            break;
+                        }
+                        json_value *v = value->u.array.values[j];
+                        char *addr_str = to_string(v);
+                        parse_addr(addr_str, conf.remote_addr + j);
+                        ss_free(addr_str);
+                        conf.remote_num = j + 1;
+                    }
+                } else if (value->type == json_string) {
+                    conf.remote_addr[0].host = to_string(value);
+                    conf.remote_addr[0].port = NULL;
+                    conf.remote_num          = 1;
+                }
+            } else if (strcmp(name, "port_password") == 0) {
+                if (value->type == json_object) {
+                    for (j = 0; j < value->u.object.length; j++) {
+                        if (j >= MAX_PORT_NUM) {
+                            break;
+                        }
+                        json_value *v = value->u.object.values[j].value;
+                        if (v->type == json_string) {
+                            conf.port_password[j].port = ss_strndup(value->u.object.values[j].name,
+                                                                    value->u.object.values[j].name_length);
+                            conf.port_password[j].password = to_string(v);
+                            conf.port_password_num         = j + 1;
+                        }
+                    }
+                }
+            } else if (strcmp(name, "server_port") == 0) {
+                conf.remote_port = to_string(value);
+            } else if (strcmp(name, "local_address") == 0) {
+                conf.local_addr = to_string(value);
+            } else if (strcmp(name, "local_port") == 0) {
+                conf.local_port = to_string(value);
+            } else if (strcmp(name, "password") == 0) {
+                conf.password = to_string(value);
+            } else if (strcmp(name, "protocol") == 0) { // SSR
+                conf.protocol = to_string(value);
+            } else if (strcmp(name, "protocol_param") == 0) { // SSR
+                conf.protocol_param = to_string(value);
+            } else if (strcmp(name, "method") == 0) {
+                conf.method = to_string(value);
+            } else if (strcmp(name, "obfs") == 0) { // SSR
+                conf.obfs = to_string(value);
+            } else if (strcmp(name, "obfs_param") == 0) { // SSR
+                conf.obfs_param = to_string(value);
+            } else if (strcmp(name, "timeout") == 0) {
+                conf.timeout = to_string(value);
+            } else if (strcmp(name, "user") == 0) {
+                conf.user = to_string(value);
+            } else if (strcmp(name, "fast_open") == 0) {
+                check_json_value_type(value, json_boolean,
+                        "invalid config file: option 'fast_open' must be a boolean");
+                conf.fast_open = value->u.boolean;
+            } else if (strcmp(name, "auth") == 0) {
+                check_json_value_type(value, json_boolean,
+                        "invalid config file: option 'auth' must be a boolean");
+                conf.auth = value->u.boolean;
+            } else if (strcmp(name, "nofile") == 0) {
+                check_json_value_type(value, json_integer,
+                    "invalid config file: option 'nofile' must be an integer");
+                conf.nofile = value->u.integer;
+            } else if (strcmp(name, "nameserver") == 0) {
+                conf.nameserver = to_string(value);
+            } else if (strcmp(name, "tunnel_address") == 0) {
+                conf.tunnel_address = to_string(value);
+            } else if (strcmp(name, "mode") == 0) {
+                char *mode_str = to_string(value);
+
+                if (strcmp(mode_str, "tcp_only") == 0)
+                    conf.mode = TCP_ONLY;
+                else if (strcmp(mode_str, "tcp_and_udp") == 0)
+                    conf.mode = TCP_AND_UDP;
+                else if (strcmp(mode_str, "udp_only") == 0)
+                    conf.mode = UDP_ONLY;
+                else
+                    LOGI("ignore unknown mode: %s, use tcp_only as fallback",
+                         mode_str);
+                ss_free(mode_str);
+            } else if (strcmp(name, "mtu") == 0) {
+                check_json_value_type(value, json_integer,
+                    "invalid config file: option 'mtu' must be an integer");
+                conf.mtu = value->u.integer;
+            } else if (strcmp(name, "mptcp") == 0) {
+                check_json_value_type(value, json_boolean,
+                    "invalid config file: option 'mptcp' must be a boolean");
+                conf.mptcp = value->u.boolean;
+            } else if (strcmp(name, "ipv6_first") == 0) {
+                check_json_value_type(value, json_boolean,
+                    "invalid config file: option 'ipv6_first' must be a boolean");
+                conf.ipv6_first = value->u.boolean;
+            }
+        }
+    } else {
+        FATAL("Invalid config file");
+    }
+
+    ss_free(buf);
+    json_value_free(obj);
+    return &conf;
+}
diff --git a/server/jconf.h b/server/jconf.h
new file mode 100644
index 0000000..9a7e5e3
--- /dev/null
+++ b/server/jconf.h
@@ -0,0 +1,78 @@
+/*
+ * jconf.h - Define the config data structure
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _JCONF_H
+#define _JCONF_H
+
+#define MAX_PORT_NUM 1024
+#define MAX_REMOTE_NUM 10
+#define MAX_CONF_SIZE 128 * 1024
+#define MAX_DNS_NUM 4
+#define MAX_CONNECT_TIMEOUT 10
+#define MAX_REQUEST_TIMEOUT 60
+#define MIN_UDP_TIMEOUT 10
+
+#define TCP_ONLY     0
+#define TCP_AND_UDP  1
+#define UDP_ONLY     3
+
+typedef struct {
+    char *host;
+    char *port;
+} ss_addr_t;
+
+typedef struct {
+    char *port;
+    char *password;
+} ss_port_password_t;
+
+typedef struct {
+    int remote_num;
+    ss_addr_t remote_addr[MAX_REMOTE_NUM];
+    int port_password_num;
+    ss_port_password_t port_password[MAX_PORT_NUM];
+    char *remote_port;
+    char *local_addr;
+    char *local_port;
+    char *password;
+    char *protocol; // SSR
+    char *protocol_param; // SSR
+    char *method;
+    char *obfs; // SSR
+    char *obfs_param; // SSR
+    char *timeout;
+    char *user;
+    int auth;
+    int fast_open;
+    int nofile;
+    char *nameserver;
+    char *tunnel_address;
+    int mode;
+    int mtu;
+    int mptcp;
+    int ipv6_first;
+} jconf_t;
+
+jconf_t *read_jconf(const char *file);
+void parse_addr(const char *str, ss_addr_t *addr);
+void free_addr(ss_addr_t *addr);
+
+#endif // _JCONF_H
diff --git a/server/json.c b/server/json.c
new file mode 100644
index 0000000..18e95ef
--- /dev/null
+++ b/server/json.c
@@ -0,0 +1,1002 @@
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2012, 2013, 2014 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "json.h"
+#include "utils.h"
+
+#ifdef _MSC_VER
+#ifndef _CRT_SECURE_NO_WARNINGS
+#define _CRT_SECURE_NO_WARNINGS
+#endif
+#endif
+
+#ifdef __cplusplus
+const struct _json_value json_value_none; /* zero-d by ctor */
+#else
+const struct _json_value json_value_none = { NULL, 0, { 0 }, { NULL } };
+#endif
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <math.h>
+
+typedef unsigned short json_uchar;
+
+static unsigned char
+hex_value(json_char c)
+{
+    if (isdigit((uint8_t)c)) {
+        return c - '0';
+    }
+
+    switch (c) {
+    case 'a':
+    case 'A':
+        return 0x0A;
+    case 'b':
+    case 'B':
+        return 0x0B;
+    case 'c':
+    case 'C':
+        return 0x0C;
+    case 'd':
+    case 'D':
+        return 0x0D;
+    case 'e':
+    case 'E':
+        return 0x0E;
+    case 'f':
+    case 'F':
+        return 0x0F;
+    default:
+        return 0xFF;
+    }
+}
+
+typedef struct {
+    unsigned long used_memory;
+
+    unsigned int uint_max;
+    unsigned long ulong_max;
+
+    json_settings settings;
+    int first_pass;
+} json_state;
+
+static void *
+default_alloc(size_t size, int zero, void *user_data)
+{
+    return zero ? calloc(1, size) : ss_malloc(size);
+}
+
+static void
+default_free(void *ptr, void *user_data)
+{
+    ss_free(ptr);
+}
+
+static void *
+json_alloc(json_state *state, unsigned long size, int zero)
+{
+    if ((state->ulong_max - state->used_memory) < size) {
+        return 0;
+    }
+
+    if (state->settings.max_memory
+        && (state->used_memory += size) > state->settings.max_memory) {
+        return 0;
+    }
+
+    return state->settings.mem_alloc(size, zero, state->settings.user_data);
+}
+
+static int
+new_value(json_state *state, json_value **top, json_value **root,
+          json_value **alloc, json_type type)
+{
+    json_value *value;
+    int values_size;
+
+    if (!state->first_pass) {
+        value  = *top = *alloc;
+        *alloc = (*alloc)->_reserved.next_alloc;
+
+        if (!*root) {
+            *root = value;
+        }
+
+        switch (value->type) {
+        case json_array:
+
+            if (!(value->u.array.values = (json_value **)json_alloc
+                                              (state, value->u.array.length *
+                                              sizeof(json_value *), 0))) {
+                return 0;
+            }
+
+            value->u.array.length = 0;
+            break;
+
+        case json_object:
+
+            values_size = sizeof(*value->u.object.values) *
+                          value->u.object.length;
+
+            if (!((*(void **)&value->u.object.values) = json_alloc
+                                                            (state,
+                                                            values_size +
+                                                            ((size_t)value->u.
+                                                             object.values),
+                                                            0))) {
+                return 0;
+            }
+
+            value->_reserved.object_mem = (*(char **)&value->u.object.values) +
+                                          values_size;
+
+            value->u.object.length = 0;
+            break;
+
+        case json_string:
+
+            if (!(value->u.string.ptr = (json_char *)json_alloc
+                                            (state,
+                                            (value->u.string.length +
+                                             1) * sizeof(json_char), 0))) {
+                return 0;
+            }
+
+            value->u.string.length = 0;
+            break;
+
+        default:
+            break;
+        }
+
+        return 1;
+    }
+
+    value = (json_value *)json_alloc(state, sizeof(json_value), 1);
+
+    if (!value) {
+        return 0;
+    }
+
+    if (!*root) {
+        *root = value;
+    }
+
+    value->type   = type;
+    value->parent = *top;
+
+    if (*alloc) {
+        (*alloc)->_reserved.next_alloc = value;
+    }
+
+    *alloc = *top = value;
+
+    return 1;
+}
+
+#define e_off \
+    ((int)(i - cur_line_begin))
+
+#define whitespace                          \
+case '\n': \
+    ++cur_line; cur_line_begin = i; \
+case ' ': \
+case '\t': \
+case '\r'
+
+#define string_add(b)                                         \
+    do { if (!state.first_pass) { string[string_length] = b; \
+         } ++string_length; } while (0)
+
+static const long
+    flag_next           = 1 << 0,
+    flag_reproc         = 1 << 1,
+    flag_need_comma     = 1 << 2,
+    flag_seek_value     = 1 << 3,
+    flag_escaped        = 1 << 4,
+    flag_string         = 1 << 5,
+    flag_need_colon     = 1 << 6,
+    flag_done           = 1 << 7,
+    flag_num_negative   = 1 << 8,
+    flag_num_zero       = 1 << 9,
+    flag_num_e          = 1 << 10,
+    flag_num_e_got_sign = 1 << 11,
+    flag_num_e_negative = 1 << 12,
+    flag_line_comment   = 1 << 13,
+    flag_block_comment  = 1 << 14;
+
+json_value *
+json_parse_ex(json_settings *settings,
+              const json_char *json,
+              size_t length,
+              char *error_buf)
+{
+    json_char error[json_error_max];
+    int cur_line;
+    const json_char *cur_line_begin, *i, *end;
+    json_value *top, *root, *alloc = 0;
+    json_state state = { 0UL, 0U, 0UL, { 0UL, 0, NULL, NULL, NULL }, 0 };
+    long flags;
+    long num_digits = 0, num_e = 0;
+    json_int_t num_fraction = 0;
+
+    /* Skip UTF-8 BOM
+     */
+    if (length >= 3 && ((unsigned char)json[0]) == 0xEF
+        && ((unsigned char)json[1]) == 0xBB
+        && ((unsigned char)json[2]) == 0xBF) {
+        json   += 3;
+        length -= 3;
+    }
+
+    error[0] = '\0';
+    end      = (json + length);
+
+    memcpy(&state.settings, settings, sizeof(json_settings));
+
+    if (!state.settings.mem_alloc) {
+        state.settings.mem_alloc = default_alloc;
+    }
+
+    if (!state.settings.mem_free) {
+        state.settings.mem_free = default_free;
+    }
+
+    memset(&state.uint_max, 0xFF, sizeof(state.uint_max));
+    memset(&state.ulong_max, 0xFF, sizeof(state.ulong_max));
+
+    state.uint_max  -= 8; /* limit of how much can be added before next check */
+    state.ulong_max -= 8;
+
+    for (state.first_pass = 1; state.first_pass >= 0; --state.first_pass) {
+        json_uchar uchar;
+        unsigned char uc_b1, uc_b2, uc_b3, uc_b4;
+        json_char *string          = 0;
+        unsigned int string_length = 0;
+
+        top   = root = 0;
+        flags = flag_seek_value;
+
+        cur_line       = 1;
+        cur_line_begin = json;
+
+        for (i = json;; ++i) {
+            json_char b = (i == end ? 0 : *i);
+
+            if (flags & flag_string) {
+                if (!b) {
+                    sprintf(error, "Unexpected EOF in string (at %d:%d)",
+                            cur_line, e_off);
+                    goto e_failed;
+                }
+
+                if (string_length > state.uint_max) {
+                    goto e_overflow;
+                }
+
+                if (flags & flag_escaped) {
+                    flags &= ~flag_escaped;
+
+                    switch (b) {
+                    case 'b':
+                        string_add('\b');
+                        break;
+                    case 'f':
+                        string_add('\f');
+                        break;
+                    case 'n':
+                        string_add('\n');
+                        break;
+                    case 'r':
+                        string_add('\r');
+                        break;
+                    case 't':
+                        string_add('\t');
+                        break;
+                    case 'u':
+
+                        if (end - i < 4 ||
+                            (uc_b1 = hex_value(*++i)) == 0xFF ||
+                            (uc_b2 = hex_value(*++i)) == 0xFF
+                            || (uc_b3 = hex_value(*++i)) == 0xFF ||
+                            (uc_b4 = hex_value(*++i)) == 0xFF) {
+                            sprintf(error,
+                                    "Invalid character value `%c` (at %d:%d)",
+                                    b, cur_line, e_off);
+                            goto e_failed;
+                        }
+
+                        uc_b1 = uc_b1 * 16 + uc_b2;
+                        uc_b2 = uc_b3 * 16 + uc_b4;
+
+                        uchar = ((json_char)uc_b1) * 256 + uc_b2;
+
+                        if (sizeof(json_char) >= sizeof(json_uchar) ||
+                            (uc_b1 == 0 && uc_b2 <= 0x7F)) {
+                            string_add((json_char)uchar);
+                            break;
+                        }
+
+                        if (uchar <= 0x7FF) {
+                            if (state.first_pass) {
+                                string_length += 2;
+                            } else {
+                                string[string_length++] = 0xC0 |
+                                                          ((uc_b2 &
+                                                            0xC0) >>
+                                                           6) |
+                                                          ((uc_b1 & 0x7) << 2);
+                                string[string_length++] = 0x80 |
+                                                          (uc_b2 & 0x3F);
+                            }
+
+                            break;
+                        }
+
+                        if (state.first_pass) {
+                            string_length += 3;
+                        } else {
+                            string[string_length++] = 0xE0 |
+                                                      ((uc_b1 & 0xF0) >> 4);
+                            string[string_length++] = 0x80 |
+                                                      ((uc_b1 &
+                                                        0xF) <<
+                                                       2) |
+                                                      ((uc_b2 & 0xC0) >> 6);
+                            string[string_length++] = 0x80 | (uc_b2 & 0x3F);
+                        }
+
+                        break;
+
+                    default:
+                        string_add(b);
+                    }
+
+                    continue;
+                }
+
+                if (b == '\\') {
+                    flags |= flag_escaped;
+                    continue;
+                }
+
+                if (b == '"') {
+                    if (!state.first_pass) {
+                        string[string_length] = 0;
+                    }
+
+                    flags &= ~flag_string;
+                    string = 0;
+
+                    switch (top->type) {
+                    case json_string:
+
+                        top->u.string.length = string_length;
+                        flags               |= flag_next;
+
+                        break;
+
+                    case json_object:
+
+                        if (state.first_pass) {
+                            (*(json_char **)&top->u.object.values) +=
+                                string_length + 1;
+                        } else {
+                            top->u.object.values[top->u.object.length].name
+                                = (json_char *)top->_reserved.object_mem;
+
+                            top->u.object.values[top->u.object.length].
+                            name_length
+                                = string_length;
+
+                            (*(json_char **)&top->_reserved.object_mem) +=
+                                string_length + 1;
+                        }
+
+                        flags |= flag_seek_value | flag_need_colon;
+                        continue;
+
+                    default:
+                        break;
+                    }
+                } else {
+                    string_add(b);
+                    continue;
+                }
+            }
+
+            if (state.settings.settings & json_enable_comments) {
+                if (flags & (flag_line_comment | flag_block_comment)) {
+                    if (flags & flag_line_comment) {
+                        if (b == '\r' || b == '\n' || !b) {
+                            flags &= ~flag_line_comment;
+                            --i;   /* so null can be reproc'd */
+                        }
+
+                        continue;
+                    }
+
+                    if (flags & flag_block_comment) {
+                        if (!b) {
+                            sprintf(error,
+                                    "%d:%d: Unexpected EOF in block comment",
+                                    cur_line, e_off);
+                            goto e_failed;
+                        }
+
+                        if (b == '*' && i < (end - 1) && i[1] == '/') {
+                            flags &= ~flag_block_comment;
+                            ++i;   /* skip closing sequence */
+                        }
+
+                        continue;
+                    }
+                } else if (b == '/') {
+                    if (!(flags & (flag_seek_value | flag_done)) && top->type !=
+                        json_object) {
+                        sprintf(error, "%d:%d: Comment not allowed here",
+                                cur_line, e_off);
+                        goto e_failed;
+                    }
+
+                    if (++i == end) {
+                        sprintf(error, "%d:%d: EOF unexpected", cur_line,
+                                e_off);
+                        goto e_failed;
+                    }
+
+                    switch (b = *i) {
+                    case '/':
+                        flags |= flag_line_comment;
+                        continue;
+
+                    case '*':
+                        flags |= flag_block_comment;
+                        continue;
+
+                    default:
+                        sprintf(error,
+                                "%d:%d: Unexpected `%c` in comment opening sequence", cur_line, e_off,
+                                b);
+                        goto e_failed;
+                    }
+                }
+            }
+
+            if (flags & flag_done) {
+                if (!b) {
+                    break;
+                }
+
+                switch (b) {
+whitespace:
+                    continue;
+
+                default:
+                    sprintf(error, "%d:%d: Trailing garbage: `%c`", cur_line,
+                            e_off, b);
+                    goto e_failed;
+                }
+            }
+
+            if (flags & flag_seek_value) {
+                switch (b) {
+whitespace:
+                    continue;
+
+                case ']':
+
+                    if (top->type == json_array) {
+                        flags =
+                            (flags &
+                             ~(flag_need_comma | flag_seek_value)) | flag_next;
+                    } else {
+                        sprintf(error, "%d:%d: Unexpected ]", cur_line, e_off);
+                        goto e_failed;
+                    }
+
+                    break;
+
+                default:
+
+                    if (flags & flag_need_comma) {
+                        if (b == ',') {
+                            flags &= ~flag_need_comma;
+                            continue;
+                        } else {
+                            sprintf(error, "%d:%d: Expected , before %c",
+                                    cur_line, e_off, b);
+                            goto e_failed;
+                        }
+                    }
+
+                    if (flags & flag_need_colon) {
+                        if (b == ':') {
+                            flags &= ~flag_need_colon;
+                            continue;
+                        } else {
+                            sprintf(error, "%d:%d: Expected : before %c",
+                                    cur_line, e_off, b);
+                            goto e_failed;
+                        }
+                    }
+
+                    flags &= ~flag_seek_value;
+
+                    switch (b) {
+                    case '{':
+
+                        if (!new_value(&state, &top, &root, &alloc,
+                                       json_object)) {
+                            goto e_alloc_failure;
+                        }
+
+                        continue;
+
+                    case '[':
+
+                        if (!new_value(&state, &top, &root, &alloc,
+                                       json_array)) {
+                            goto e_alloc_failure;
+                        }
+
+                        flags |= flag_seek_value;
+                        continue;
+
+                    case '"':
+
+                        if (!new_value(&state, &top, &root, &alloc,
+                                       json_string)) {
+                            goto e_alloc_failure;
+                        }
+
+                        flags |= flag_string;
+
+                        string        = top->u.string.ptr;
+                        string_length = 0;
+
+                        continue;
+
+                    case 't':
+
+                        if ((end - i) < 3 || *(++i) != 'r' || *(++i) != 'u' ||
+                            *(++i) != 'e') {
+                            goto e_unknown_value;
+                        }
+
+                        if (!new_value(&state, &top, &root, &alloc,
+                                       json_boolean)) {
+                            goto e_alloc_failure;
+                        }
+
+                        top->u.boolean = 1;
+
+                        flags |= flag_next;
+                        break;
+
+                    case 'f':
+
+                        if ((end - i) < 4 || *(++i) != 'a' || *(++i) != 'l' ||
+                            *(++i) != 's' || *(++i) != 'e') {
+                            goto e_unknown_value;
+                        }
+
+                        if (!new_value(&state, &top, &root, &alloc,
+                                       json_boolean)) {
+                            goto e_alloc_failure;
+                        }
+
+                        flags |= flag_next;
+                        break;
+
+                    case 'n':
+
+                        if ((end - i) < 3 || *(++i) != 'u' || *(++i) != 'l' ||
+                            *(++i) != 'l') {
+                            goto e_unknown_value;
+                        }
+
+                        if (!new_value(&state, &top, &root, &alloc,
+                                       json_null)) {
+                            goto e_alloc_failure;
+                        }
+
+                        flags |= flag_next;
+                        break;
+
+                    default:
+
+                        if (isdigit((uint8_t)b) || b == '-') {
+                            if (!new_value(&state, &top, &root, &alloc,
+                                           json_integer)) {
+                                goto e_alloc_failure;
+                            }
+
+                            if (!state.first_pass) {
+                                while (isdigit((uint8_t)b) || b == '+' || b ==
+                                       '-'
+                                       || b == 'e' || b == 'E' || b == '.') {
+                                    if ((++i) == end) {
+                                        b = 0;
+                                        break;
+                                    }
+
+                                    b = *i;
+                                }
+
+                                flags |= flag_next | flag_reproc;
+                                break;
+                            }
+
+                            flags &= ~(flag_num_negative | flag_num_e |
+                                       flag_num_e_got_sign |
+                                       flag_num_e_negative |
+                                       flag_num_zero);
+
+                            num_digits   = 0;
+                            num_fraction = 0;
+                            num_e        = 0;
+
+                            if (b != '-') {
+                                flags |= flag_reproc;
+                                break;
+                            }
+
+                            flags |= flag_num_negative;
+                            continue;
+                        } else {
+                            sprintf(error,
+                                    "%d:%d: Unexpected %c when seeking value",
+                                    cur_line, e_off, b);
+                            goto e_failed;
+                        }
+                    }
+                }
+            } else {
+                switch (top->type) {
+                case json_object:
+
+                    switch (b) {
+whitespace:
+                        continue;
+
+                    case '"':
+
+                        if (flags & flag_need_comma) {
+                            sprintf(error, "%d:%d: Expected , before \"",
+                                    cur_line, e_off);
+                            goto e_failed;
+                        }
+
+                        flags |= flag_string;
+
+                        string        = (json_char *)top->_reserved.object_mem;
+                        string_length = 0;
+
+                        break;
+
+                    case '}':
+
+                        flags = (flags & ~flag_need_comma) | flag_next;
+                        break;
+
+                    case ',':
+
+                        if (flags & flag_need_comma) {
+                            flags &= ~flag_need_comma;
+                            break;
+                        }
+
+                    default:
+
+                        sprintf(error, "%d:%d: Unexpected `%c` in object",
+                                cur_line, e_off, b);
+                        goto e_failed;
+                    }
+
+                    break;
+
+                case json_integer:
+                case json_double:
+
+                    if (isdigit((uint8_t)b)) {
+                        ++num_digits;
+
+                        if (top->type == json_integer || flags & flag_num_e) {
+                            if (!(flags & flag_num_e)) {
+                                if (flags & flag_num_zero) {
+                                    sprintf(error,
+                                            "%d:%d: Unexpected `0` before `%c`",
+                                            cur_line, e_off, b);
+                                    goto e_failed;
+                                }
+
+                                if (num_digits == 1 && b == '0') {
+                                    flags |= flag_num_zero;
+                                }
+                            } else {
+                                flags |= flag_num_e_got_sign;
+                                num_e  = (num_e * 10) + (b - '0');
+                                continue;
+                            }
+
+                            top->u.integer = (top->u.integer * 10) + (b - '0');
+                            continue;
+                        }
+
+                        num_fraction = (num_fraction * 10) + (b - '0');
+                        continue;
+                    }
+
+                    if (b == '+' || b == '-') {
+                        if ((flags & flag_num_e) &&
+                            !(flags & flag_num_e_got_sign)) {
+                            flags |= flag_num_e_got_sign;
+
+                            if (b == '-') {
+                                flags |= flag_num_e_negative;
+                            }
+
+                            continue;
+                        }
+                    } else if (b == '.' && top->type == json_integer) {
+                        if (!num_digits) {
+                            sprintf(error, "%d:%d: Expected digit before `.`",
+                                    cur_line, e_off);
+                            goto e_failed;
+                        }
+
+                        top->type  = json_double;
+                        top->u.dbl = (double)top->u.integer;
+
+                        num_digits = 0;
+                        continue;
+                    }
+
+                    if (!(flags & flag_num_e)) {
+                        if (top->type == json_double) {
+                            if (!num_digits) {
+                                sprintf(error,
+                                        "%d:%d: Expected digit after `.`",
+                                        cur_line, e_off);
+                                goto e_failed;
+                            }
+
+                            top->u.dbl += ((double)num_fraction) /
+                                          (pow(10, (double)num_digits));
+                        }
+
+                        if (b == 'e' || b == 'E') {
+                            flags |= flag_num_e;
+
+                            if (top->type == json_integer) {
+                                top->type  = json_double;
+                                top->u.dbl = (double)top->u.integer;
+                            }
+
+                            num_digits = 0;
+                            flags     &= ~flag_num_zero;
+
+                            continue;
+                        }
+                    } else {
+                        if (!num_digits) {
+                            sprintf(error, "%d:%d: Expected digit after `e`",
+                                    cur_line, e_off);
+                            goto e_failed;
+                        }
+
+                        top->u.dbl *=
+                            pow(10,
+                                (double)((flags &
+                                         flag_num_e_negative) ? -num_e : num_e));
+                    }
+
+                    if (flags & flag_num_negative) {
+                        if (top->type == json_integer) {
+                            top->u.integer = -top->u.integer;
+                        } else {
+                            top->u.dbl = -top->u.dbl;
+                        }
+                    }
+
+                    flags |= flag_next | flag_reproc;
+                    break;
+
+                default:
+                    break;
+                }
+            }
+
+            if (flags & flag_reproc) {
+                flags &= ~flag_reproc;
+                --i;
+            }
+
+            if (flags & flag_next) {
+                flags = (flags & ~flag_next) | flag_need_comma;
+
+                if (!top->parent) {
+                    /* root value done */
+
+                    flags |= flag_done;
+                    continue;
+                }
+
+                if (top->parent->type == json_array) {
+                    flags |= flag_seek_value;
+                }
+
+                if (!state.first_pass) {
+                    json_value *parent = top->parent;
+
+                    switch (parent->type) {
+                    case json_object:
+
+                        parent->u.object.values
+                        [parent->u.object.length].value = top;
+
+                        break;
+
+                    case json_array:
+
+                        parent->u.array.values
+                        [parent->u.array.length] = top;
+
+                        break;
+
+                    default:
+                        break;
+                    }
+                }
+
+                if ((++top->parent->u.array.length) > state.uint_max) {
+                    goto e_overflow;
+                }
+
+                top = top->parent;
+
+                continue;
+            }
+        }
+
+        alloc = root;
+    }
+
+    return root;
+
+e_unknown_value:
+
+    sprintf(error, "%d:%d: Unknown value", cur_line, e_off);
+    goto e_failed;
+
+e_alloc_failure:
+
+    strcpy(error, "Memory allocation failure");
+    goto e_failed;
+
+e_overflow:
+
+    sprintf(error, "%d:%d: Too long (caught overflow)", cur_line, e_off);
+    goto e_failed;
+
+e_failed:
+
+    if (error_buf) {
+        if (*error) {
+            strcpy(error_buf, error);
+        } else {
+            strcpy(error_buf, "Unknown error");
+        }
+    }
+
+    if (state.first_pass) {
+        alloc = root;
+    }
+
+    while (alloc) {
+        top = alloc->_reserved.next_alloc;
+        state.settings.mem_free(alloc, state.settings.user_data);
+        alloc = top;
+    }
+
+    if (!state.first_pass) {
+        json_value_free_ex(&state.settings, root);
+    }
+
+    return 0;
+}
+
+json_value *
+json_parse(const json_char *json, size_t length)
+{
+    json_settings settings = { 0UL, 0, NULL, NULL, NULL };
+    return json_parse_ex(&settings, json, length, 0);
+}
+
+void
+json_value_free_ex(json_settings *settings, json_value *value)
+{
+    json_value *cur_value;
+
+    if (!value) {
+        return;
+    }
+
+    value->parent = 0;
+
+    while (value) {
+        switch (value->type) {
+        case json_array:
+
+            if (!value->u.array.length) {
+                settings->mem_free(value->u.array.values, settings->user_data);
+                break;
+            }
+
+            value = value->u.array.values[--value->u.array.length];
+            continue;
+
+        case json_object:
+
+            if (!value->u.object.length) {
+                settings->mem_free(value->u.object.values, settings->user_data);
+                break;
+            }
+
+            value = value->u.object.values[--value->u.object.length].value;
+            continue;
+
+        case json_string:
+
+            settings->mem_free(value->u.string.ptr, settings->user_data);
+            break;
+
+        default:
+            break;
+        }
+
+        cur_value = value;
+        value     = value->parent;
+        settings->mem_free(cur_value, settings->user_data);
+    }
+}
+
+void
+json_value_free(json_value *value)
+{
+    json_settings settings = { 0UL, 0, NULL, NULL, NULL };
+    settings.mem_free = default_free;
+    json_value_free_ex(&settings, value);
+}
diff --git a/server/json.h b/server/json.h
new file mode 100644
index 0000000..016fc5a
--- /dev/null
+++ b/server/json.h
@@ -0,0 +1,249 @@
+/* vim: set et ts=3 sw=3 sts=3 ft=c:
+ *
+ * Copyright (C) 2012, 2013, 2014 James McLaughlin et al.  All rights reserved.
+ * https://github.com/udp/json-parser
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 _JSON_H
+#define _JSON_H
+
+#ifndef json_char
+#define json_char char
+#endif
+
+#ifndef json_int_t
+#ifndef _MSC_VER
+#include <inttypes.h>
+#define json_int_t int64_t
+#else
+#define json_int_t __int64
+#endif
+#endif
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+
+#include <string.h>
+
+extern "C"
+{
+#endif
+
+typedef struct {
+    unsigned long max_memory;
+    int settings;
+
+    /* Custom allocator support (leave null to use malloc/free)
+     */
+
+    void * (*mem_alloc)(size_t, int zero, void *user_data);
+    void (*mem_free)(void *, void *user_data);
+
+    void *user_data;       /* will be passed to mem_alloc and mem_free */
+} json_settings;
+
+#define json_enable_comments  0x01
+
+typedef enum {
+    json_none,
+    json_object,
+    json_array,
+    json_integer,
+    json_double,
+    json_string,
+    json_boolean,
+    json_null
+} json_type;
+
+extern const struct _json_value json_value_none;
+
+typedef struct _json_value {
+    struct _json_value *parent;
+
+    json_type type;
+
+    union {
+        int boolean;
+        json_int_t integer;
+        double dbl;
+
+        struct {
+            unsigned int length;
+            json_char *ptr;      /* null terminated */
+        } string;
+
+        struct {
+            unsigned int length;
+
+            struct {
+                json_char *name;
+                unsigned int name_length;
+
+                struct _json_value *value;
+            } *values;
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+            decltype(values) begin() const
+            {
+                return values;
+            }
+            decltype(values) end() const
+            {
+                return values + length;
+            }
+#endif
+        } object;
+
+        struct {
+            unsigned int length;
+            struct _json_value **values;
+
+#if defined(__cplusplus) && __cplusplus >= 201103L
+            decltype(values) begin() const
+            {
+                return values;
+            }
+            decltype(values) end() const
+            {
+                return values + length;
+            }
+#endif
+        } array;
+    } u;
+
+    union {
+        struct _json_value *next_alloc;
+        void *object_mem;
+    } _reserved;
+
+    /* Some C++ operator sugar */
+
+#ifdef __cplusplus
+
+public:
+
+    inline _json_value(){
+        memset(this, 0, sizeof(_json_value));
+    }
+
+    inline const struct _json_value &operator [] (int index) const {
+        if (type != json_array || index < 0
+            || ((unsigned int)index) >= u.array.length) {
+            return json_value_none;
+        }
+
+        return *u.array.values[index];
+    }
+
+    inline const struct _json_value &operator [] (const char *index) const {
+        if (type != json_object) {
+            return json_value_none;
+        }
+
+        for (unsigned int i = 0; i < u.object.length; ++i)
+            if (!strcmp(u.object.values[i].name, index)) {
+                return *u.object.values[i].value;
+            }
+
+        return json_value_none;
+    }
+
+    inline operator const char * () const
+    {
+        switch (type) {
+        case json_string:
+            return u.string.ptr;
+
+        default:
+            return "";
+        }
+    }
+
+    inline operator
+    json_int_t() const
+    {
+        switch (type) {
+        case json_integer:
+            return u.integer;
+
+        case json_double:
+            return (json_int_t)u.dbl;
+
+        default:
+            return 0;
+        }
+    }
+
+    inline operator
+    bool() const
+    {
+        if (type != json_boolean) {
+            return false;
+        }
+
+        return u.boolean != 0;
+    }
+
+    inline operator double () const
+    {
+        switch (type) {
+        case json_integer:
+            return (double)u.integer;
+
+        case json_double:
+            return u.dbl;
+
+        default:
+            return 0;
+        }
+    }
+
+#endif
+} json_value;
+
+json_value *json_parse(const json_char *json,
+                       size_t length);
+
+#define json_error_max 128
+json_value *json_parse_ex(json_settings *settings,
+                          const json_char *json,
+                          size_t length,
+                          char *error);
+
+void json_value_free(json_value *);
+
+/* Not usually necessary, unless you used a custom mem_alloc and now want to
+ * use a custom mem_free.
+ */
+void json_value_free_ex(json_settings *settings,
+                        json_value *);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif
diff --git a/server/list.c b/server/list.c
new file mode 100644
index 0000000..dde085d
--- /dev/null
+++ b/server/list.c
@@ -0,0 +1,370 @@
+#include "list.h"
+
+/// 文件：list_impl.c
+/// 功能：实现链表的基本操作
+/// 作者：bluewind
+/// 完成时间：2011.5.29
+/// 修改时间：2011.5.31, 2011.7.2
+/// 修改备注：在头节点处添加一个空节点，可以优化添加、删除节点代码
+///  再次修改，链表增加节点数据data_size，限制数据大小，修改了
+///  添加复制数据代码，修正重复添加节点后释放节点的Bug，添加了前
+///  插、排序和遍历功能，7.3 添加tail尾指针，改进后插法性能，并改名
+/// --------------------------------------------------------------
+
+void swap_data(Node n1, Node n2);
+
+/// --------------------------------------------------------------
+//  函数名：list_init
+//  功能：  链表初始化
+//  参数：  无
+//  返回值：已初始化链表指针
+//  备注：  链表本身动态分配，由list_destroy函数管理释放
+/// --------------------------------------------------------------
+List list_init(unsigned int data_size)
+{
+    List list = (List) malloc(sizeof(struct clist));
+    if(list != NULL)                                        //内存分配成功
+    {
+        list->head = (Node) malloc(sizeof(node));           //为头节点分配内存
+        if(list->head)          //内存分配成功
+        {
+            list->head->data = NULL;      //初始化头节点
+            list->head->next = NULL;
+            list->data_size  = data_size;
+            list->tail = list->head;
+            list->size = 0;
+
+            list->add_back  = list_add_back;   //初始化成员函数
+            list->add_front  = list_add_front;
+            list->delete_node = list_delete_node;
+            list->delete_at  = list_delete_at;
+            list->modify_at  = list_modify_at;
+            list->have_same  = list_have_same;
+            list->have_same_cmp  = list_have_same_cmp;
+            list->foreach  = list_foreach;
+            list->clear   = list_clear;
+            list->sort   = list_sort;
+            list->destroy  = list_destroy;
+        }
+    }
+    return list;
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_add_back
+//  功能：  添加链表结点 (后插法)
+//  参数：  l--链表指针，data--链表数据指针，可为任意类型
+//  返回值：int型，为1表示添加成功，为0表示添加失败
+//  备注：  如果链表本身为空或是分配节点内存失败，将返回0
+/// --------------------------------------------------------------
+int  list_add_back(List l, void *data)
+{
+    Node new_node = (Node) malloc(sizeof(node));
+
+    if(l != NULL && new_node != NULL)  //链表本身不为空，且内存申请成功
+    {
+        new_node->data = malloc(l->data_size);
+        memcpy(new_node->data, data, l->data_size);
+        new_node->next = NULL;
+
+        l->tail->next = new_node;   //添加节点
+        l->tail = new_node;     //记录尾节点位置
+        l->size ++;       //链表元素总数加1
+
+        return 1;
+    }
+
+    return 0;
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_add_front
+//  功能：  添加链表结点 (前插法)
+//  参数：  l--链表指针，data--链表数据指针，可为任意类型
+//  返回值：int型，为1表示添加成功，为0表示添加失败
+//  备注：  如果链表本身为空或是分配节点内存失败，将返回0
+/// --------------------------------------------------------------
+int list_add_front(List l, void *data)
+{
+    Node new_node = (Node) malloc(sizeof(node));
+
+    if(l != NULL && new_node != NULL)
+    {
+        new_node->data = malloc(l->data_size);
+        memcpy(new_node->data, data, l->data_size);
+        new_node->next = l->head->next;
+
+        l->head->next = new_node;
+        if(!l->size)        //记录尾指针位置
+            l->tail = new_node;
+        l->size ++;
+
+        return 1;
+    }
+
+    return 0;
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_delete_node
+//  功能：删除链表结点
+//  参数：l--链表指针，data--链表数据指针，可为任意类型
+//        *pfunc为指向一个数据类型比较的函数指针
+//  返回值：int型，为1表示删除成功，为0表示没有找到匹配数据
+//  备注：*pfunc函数接口参数ndata为节点数据，data为比较数据，返回为真表示匹配数据
+/// --------------------------------------------------------------
+int  list_delete_node(List l, void *data, int (*pfunc)(void *ndata, void *data))
+{
+    if(l != NULL)
+    {
+        Node prev = l->head;      //前一个节点
+        Node curr = l->head->next;     //当前节点
+
+        while(curr != NULL)
+        {
+            if(pfunc(curr->data, data))    //如果找到匹配数据
+            {
+                if(curr == l->tail)     //如果是删除尾节点
+                 l->tail = prev;
+
+                prev->next = prev->next->next;  //修改前节点next指针指向下下个节点
+
+                free(curr->data);     //释放节点数据
+                free(curr);       //释放节点
+
+                l->size--;       //链表元素总数减1
+                return 1;       //返回真值
+            }
+            prev = prev->next;      //没有找到匹配时移动前节点和当前节点
+            curr = curr->next;
+        }
+    }
+
+ return 0;         //没有找到匹配数据
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_delete_at
+//  功能：  修改链表节点元素值
+//  参数：  l--链表指针，index--索引值, 范围(0 -- size-1)
+//  返回值：int型，为1表示删除成功，为0表示删除失败
+//  备注：  如果链表本身为空或是index为非法值，将返回0
+/// --------------------------------------------------------------
+int list_delete_at(List l, unsigned int index)
+{
+    unsigned int cindex = 0;
+
+    if(l != NULL && index >= 0 && index < l->size)
+    {
+        Node prev = l->head;      //前一个节点
+        Node curr = l->head->next;     //当前节点
+
+        while(cindex != index)
+        {
+            prev = prev->next;
+            curr = curr->next;
+            cindex ++;
+        }
+
+        if(index == (l->size) - 1)
+            l->tail = prev;
+
+        prev->next = prev->next->next;
+        free(curr->data);
+        free(curr);
+        l->size --;
+
+        return 1;
+    }
+
+    return 0;
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_modify_at
+//  功能：  修改链表节点元素值
+//  参数：  l--链表指针，index--索引值, 范围(0 -- size-1)
+//   data--链表数据指针
+//  返回值：int型，为1表示修改成功，为0表示修改失败
+//  备注：  如果链表本身为空或是index为非法值，将返回0
+/// --------------------------------------------------------------
+int list_modify_at(List l, unsigned int index, void *new_data)
+{
+    unsigned int cindex = 0;
+
+    if(l != NULL && index >= 0 && index < l->size )  //非空链表，并且index值合法
+    {
+        Node curr = l->head->next;
+        while(cindex != index)
+        {
+            curr = curr->next;
+            cindex ++;
+        }
+        memcpy(curr->data, new_data, l->data_size);
+        return 1;
+    }
+
+    return 0;
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_sort
+//  功能：  链表排序
+//  参数：  l--链表指针，*pfunc为指向一个数据类型比较的函数指针
+//  返回值：无
+//  备注：  使用简单选择排序法，相比冒泡法每次交换，效率高一点
+/// --------------------------------------------------------------
+void list_sort(List l, compare pfunc)
+{
+    if(l != NULL)
+    {
+        Node min, icurr, jcurr;
+
+        icurr = l->head->next;
+        while(icurr)
+        {
+            min = icurr;        //记录最小值
+            jcurr = icurr->next;      //内循环指向下一个节点
+            while(jcurr)
+            {
+                if(pfunc(min->data, jcurr->data))  //如果找到n+1到最后一个元素最小值
+                    min = jcurr;      //记录下最小值的位置
+
+                jcurr = jcurr->next;
+            }
+
+            if(min != icurr)       //当最小值位置和n+1元素位置不相同时
+            {
+                swap_data(min, icurr);     //才进行交换，减少交换次数
+            }
+
+            icurr = icurr->next;
+        }
+    }
+}
+
+void swap_data(Node n1, Node n2)
+{
+    void *temp;
+
+    temp = n2->data;
+    n2->data = n1->data;
+    n1->data = temp;
+}
+
+
+int list_have_same(List l, void *data, int (*pfunc)(void *ndata, void *data))
+{
+    if(l != NULL)
+    {
+        Node curr;
+
+        for(curr = l->head->next; curr != NULL; curr = curr->next)
+        {
+            if(pfunc(curr->data, data))
+            {
+                return 1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+int list_have_same_cmp(List l, void *data)
+{
+    if(l != NULL)
+    {
+        Node curr;
+
+        for(curr = l->head->next; curr != NULL; curr = curr->next)
+        {
+            if(memcmp(curr->data, data, l->data_size))
+            {
+                return 1;
+            }
+        }
+    }
+
+    return 0;
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_foreach
+//  功能：  遍历链表元素
+//  参数：  l--链表指针，doit为指向一个处理数据的函数指针
+//  返回值：无
+//  备注：  doit申明为void (*dofunc)(void *ndata)原型
+/// --------------------------------------------------------------
+void list_foreach(List l, dofunc doit)
+{
+    if(l != NULL)
+    {
+        Node curr;
+
+        for(curr = l->head->next; curr != NULL; curr = curr->next)
+        {
+            doit(curr->data);
+        }
+    }
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_clear
+//  功能：  清空链表元素
+//  参数：  l--链表指针
+//  返回值：无
+// 备注： 没有使用先Destroy再Init链表的办法，直接实现
+/// --------------------------------------------------------------
+void list_clear(List l)
+{
+    if(l != NULL)
+    {
+        Node temp;
+        Node curr = l->head->next;
+
+        while(curr != NULL)
+        {
+            temp = curr->next;
+
+            free(curr->data);    //释放节点和数据
+            free(curr);
+
+            curr = temp;
+        }
+
+        l->size = 0;      //重置链表数据
+        l->head->next = NULL;
+        l->tail = l->head;
+    }
+}
+
+/// --------------------------------------------------------------
+//  函数名：list_destroy
+//  功能：  释放链表
+//  参数：  l--链表指针
+//  返回值：空链表指针
+/// --------------------------------------------------------------
+List list_destroy(List l)
+{
+    if(l != NULL)
+    {
+        Node temp;
+
+        while(l->head)
+        {
+            temp = l->head->next;
+
+            if(l->head->data != NULL)   //如果是头节点就不释放数据空间
+            free(l->head->data);   //先释放节点数据(但是节点数据里也有指针？)
+            free(l->head);      //再释放节点
+
+            l->head = temp;
+        }
+
+        free(l);        //释放链表本身占用空间
+        l = NULL;
+    }
+
+    return l;
+}
diff --git a/server/list.h b/server/list.h
new file mode 100644
index 0000000..ab49720
--- /dev/null
+++ b/server/list.h
@@ -0,0 +1,61 @@
+#ifndef LIST_H_H
+#define LIST_H_H
+
+#include <stdio.h>
+#include <malloc.h>
+#include <string.h>
+
+typedef struct clist *List;
+
+typedef int (*compare)(void *ndata, void *data);
+typedef void (*dofunc)(void *ndata);
+
+typedef int (*lpf0)(List l, void *data);
+typedef int (*lpf1)(List l, void *data, compare pfunc);
+typedef List (*lpf2)(List l);
+typedef void (*lpf3)(List l);
+typedef void (*lpf4)(List l, dofunc pfunc);
+typedef int (*lpf5)(List l, unsigned int index, void *new_data);
+typedef void (*lpf6)(List l, compare pfunc);
+typedef int (*lpf7)(List l, unsigned int index);
+
+typedef struct cnode
+{
+    void *data;
+    struct cnode *next;
+}node, *Node;
+
+typedef struct clist
+{
+    Node head;
+    Node tail;
+    unsigned int size;
+    unsigned int data_size;
+    lpf0 add_back;
+    lpf0 add_front;
+    lpf1 delete_node;
+    lpf1 have_same;
+    lpf0 have_same_cmp;
+    lpf4 foreach;
+    lpf3 clear;
+    lpf2 destroy;
+    lpf5 modify_at;
+    lpf6 sort;
+    lpf7 delete_at;
+}list;
+
+//初始化链表
+List list_init(unsigned int data_size);
+int  list_add_back(List l, void *data);
+int  list_add_front(List l, void *data);
+int  list_delete_node(List l, void *data, compare pfunc);
+int  list_delete_at(List l, unsigned int index);
+int  list_modify_at(List l, unsigned int index, void *new_data);
+int  list_have_same(List l, void *data, compare pfunc);
+int  list_have_same_cmp(List l, void *data);
+void list_foreach(List l, dofunc doit);
+void list_sort(List l, compare pfunc);
+void list_clear(List l);
+//释放链表
+List list_destroy(List l);
+#endif
diff --git a/server/netutils.c b/server/netutils.c
new file mode 100644
index 0000000..3a32b4d
--- /dev/null
+++ b/server/netutils.c
@@ -0,0 +1,297 @@
+/*
+ * netutils.c - Network utilities
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <math.h>
+
+#include <libcork/core.h>
+#include <udns.h>
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#ifdef __MINGW32__
+#include "win32.h"
+#define sleep(n) Sleep(1000 * (n))
+#else
+#include <sys/socket.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <unistd.h>
+#endif
+
+#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
+#include <net/if.h>
+#include <sys/ioctl.h>
+#define SET_INTERFACE
+#endif
+
+#include "netutils.h"
+#include "utils.h"
+
+#ifndef SO_REUSEPORT
+#define SO_REUSEPORT 15
+#endif
+
+extern int verbose;
+
+static const char valid_label_bytes[] =
+    "-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz";
+
+#if defined(MODULE_LOCAL)
+extern int keep_resolving;
+#endif
+
+int
+set_reuseport(int socket)
+{
+    int opt = 1;
+    return setsockopt(socket, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
+}
+
+size_t
+get_sockaddr_len(struct sockaddr *addr)
+{
+    if (addr->sa_family == AF_INET) {
+        return sizeof(struct sockaddr_in);
+    } else if (addr->sa_family == AF_INET6) {
+        return sizeof(struct sockaddr_in6);
+    }
+    return 0;
+}
+
+#ifdef SET_INTERFACE
+int
+setinterface(int socket_fd, const char *interface_name)
+{
+    struct ifreq interface;
+    memset(&interface, 0, sizeof(struct ifreq));
+    strncpy(interface.ifr_name, interface_name, IFNAMSIZ);
+    int res = setsockopt(socket_fd, SOL_SOCKET, SO_BINDTODEVICE, &interface,
+                         sizeof(struct ifreq));
+    return res;
+}
+
+#endif
+
+int
+bind_to_address(int socket_fd, const char *host)
+{
+    if (host != NULL) {
+        struct cork_ip ip;
+        struct sockaddr_storage storage;
+        memset(&storage, 0, sizeof(struct sockaddr_storage));
+        if (cork_ip_init(&ip, host) != -1) {
+            if (ip.version == 4) {
+                struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
+                dns_pton(AF_INET, host, &addr->sin_addr);
+                addr->sin_family = AF_INET;
+                return bind(socket_fd, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
+            } else if (ip.version == 6) {
+                struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
+                dns_pton(AF_INET6, host, &addr->sin6_addr);
+                addr->sin6_family = AF_INET6;
+                return bind(socket_fd, (struct sockaddr *)addr, sizeof(struct sockaddr_in6));
+            }
+        }
+    }
+    return -1;
+}
+
+ssize_t
+get_sockaddr(char *host, char *port,
+             struct sockaddr_storage *storage, int block,
+             int ipv6first)
+{
+    struct cork_ip ip;
+    if (cork_ip_init(&ip, host) != -1) {
+        if (ip.version == 4) {
+            struct sockaddr_in *addr = (struct sockaddr_in *)storage;
+            addr->sin_family = AF_INET;
+            dns_pton(AF_INET, host, &(addr->sin_addr));
+            if (port != NULL) {
+                addr->sin_port = htons(atoi(port));
+            }
+        } else if (ip.version == 6) {
+            struct sockaddr_in6 *addr = (struct sockaddr_in6 *)storage;
+            addr->sin6_family = AF_INET6;
+            dns_pton(AF_INET6, host, &(addr->sin6_addr));
+            if (port != NULL) {
+                addr->sin6_port = htons(atoi(port));
+            }
+        }
+        return 0;
+    } else {
+        struct addrinfo hints;
+        struct addrinfo *result, *rp;
+
+        memset(&hints, 0, sizeof(struct addrinfo));
+        hints.ai_family   = AF_UNSPEC;   /* Return IPv4 and IPv6 choices */
+        hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */
+
+        int err, i;
+
+        for (i = 1; i < 8; i++) {
+            err = getaddrinfo(host, port, &hints, &result);
+#if defined(MODULE_LOCAL)
+            if (!keep_resolving)
+                break;
+#endif
+            if ((!block || !err)) {
+                break;
+            } else {
+                sleep(pow(2, i));
+                LOGE("failed to resolve server name, wait %.0f seconds", pow(2, i));
+            }
+        }
+
+        if (err != 0) {
+            LOGE("getaddrinfo: %s", gai_strerror(err));
+            return -1;
+        }
+
+        int prefer_af = ipv6first ? AF_INET6 : AF_INET;
+        for (rp = result; rp != NULL; rp = rp->ai_next)
+            if (rp->ai_family == prefer_af) {
+                if (rp->ai_family == AF_INET)
+                    memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in));
+                else if (rp->ai_family == AF_INET6)
+                    memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in6));
+                break;
+            }
+
+        if (rp == NULL) {
+            for (rp = result; rp != NULL; rp = rp->ai_next) {
+                if (rp->ai_family == AF_INET)
+                    memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in));
+                else if (rp->ai_family == AF_INET6)
+                    memcpy(storage, rp->ai_addr, sizeof(struct sockaddr_in6));
+                break;
+            }
+        }
+
+        if (rp == NULL) {
+            LOGE("failed to resolve remote addr");
+            return -1;
+        }
+
+        freeaddrinfo(result);
+        return 0;
+    }
+
+    return -1;
+}
+
+int
+sockaddr_cmp(struct sockaddr_storage *addr1,
+             struct sockaddr_storage *addr2, socklen_t len)
+{
+    struct sockaddr_in *p1_in   = (struct sockaddr_in *)addr1;
+    struct sockaddr_in *p2_in   = (struct sockaddr_in *)addr2;
+    struct sockaddr_in6 *p1_in6 = (struct sockaddr_in6 *)addr1;
+    struct sockaddr_in6 *p2_in6 = (struct sockaddr_in6 *)addr2;
+    if (p1_in->sin_family < p2_in->sin_family)
+        return -1;
+    if (p1_in->sin_family > p2_in->sin_family)
+        return 1;
+    /* compare ip4 */
+    if (p1_in->sin_family == AF_INET) {
+        /* just order it, ntohs not required */
+        if (p1_in->sin_port < p2_in->sin_port)
+            return -1;
+        if (p1_in->sin_port > p2_in->sin_port)
+            return 1;
+        return memcmp(&p1_in->sin_addr, &p2_in->sin_addr, INET_SIZE);
+    } else if (p1_in6->sin6_family == AF_INET6) {
+        /* just order it, ntohs not required */
+        if (p1_in6->sin6_port < p2_in6->sin6_port)
+            return -1;
+        if (p1_in6->sin6_port > p2_in6->sin6_port)
+            return 1;
+        return memcmp(&p1_in6->sin6_addr, &p2_in6->sin6_addr,
+                      INET6_SIZE);
+    } else {
+        /* eek unknown type, perform this comparison for sanity. */
+        return memcmp(addr1, addr2, len);
+    }
+}
+
+int
+sockaddr_cmp_addr(struct sockaddr_storage *addr1,
+                  struct sockaddr_storage *addr2, socklen_t len)
+{
+    struct sockaddr_in *p1_in   = (struct sockaddr_in *)addr1;
+    struct sockaddr_in *p2_in   = (struct sockaddr_in *)addr2;
+    struct sockaddr_in6 *p1_in6 = (struct sockaddr_in6 *)addr1;
+    struct sockaddr_in6 *p2_in6 = (struct sockaddr_in6 *)addr2;
+    if (p1_in->sin_family < p2_in->sin_family)
+        return -1;
+    if (p1_in->sin_family > p2_in->sin_family)
+        return 1;
+    /* compare ip4 */
+    if (p1_in->sin_family == AF_INET) {
+        return memcmp(&p1_in->sin_addr, &p2_in->sin_addr, INET_SIZE);
+    } else if (p1_in6->sin6_family == AF_INET6) {
+        return memcmp(&p1_in6->sin6_addr, &p2_in6->sin6_addr,
+                      INET6_SIZE);
+    } else {
+        /* eek unknown type, perform this comparison for sanity. */
+        return memcmp(addr1, addr2, len);
+    }
+}
+
+int
+validate_hostname(const char *hostname, const int hostname_len)
+{
+    if (hostname == NULL)
+        return 0;
+
+    if (hostname_len < 1 || hostname_len > 255)
+        return 0;
+
+    if (hostname[0] == '.')
+        return 0;
+
+    const char *label = hostname;
+    while (label < hostname + hostname_len) {
+        size_t label_len = hostname_len - (label - hostname);
+        char *next_dot   = strchr(label, '.');
+        if (next_dot != NULL)
+            label_len = next_dot - label;
+
+        if (label + label_len > hostname + hostname_len)
+            return 0;
+
+        if (label_len > 63 || label_len < 1)
+            return 0;
+
+        if (label[0] == '-' || label[label_len - 1] == '-')
+            return 0;
+
+        if (strspn(label, valid_label_bytes) < label_len)
+            return 0;
+
+        label += label_len + 1;
+    }
+
+    return 1;
+}
diff --git a/server/netutils.h b/server/netutils.h
new file mode 100644
index 0000000..0725592
--- /dev/null
+++ b/server/netutils.h
@@ -0,0 +1,98 @@
+/*
+ * netutils.h - Network utilities
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _NETUTILS_H
+#define _NETUTILS_H
+
+#if defined(__linux__)
+#include <netdb.h>
+#elif !defined(__MINGW32__)
+#include <netinet/tcp.h>
+#endif
+
+// only enable TCP_FASTOPEN on linux
+#if defined(__linux__)
+#include <linux/tcp.h>
+/*  conditional define for TCP_FASTOPEN */
+#ifndef TCP_FASTOPEN
+#define TCP_FASTOPEN   23
+#endif
+/*  conditional define for MSG_FASTOPEN */
+#ifndef MSG_FASTOPEN
+#define MSG_FASTOPEN   0x20000000
+#endif
+#elif !defined(__APPLE__)
+#ifdef TCP_FASTOPEN
+#undef TCP_FASTOPEN
+#endif
+#endif
+
+/* Backward compatibility for MPTCP_ENABLED between kernel 3 & 4 */
+#ifndef MPTCP_ENABLED
+#ifdef TCP_CC_INFO
+#define MPTCP_ENABLED 42
+#else
+#define MPTCP_ENABLED 26
+#endif
+#endif
+
+/** byte size of ip4 address */
+#define INET_SIZE 4
+/** byte size of ip6 address */
+#define INET6_SIZE 16
+
+size_t get_sockaddr_len(struct sockaddr *addr);
+ssize_t get_sockaddr(char *host, char *port,
+                     struct sockaddr_storage *storage, int block,
+                     int ipv6first);
+int set_reuseport(int socket);
+
+#ifdef SET_INTERFACE
+int setinterface(int socket_fd, const char *interface_name);
+#endif
+
+int bind_to_address(int socket_fd, const char *address);
+
+/**
+ * Compare two sockaddrs. Imposes an ordering on the addresses.
+ * Compares address and port.
+ * @param addr1: address 1.
+ * @param addr2: address 2.
+ * @param len: lengths of addr.
+ * @return: 0 if addr1 == addr2. -1 if addr1 is smaller, +1 if larger.
+ */
+int sockaddr_cmp(struct sockaddr_storage *addr1,
+                 struct sockaddr_storage *addr2, socklen_t len);
+
+/**
+ * Compare two sockaddrs. Compares address, not the port.
+ * @param addr1: address 1.
+ * @param addr2: address 2.
+ * @param len: lengths of addr.
+ * @return: 0 if addr1 == addr2. -1 if addr1 is smaller, +1 if larger.
+ */
+int sockaddr_cmp_addr(struct sockaddr_storage *addr1,
+                      struct sockaddr_storage *addr2, socklen_t len);
+
+int validate_hostname(const char *hostname, const int hostname_len);
+
+#endif
diff --git a/server/obfs.c b/server/obfs.c
new file mode 100644
index 0000000..5c885bf
--- /dev/null
+++ b/server/obfs.c
@@ -0,0 +1,205 @@
+#include <string.h>
+#include <stdlib.h>
+
+#include "utils.h"
+#include "obfs.h"
+
+int rand_bytes(uint8_t *output, int len);
+#define OBFS_HMAC_SHA1_LEN 10
+
+#include "obfsutil.c"
+#include "crc32.c"
+#include "base64.c"
+#include "http_simple.c"
+#include "tls1.2_ticket.c"
+#include "verify.c"
+#include "auth.c"
+
+void * init_data() {
+    return malloc(1);
+}
+
+obfs * new_obfs() {
+    obfs * self = (obfs*)malloc(sizeof(obfs));
+    self->l_data = NULL;
+    return self;
+}
+
+void set_server_info(obfs *self, server_info *server) {
+    memmove(&self->server, server, sizeof(server_info));
+}
+
+void get_server_info(obfs *self, server_info *server) {
+    memmove(server, &self->server, sizeof(server_info));
+}
+
+void dispose_obfs(obfs *self) {
+    free(self);
+}
+
+obfs_class * new_obfs_class(char *plugin_name)
+{
+    if (plugin_name == NULL)
+        return NULL;
+    if (strcmp(plugin_name, "origin") == 0)
+        return NULL;
+    if (strcmp(plugin_name, "plain") == 0)
+        return NULL;
+    init_crc32_table();
+    init_shift128plus();
+    if (strcmp(plugin_name, "http_simple") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = init_data;
+        plugin->new_obfs = http_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = http_simple_dispose;
+
+        plugin->client_encode = http_simple_client_encode;
+        plugin->client_decode = http_simple_client_decode;
+
+        plugin->server_encode = http_simple_server_encode;
+        plugin->server_decode = http_simple_server_decode;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "http_post") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = init_data;
+        plugin->new_obfs = http_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = http_simple_dispose;
+
+        plugin->client_encode = http_post_client_encode;
+        plugin->client_decode = http_simple_client_decode;
+
+        plugin->server_encode = http_simple_server_encode;
+        plugin->server_decode = http_simple_server_decode;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "tls1.2_ticket_auth") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = tls12_ticket_auth_init_data;
+        plugin->new_obfs = tls12_ticket_auth_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = tls12_ticket_auth_dispose;
+
+        plugin->client_encode = tls12_ticket_auth_client_encode;
+        plugin->client_decode = tls12_ticket_auth_client_decode;
+
+        plugin->server_encode = tls12_ticket_auth_server_encode;
+        plugin->server_decode = tls12_ticket_auth_server_decode;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "verify_simple") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = init_data;
+        plugin->new_obfs = verify_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = verify_simple_dispose;
+
+        plugin->client_pre_encrypt = verify_simple_client_pre_encrypt;
+        plugin->client_post_decrypt = verify_simple_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = NULL;
+        plugin->client_udp_post_decrypt = NULL;
+
+        plugin->server_pre_encrypt = verify_simple_server_pre_encrypt;
+        plugin->server_post_decrypt = verify_simple_server_post_decrypt;
+        plugin->server_udp_pre_encrypt = NULL;
+        plugin->server_udp_post_decrypt = NULL;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "auth_simple") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = auth_simple_init_data;
+        plugin->new_obfs = auth_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = auth_simple_dispose;
+
+        plugin->client_pre_encrypt = auth_simple_client_pre_encrypt;
+        plugin->client_post_decrypt = auth_simple_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = NULL;
+        plugin->client_udp_post_decrypt = NULL;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "auth_sha1") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = auth_simple_init_data;
+        plugin->new_obfs = auth_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = auth_simple_dispose;
+
+        plugin->client_pre_encrypt = auth_sha1_client_pre_encrypt;
+        plugin->client_post_decrypt = auth_sha1_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = NULL;
+        plugin->client_udp_post_decrypt = NULL;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "auth_sha1_v2") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = auth_simple_init_data;
+        plugin->new_obfs = auth_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = auth_simple_dispose;
+
+        plugin->client_pre_encrypt = auth_sha1_v2_client_pre_encrypt;
+        plugin->client_post_decrypt = auth_sha1_v2_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = NULL;
+        plugin->client_udp_post_decrypt = NULL;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "auth_sha1_v4") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = auth_simple_init_data;
+        plugin->new_obfs = auth_simple_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = auth_simple_dispose;
+
+        plugin->client_pre_encrypt = auth_sha1_v4_client_pre_encrypt;
+        plugin->client_post_decrypt = auth_sha1_v4_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = NULL;
+        plugin->client_udp_post_decrypt = NULL;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "auth_aes128_md5") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = auth_simple_init_data;
+        plugin->new_obfs = auth_aes128_md5_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = auth_simple_dispose;
+
+        plugin->client_pre_encrypt = auth_aes128_sha1_client_pre_encrypt;
+        plugin->client_post_decrypt = auth_aes128_sha1_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = auth_aes128_sha1_client_udp_pre_encrypt;
+        plugin->client_udp_post_decrypt = auth_aes128_sha1_client_udp_post_decrypt;
+
+        return plugin;
+    } else if (strcmp(plugin_name, "auth_aes128_sha1") == 0) {
+        obfs_class * plugin = (obfs_class*)malloc(sizeof(obfs));
+        plugin->init_data = auth_simple_init_data;
+        plugin->new_obfs = auth_aes128_sha1_new_obfs;
+        plugin->get_server_info = get_server_info;
+        plugin->set_server_info = set_server_info;
+        plugin->dispose = auth_simple_dispose;
+
+        plugin->client_pre_encrypt = auth_aes128_sha1_client_pre_encrypt;
+        plugin->client_post_decrypt = auth_aes128_sha1_client_post_decrypt;
+        plugin->client_udp_pre_encrypt = auth_aes128_sha1_client_udp_pre_encrypt;
+        plugin->client_udp_post_decrypt = auth_aes128_sha1_client_udp_post_decrypt;
+
+        return plugin;
+    }
+    LOGE("Load obfs '%s' failed", plugin_name);
+    return NULL;
+}
+
+void free_obfs_class(obfs_class *plugin) {
+    free(plugin);
+}
diff --git a/server/obfs.h b/server/obfs.h
new file mode 100644
index 0000000..74c60c9
--- /dev/null
+++ b/server/obfs.h
@@ -0,0 +1,100 @@
+/*
+ * obfs.h - Define shadowsocksR server's buffers and callbacks
+ *
+ * Copyright (C) 2015 - 2016, Break Wa11 <mmgac001@gmail.com>
+ */
+
+#ifndef _OBFS_H
+#define _OBFS_H
+
+#include <stdint.h>
+#include <unistd.h>
+
+typedef struct server_info {
+    char host[64];
+    uint16_t port;
+    char *param;
+    void *g_data;
+    uint8_t *iv;
+    size_t iv_len;
+    uint8_t *recv_iv;
+    size_t recv_iv_len;
+    uint8_t *key;
+    size_t key_len;
+    int head_len;
+    size_t tcp_mss;
+}server_info;
+
+typedef struct obfs {
+    server_info server;
+    void *l_data;
+}obfs;
+
+typedef struct obfs_class {
+    void * (*init_data)();
+    obfs * (*new_obfs)();
+    void (*get_server_info)(obfs *self, server_info *server);
+    void (*set_server_info)(obfs *self, server_info *server);
+    void (*dispose)(obfs *self);
+
+    int (*client_pre_encrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*client_encode)(obfs *self,
+            char **pencryptdata,
+            int datalength,
+            size_t* capacity);
+    int (*client_decode)(obfs *self,
+            char **pencryptdata,
+            int datalength,
+            size_t* capacity,
+            int *needsendback);
+    int (*client_post_decrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*client_udp_pre_encrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*client_udp_post_decrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*server_pre_encrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*server_post_decrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*server_udp_pre_encrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*server_udp_post_decrypt)(obfs *self,
+            char **pplaindata,
+            int datalength,
+            size_t* capacity);
+    int (*server_encode)(obfs *self,
+            char **pencryptdata,
+            int datalength,
+            size_t* capacity);
+    int (*server_decode)(obfs *self,
+            char **pencryptdata,
+            int datalength,
+            size_t* capacity,
+            int *needsendback);
+}obfs_class;
+
+obfs_class * new_obfs_class(char *plugin_name);
+void free_obfs_class(obfs_class *plugin);
+
+void set_server_info(obfs *self, server_info *server);
+void get_server_info(obfs *self, server_info *server);
+obfs * new_obfs();
+void dispose_obfs(obfs *self);
+
+#endif // _OBFS_H
diff --git a/server/obfsutil.c b/server/obfsutil.c
new file mode 100644
index 0000000..d00959b
--- /dev/null
+++ b/server/obfsutil.c
@@ -0,0 +1,36 @@
+int get_head_size(char *plaindata, int size, int def_size) {
+    if (plaindata == NULL || size < 2)
+        return def_size;
+    int head_type = plaindata[0] & 0x7;
+    if (head_type == 1)
+        return 7;
+    if (head_type == 4)
+        return 19;
+    if (head_type == 3)
+        return 4 + plaindata[1];
+    return def_size;
+}
+
+static int shift128plus_init_flag = 0;
+static uint64_t shift128plus_s[2] = {0x10000000, 0xFFFFFFFF};
+
+void init_shift128plus(void) {
+    if (shift128plus_init_flag == 0) {
+        shift128plus_init_flag = 1;
+        uint32_t seed = time(NULL);
+        shift128plus_s[0] = seed | 0x100000000L;
+        shift128plus_s[1] = ((uint64_t)seed << 32) | 0x1;
+    }
+}
+
+uint64_t xorshift128plus(void) {
+    uint64_t x = shift128plus_s[0];
+    uint64_t const y = shift128plus_s[1];
+    shift128plus_s[0] = y;
+    x ^= x << 23; // a
+    x ^= x >> 17; // b
+    x ^= y ^ (y >> 26); // c
+    shift128plus_s[1] = x;
+    return x + y;
+}
+
diff --git a/server/protocol.h b/server/protocol.h
new file mode 100644
index 0000000..eaa866e
--- /dev/null
+++ b/server/protocol.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2014, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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 PROTOCOL_H
+#define PROTOCOL_H
+
+typedef struct protocol {
+    const int default_port;
+    int(*const parse_packet)(const char *, size_t, char **);
+} protocol_t;
+
+#endif
diff --git a/server/resolv.c b/server/resolv.c
new file mode 100644
index 0000000..f580d06
--- /dev/null
+++ b/server/resolv.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (c) 2014, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <ev.h>
+#include <udns.h>
+
+#ifdef __MINGW32__
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <unistd.h>
+#endif
+
+#include "resolv.h"
+#include "utils.h"
+#include "netutils.h"
+
+/*
+ * Implement DNS resolution interface using libudns
+ */
+
+struct ResolvQuery {
+    void (*client_cb)(struct sockaddr *, void *);
+    void (*client_free_cb)(void *);
+    void *client_cb_data;
+    struct dns_query *queries[2];
+    size_t response_count;
+    struct sockaddr **responses;
+    uint16_t port;
+};
+
+extern int verbose;
+
+static struct ev_io resolv_io_watcher;
+static struct ev_timer resolv_timeout_watcher;
+static const int MODE_IPV4_ONLY  = 0;
+static const int MODE_IPV6_ONLY  = 1;
+static const int MODE_IPV4_FIRST = 2;
+static const int MODE_IPV6_FIRST = 3;
+static int resolv_mode           = 0;
+
+static void resolv_sock_cb(struct ev_loop *, struct ev_io *, int);
+static void resolv_timeout_cb(struct ev_loop *, struct ev_timer *, int);
+static void dns_query_v4_cb(struct dns_ctx *, struct dns_rr_a4 *, void *);
+static void dns_query_v6_cb(struct dns_ctx *, struct dns_rr_a6 *, void *);
+static void dns_timer_setup_cb(struct dns_ctx *, int, void *);
+static void process_client_callback(struct ResolvQuery *);
+static inline int all_queries_are_null(struct ResolvQuery *);
+static struct sockaddr *choose_ipv4_first(struct ResolvQuery *);
+static struct sockaddr *choose_ipv6_first(struct ResolvQuery *);
+static struct sockaddr *choose_any(struct ResolvQuery *);
+
+int
+resolv_init(struct ev_loop *loop, char **nameservers, int nameserver_num, int ipv6first)
+{
+    if (ipv6first)
+        resolv_mode = MODE_IPV6_FIRST;
+    else
+        resolv_mode = MODE_IPV4_FIRST;
+
+    struct dns_ctx *ctx = &dns_defctx;
+    if (nameservers == NULL) {
+        /* Nameservers not specified, use system resolver config */
+        dns_init(ctx, 0);
+    } else {
+        dns_reset(ctx);
+
+        for (int i = 0; i < nameserver_num; i++) {
+            char *server = nameservers[i];
+            dns_add_serv(ctx, server);
+        }
+    }
+
+    int sockfd = dns_open(ctx);
+    if (sockfd < 0) {
+        FATAL("Failed to open DNS resolver socket");
+    }
+
+    if (nameserver_num == 1 && nameservers != NULL) {
+        if (strncmp("127.0.0.1", nameservers[0], 9) == 0
+            || strncmp("::1", nameservers[0], 3) == 0) {
+            if (verbose) {
+                LOGI("bind UDP resolver to %s", nameservers[0]);
+            }
+            if (bind_to_address(sockfd, nameservers[0]) == -1)
+                ERROR("bind_to_address");
+        }
+    }
+
+#ifdef __MINGW32__
+    setnonblocking(sockfd);
+#else
+    int flags = fcntl(sockfd, F_GETFL, 0);
+    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
+#endif
+
+    ev_io_init(&resolv_io_watcher, resolv_sock_cb, sockfd, EV_READ);
+    resolv_io_watcher.data = ctx;
+
+    ev_io_start(loop, &resolv_io_watcher);
+
+    ev_timer_init(&resolv_timeout_watcher, resolv_timeout_cb, 0.0, 0.0);
+    resolv_timeout_watcher.data = ctx;
+
+    dns_set_tmcbck(ctx, dns_timer_setup_cb, loop);
+
+    return sockfd;
+}
+
+void
+resolv_shutdown(struct ev_loop *loop)
+{
+    struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
+
+    ev_io_stop(loop, &resolv_io_watcher);
+
+    if (ev_is_active(&resolv_timeout_watcher)) {
+        ev_timer_stop(loop, &resolv_timeout_watcher);
+    }
+
+    dns_close(ctx);
+}
+
+struct ResolvQuery *
+resolv_query(const char *hostname, void (*client_cb)(struct sockaddr *, void *),
+             void (*client_free_cb)(void *), void *client_cb_data,
+             uint16_t port)
+{
+    struct dns_ctx *ctx = (struct dns_ctx *)resolv_io_watcher.data;
+
+    /*
+     * Wrap udns's call back in our own
+     */
+    struct ResolvQuery *cb_data = ss_malloc(sizeof(struct ResolvQuery));
+    if (cb_data == NULL) {
+        LOGE("Failed to allocate memory for DNS query callback data.");
+        return NULL;
+    }
+    memset(cb_data, 0, sizeof(struct ResolvQuery));
+
+    cb_data->client_cb      = client_cb;
+    cb_data->client_free_cb = client_free_cb;
+    cb_data->client_cb_data = client_cb_data;
+    memset(cb_data->queries, 0, sizeof(cb_data->queries));
+    cb_data->response_count = 0;
+    cb_data->responses      = NULL;
+    cb_data->port           = port;
+
+    /* Submit A and AAAA queries */
+    if (resolv_mode != MODE_IPV6_ONLY) {
+        cb_data->queries[0] = dns_submit_a4(ctx,
+                                            hostname, 0,
+                                            dns_query_v4_cb, cb_data);
+        if (cb_data->queries[0] == NULL) {
+            LOGE("Failed to submit DNS query: %s",
+                 dns_strerror(dns_status(ctx)));
+        }
+    }
+
+    if (resolv_mode != MODE_IPV4_ONLY) {
+        cb_data->queries[1] = dns_submit_a6(ctx,
+                                            hostname, 0,
+                                            dns_query_v6_cb, cb_data);
+        if (cb_data->queries[1] == NULL) {
+            LOGE("Failed to submit DNS query: %s",
+                 dns_strerror(dns_status(ctx)));
+        }
+    }
+
+    if (all_queries_are_null(cb_data)) {
+        if (cb_data->client_free_cb != NULL) {
+            cb_data->client_free_cb(cb_data->client_cb_data);
+        }
+        ss_free(cb_data);
+    }
+
+    return cb_data;
+}
+
+void
+resolv_cancel(struct ResolvQuery *query_handle)
+{
+    struct ResolvQuery *cb_data = (struct ResolvQuery *)query_handle;
+    struct dns_ctx *ctx         = (struct dns_ctx *)resolv_io_watcher.data;
+
+    for (int i = 0; i < sizeof(cb_data->queries) / sizeof(cb_data->queries[0]);
+         i++)
+        if (cb_data->queries[i] != NULL) {
+            dns_cancel(ctx, cb_data->queries[i]);
+            ss_free(cb_data->queries[i]);
+        }
+
+    if (cb_data->client_free_cb != NULL) {
+        cb_data->client_free_cb(cb_data->client_cb_data);
+    }
+
+    ss_free(cb_data);
+}
+
+/*
+ * DNS UDP socket activity callback
+ */
+static void
+resolv_sock_cb(struct ev_loop *loop, struct ev_io *w, int revents)
+{
+    struct dns_ctx *ctx = (struct dns_ctx *)w->data;
+
+    if (revents & EV_READ) {
+        dns_ioevent(ctx, ev_now(loop));
+    }
+}
+
+/*
+ * Wrapper for client callback we provide to udns
+ */
+static void
+dns_query_v4_cb(struct dns_ctx *ctx, struct dns_rr_a4 *result, void *data)
+{
+    struct ResolvQuery *cb_data = (struct ResolvQuery *)data;
+
+    if (result == NULL) {
+        if (verbose) {
+            LOGI("IPv4 resolv: %s", dns_strerror(dns_status(ctx)));
+        }
+    } else if (result->dnsa4_nrr > 0) {
+        struct sockaddr **new_responses = ss_realloc(cb_data->responses,
+                                                     (cb_data->response_count +
+                                                      result->dnsa4_nrr) *
+                                                     sizeof(struct sockaddr *));
+        if (new_responses == NULL) {
+            LOGE("Failed to allocate memory for additional DNS responses");
+        } else {
+            cb_data->responses = new_responses;
+
+            for (int i = 0; i < result->dnsa4_nrr; i++) {
+                struct sockaddr_in *sa =
+                    (struct sockaddr_in *)ss_malloc(sizeof(struct sockaddr_in));
+                sa->sin_family = AF_INET;
+                sa->sin_port   = cb_data->port;
+                sa->sin_addr   = result->dnsa4_addr[i];
+
+                cb_data->responses[cb_data->response_count] =
+                    (struct sockaddr *)sa;
+                if (cb_data->responses[cb_data->response_count] == NULL) {
+                    LOGE(
+                        "Failed to allocate memory for DNS query result address");
+                } else {
+                    cb_data->response_count++;
+                }
+            }
+        }
+    }
+
+    ss_free(result);
+    cb_data->queries[0] = NULL; /* mark A query as being completed */
+
+    /* Once all queries have completed, call client callback */
+    if (all_queries_are_null(cb_data)) {
+        return process_client_callback(cb_data);
+    }
+}
+
+static void
+dns_query_v6_cb(struct dns_ctx *ctx, struct dns_rr_a6 *result, void *data)
+{
+    struct ResolvQuery *cb_data = (struct ResolvQuery *)data;
+
+    if (result == NULL) {
+        if (verbose) {
+            LOGI("IPv6 resolv: %s", dns_strerror(dns_status(ctx)));
+        }
+    } else if (result->dnsa6_nrr > 0) {
+        struct sockaddr **new_responses = ss_realloc(cb_data->responses,
+                                                     (cb_data->response_count +
+                                                      result->dnsa6_nrr) *
+                                                     sizeof(struct sockaddr *));
+        if (new_responses == NULL) {
+            LOGE("Failed to allocate memory for additional DNS responses");
+        } else {
+            cb_data->responses = new_responses;
+
+            for (int i = 0; i < result->dnsa6_nrr; i++) {
+                struct sockaddr_in6 *sa =
+                    (struct sockaddr_in6 *)ss_malloc(sizeof(struct sockaddr_in6));
+                sa->sin6_family = AF_INET6;
+                sa->sin6_port   = cb_data->port;
+                sa->sin6_addr   = result->dnsa6_addr[i];
+
+                cb_data->responses[cb_data->response_count] =
+                    (struct sockaddr *)sa;
+                if (cb_data->responses[cb_data->response_count] == NULL) {
+                    LOGE(
+                        "Failed to allocate memory for DNS query result address");
+                } else {
+                    cb_data->response_count++;
+                }
+            }
+        }
+    }
+
+    ss_free(result);
+    cb_data->queries[1] = NULL; /* mark AAAA query as being completed */
+
+    /* Once all queries have completed, call client callback */
+    if (all_queries_are_null(cb_data)) {
+        return process_client_callback(cb_data);
+    }
+}
+
+/*
+ * Called once all queries have been completed
+ */
+static void
+process_client_callback(struct ResolvQuery *cb_data)
+{
+    struct sockaddr *best_address = NULL;
+
+    if (resolv_mode == MODE_IPV4_FIRST) {
+        best_address = choose_ipv4_first(cb_data);
+    } else if (resolv_mode == MODE_IPV6_FIRST) {
+        best_address = choose_ipv6_first(cb_data);
+    } else {
+        best_address = choose_any(cb_data);
+    }
+
+    cb_data->client_cb(best_address, cb_data->client_cb_data);
+
+    for (int i = 0; i < cb_data->response_count; i++)
+        ss_free(cb_data->responses[i]);
+
+    ss_free(cb_data->responses);
+    if (cb_data->client_free_cb != NULL) {
+        cb_data->client_free_cb(cb_data->client_cb_data);
+    }
+    ss_free(cb_data);
+}
+
+static struct sockaddr *
+choose_ipv4_first(struct ResolvQuery *cb_data)
+{
+    for (int i = 0; i < cb_data->response_count; i++)
+        if (cb_data->responses[i]->sa_family == AF_INET) {
+            return cb_data->responses[i];
+        }
+
+    return choose_any(cb_data);
+}
+
+static struct sockaddr *
+choose_ipv6_first(struct ResolvQuery *cb_data)
+{
+    for (int i = 0; i < cb_data->response_count; i++)
+        if (cb_data->responses[i]->sa_family == AF_INET6) {
+            return cb_data->responses[i];
+        }
+
+    return choose_any(cb_data);
+}
+
+static struct sockaddr *
+choose_any(struct ResolvQuery *cb_data)
+{
+    if (cb_data->response_count >= 1) {
+        return cb_data->responses[0];
+    }
+
+    return NULL;
+}
+
+/*
+ * DNS timeout callback
+ */
+static void
+resolv_timeout_cb(struct ev_loop *loop, struct ev_timer *w, int revents)
+{
+    struct dns_ctx *ctx = (struct dns_ctx *)w->data;
+
+    if (revents & EV_TIMER) {
+        dns_timeouts(ctx, 30, ev_now(loop));
+    }
+}
+
+/*
+ * Callback to setup DNS timeout callback
+ */
+static void
+dns_timer_setup_cb(struct dns_ctx *ctx, int timeout, void *data)
+{
+    struct ev_loop *loop = (struct ev_loop *)data;
+
+    if (ev_is_active(&resolv_timeout_watcher)) {
+        ev_timer_stop(loop, &resolv_timeout_watcher);
+    }
+
+    if (ctx != NULL && timeout >= 0) {
+        ev_timer_set(&resolv_timeout_watcher, timeout, 0.0);
+        ev_timer_start(loop, &resolv_timeout_watcher);
+    }
+}
+
+static inline int
+all_queries_are_null(struct ResolvQuery *cb_data)
+{
+    int result = 1;
+
+    for (int i = 0; i < sizeof(cb_data->queries) / sizeof(cb_data->queries[0]);
+         i++)
+        result = result && cb_data->queries[i] == NULL;
+
+    return result;
+}
diff --git a/server/resolv.h b/server/resolv.h
new file mode 100644
index 0000000..0552922
--- /dev/null
+++ b/server/resolv.h
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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 RESOLV_H
+#define RESOLV_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdint.h>
+
+#ifdef __MINGW32__
+#include "win32.h"
+#else
+#include <sys/socket.h>
+#endif
+
+struct ResolvQuery;
+
+int resolv_init(struct ev_loop *, char **, int, int);
+struct ResolvQuery *resolv_query(const char *, void (*)(struct sockaddr *,
+                                                        void *), void (*)(
+                                     void *), void *, uint16_t);
+void resolv_cancel(struct ResolvQuery *);
+void resolv_shutdown(struct ev_loop *);
+
+#endif
diff --git a/server/rule.c b/server/rule.c
new file mode 100644
index 0000000..8aae04e
--- /dev/null
+++ b/server/rule.c
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
+ * Copyright (c) 2011 Manuel Kasper <mk@neon1.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#ifdef __MINGW32__
+extern void ss_error(const char *s);
+#endif
+
+#include "rule.h"
+#include "utils.h"
+
+static void free_rule(rule_t *);
+
+rule_t *
+new_rule()
+{
+    rule_t *rule;
+
+    rule = calloc(1, sizeof(rule_t));
+    if (rule == NULL) {
+        ERROR("malloc");
+        return NULL;
+    }
+
+    return rule;
+}
+
+int
+accept_rule_arg(rule_t *rule, const char *arg)
+{
+    if (rule->pattern == NULL) {
+        rule->pattern = strdup(arg);
+        if (rule->pattern == NULL) {
+            ERROR("strdup failed");
+            return -1;
+        }
+    } else {
+        LOGE("Unexpected table rule argument: %s", arg);
+        return -1;
+    }
+
+    return 1;
+}
+
+void
+add_rule(struct cork_dllist *rules, rule_t *rule)
+{
+    cork_dllist_add(rules, &rule->entries);
+}
+
+int
+init_rule(rule_t *rule)
+{
+    if (rule->pattern_re == NULL) {
+        const char *reerr;
+        int reerroffset;
+
+        rule->pattern_re =
+            pcre_compile(rule->pattern, 0, &reerr, &reerroffset, NULL);
+        if (rule->pattern_re == NULL) {
+            LOGE("Regex compilation of \"%s\" failed: %s, offset %d",
+                 rule->pattern, reerr, reerroffset);
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+rule_t *
+lookup_rule(const struct cork_dllist *rules, const char *name, size_t name_len)
+{
+    struct cork_dllist_item *curr, *next;
+
+    if (name == NULL) {
+        name     = "";
+        name_len = 0;
+    }
+
+    cork_dllist_foreach_void(rules, curr, next) {
+        rule_t *rule = cork_container_of(curr, rule_t, entries);
+        if (pcre_exec(rule->pattern_re, NULL,
+                      name, name_len, 0, 0, NULL, 0) >= 0)
+            return rule;
+    }
+
+    return NULL;
+}
+
+void
+remove_rule(rule_t *rule)
+{
+    cork_dllist_remove(&rule->entries);
+    free_rule(rule);
+}
+
+static void
+free_rule(rule_t *rule)
+{
+    if (rule == NULL)
+        return;
+
+    ss_free(rule->pattern);
+    if (rule->pattern_re != NULL)
+        pcre_free(rule->pattern_re);
+    ss_free(rule);
+}
diff --git a/server/rule.h b/server/rule.h
new file mode 100644
index 0000000..015bc42
--- /dev/null
+++ b/server/rule.h
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
+ * Copyright (c) 2011 Manuel Kasper <mk@neon1.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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 RULE_H
+#define RULE_H
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <libcork/ds.h>
+
+#ifdef HAVE_PCRE_H
+#include <pcre.h>
+#elif HAVE_PCRE_PCRE_H
+#include <pcre/pcre.h>
+#endif
+
+typedef struct rule {
+    char *pattern;
+
+    /* Runtime fields */
+    pcre *pattern_re;
+
+    struct cork_dllist_item entries;
+} rule_t;
+
+void add_rule(struct cork_dllist *, rule_t *);
+int init_rule(rule_t *);
+rule_t *lookup_rule(const struct cork_dllist *, const char *, size_t);
+void remove_rule(rule_t *);
+rule_t *new_rule();
+int accept_rule_arg(rule_t *, const char *);
+
+#endif
diff --git a/server/server.c b/server/server.c
new file mode 100644
index 0000000..65b0e42
--- /dev/null
+++ b/server/server.c
@@ -0,0 +1,2209 @@
+/*
+ * server.c - Provide shadowsocks service
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <math.h>
+
+#ifndef __MINGW32__
+#include <netdb.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#include <sys/un.h>
+#endif
+
+#include <libcork/core.h>
+#include <udns.h>
+
+#ifdef __MINGW32__
+#include "win32.h"
+#endif
+
+#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
+#include <net/if.h>
+#include <sys/ioctl.h>
+#define SET_INTERFACE
+#endif
+
+#include "netutils.h"
+#include "utils.h"
+#include "acl.h"
+#include "server.h"
+
+#include "obfs.c" // I don't want to modify makefile
+
+#ifndef EAGAIN
+#define EAGAIN EWOULDBLOCK
+#endif
+
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK EAGAIN
+#endif
+
+#ifndef BUF_SIZE
+#define BUF_SIZE 2048
+#endif
+
+#ifndef SSMAXCONN
+#define SSMAXCONN 1024
+#endif
+
+#ifndef UPDATE_INTERVAL
+#define UPDATE_INTERVAL 30
+#endif
+
+static void signal_cb(EV_P_ ev_signal *w, int revents);
+static void accept_cb(EV_P_ ev_io *w, int revents);
+static void server_send_cb(EV_P_ ev_io *w, int revents);
+static void server_recv_cb(EV_P_ ev_io *w, int revents);
+static void remote_recv_cb(EV_P_ ev_io *w, int revents);
+static void remote_send_cb(EV_P_ ev_io *w, int revents);
+static void server_timeout_cb(EV_P_ ev_timer *watcher, int revents);
+static void block_list_clear_cb(EV_P_ ev_timer *watcher, int revents);
+
+static remote_t *new_remote(int fd);
+static server_t *new_server(int fd, listen_ctx_t *listener);
+static remote_t *connect_to_remote(EV_P_ struct addrinfo *res,
+                                   server_t *server);
+
+static void free_remote(remote_t *remote);
+static void close_and_free_remote(EV_P_ remote_t *remote);
+static void free_server(server_t *server);
+static void close_and_free_server(EV_P_ server_t *server);
+static void server_resolve_cb(struct sockaddr *addr, void *data);
+static void query_free_cb(void *data);
+
+static size_t parse_header_len(const char atyp, const char *data, size_t offset);
+static int is_header_complete(const buffer_t *buf);
+
+int verbose = 0;
+
+static int acl       = 0;
+static int mode      = TCP_ONLY;
+static int auth      = 0;
+static int ipv6first = 0;
+
+static int protocol_compatible = 0;//SSR
+static int obfs_compatible = 0;//SSR
+
+static int fast_open = 0;
+#ifdef HAVE_SETRLIMIT
+static int nofile = 0;
+#endif
+static int remote_conn = 0;
+static int server_conn = 0;
+
+static char *bind_address    = NULL;
+static char *server_port     = NULL;
+static char *manager_address = NULL;
+uint64_t tx                  = 0;
+uint64_t rx                  = 0;
+ev_timer stat_update_watcher;
+ev_timer block_list_watcher;
+
+static struct cork_dllist connections;
+
+static void
+stat_update_cb(EV_P_ ev_timer *watcher, int revents)
+{
+    struct sockaddr_un svaddr, claddr;
+    int sfd = -1;
+    size_t msgLen;
+    char resp[BUF_SIZE];
+
+    if (verbose) {
+        LOGI("update traffic stat: tx: %" PRIu64 " rx: %" PRIu64 "", tx, rx);
+    }
+
+    snprintf(resp, BUF_SIZE, "stat: {\"%s\":%" PRIu64 "}", server_port, tx + rx);
+    msgLen = strlen(resp) + 1;
+
+    ss_addr_t ip_addr = { .host = NULL, .port = NULL };
+    parse_addr(manager_address, &ip_addr);
+
+    if (ip_addr.host == NULL || ip_addr.port == NULL) {
+        sfd = socket(AF_UNIX, SOCK_DGRAM, 0);
+        if (sfd == -1) {
+            ERROR("stat_socket");
+            return;
+        }
+
+        memset(&claddr, 0, sizeof(struct sockaddr_un));
+        claddr.sun_family = AF_UNIX;
+        snprintf(claddr.sun_path, sizeof(claddr.sun_path), "/tmp/shadowsocks.%s", server_port);
+
+        unlink(claddr.sun_path);
+
+        if (bind(sfd, (struct sockaddr *)&claddr, sizeof(struct sockaddr_un)) == -1) {
+            ERROR("stat_bind");
+            close(sfd);
+            return;
+        }
+
+        memset(&svaddr, 0, sizeof(struct sockaddr_un));
+        svaddr.sun_family = AF_UNIX;
+        strncpy(svaddr.sun_path, manager_address, sizeof(svaddr.sun_path) - 1);
+
+        if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&svaddr,
+                   sizeof(struct sockaddr_un)) != msgLen) {
+            ERROR("stat_sendto");
+            close(sfd);
+            return;
+        }
+
+        unlink(claddr.sun_path);
+    } else {
+        struct sockaddr_storage storage;
+        memset(&storage, 0, sizeof(struct sockaddr_storage));
+        if (get_sockaddr(ip_addr.host, ip_addr.port, &storage, 0, ipv6first) == -1) {
+            ERROR("failed to parse the manager addr");
+            return;
+        }
+
+        sfd = socket(storage.ss_family, SOCK_DGRAM, 0);
+
+        if (sfd == -1) {
+            ERROR("stat_socket");
+            return;
+        }
+
+        size_t addr_len = get_sockaddr_len((struct sockaddr *)&storage);
+        if (sendto(sfd, resp, strlen(resp) + 1, 0, (struct sockaddr *)&storage,
+                   addr_len) != msgLen) {
+            ERROR("stat_sendto");
+            close(sfd);
+            return;
+        }
+    }
+
+    close(sfd);
+}
+
+static void
+free_connections(struct ev_loop *loop)
+{
+    struct cork_dllist_item *curr, *next;
+    cork_dllist_foreach_void(&connections, curr, next) {
+        server_t *server = cork_container_of(curr, server_t, entries);
+        remote_t *remote = server->remote;
+        close_and_free_server(loop, server);
+        close_and_free_remote(loop, remote);
+    }
+}
+
+static size_t
+parse_header_len(const char atyp, const char *data, size_t offset)
+{
+    size_t len = 0;
+    if ((atyp & ADDRTYPE_MASK) == 1) {
+        // IP V4
+        len += sizeof(struct in_addr);
+    } else if ((atyp & ADDRTYPE_MASK) == 3) {
+        // Domain name
+        uint8_t name_len = *(uint8_t *)(data + offset);
+        len += name_len + 1;
+    } else if ((atyp & ADDRTYPE_MASK) == 4) {
+        // IP V6
+        len += sizeof(struct in6_addr);
+    } else {
+        return 0;
+    }
+    len += 2;
+    return len;
+}
+
+static int
+is_header_complete(const buffer_t *buf)
+{
+    size_t header_len = 0;
+    size_t buf_len    = buf->len;
+
+    char atyp = buf->array[header_len];
+
+    // 1 byte for atyp
+    header_len++;
+
+    if ((atyp & ADDRTYPE_MASK) == 1) {
+        // IP V4
+        header_len += sizeof(struct in_addr);
+    } else if ((atyp & ADDRTYPE_MASK) == 3) {
+        // Domain name
+        // domain len + len of domain
+        if (buf_len < header_len + 1)
+            return 0;
+        uint8_t name_len = *(uint8_t *)(buf->array + header_len);
+        header_len += name_len + 1;
+    } else if ((atyp & ADDRTYPE_MASK) == 4) {
+        // IP V6
+        header_len += sizeof(struct in6_addr);
+    } else {
+        return -1;
+    }
+
+    // len of port
+    header_len += 2;
+
+    // size of ONETIMEAUTH_BYTES
+    if (auth || (atyp & ONETIMEAUTH_FLAG)) {
+        header_len += ONETIMEAUTH_BYTES;
+    }
+
+    return buf_len >= header_len ? 1 : 0;
+}
+
+static char *
+get_peer_name(int fd)
+{
+    static char peer_name[INET6_ADDRSTRLEN] = { 0 };
+    struct sockaddr_storage addr;
+    socklen_t len = sizeof(struct sockaddr_storage);
+    memset(&addr, 0, len);
+    memset(peer_name, 0, INET6_ADDRSTRLEN);
+    int err = getpeername(fd, (struct sockaddr *)&addr, &len);
+    if (err == 0) {
+        if (addr.ss_family == AF_INET) {
+            struct sockaddr_in *s = (struct sockaddr_in *)&addr;
+            dns_ntop(AF_INET, &s->sin_addr, peer_name, INET_ADDRSTRLEN);
+        } else if (addr.ss_family == AF_INET6) {
+            struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
+            dns_ntop(AF_INET6, &s->sin6_addr, peer_name, INET6_ADDRSTRLEN);
+        }
+    } else {
+        return NULL;
+    }
+    return peer_name;
+}
+
+#ifdef __linux__
+static void
+set_linger(int fd)
+{
+    struct linger so_linger;
+    memset(&so_linger, 0, sizeof(struct linger));
+    so_linger.l_onoff  = 1;
+    so_linger.l_linger = 0;
+    setsockopt(fd, SOL_SOCKET, SO_LINGER, &so_linger, sizeof so_linger);
+}
+#endif
+
+static void
+reset_addr(int fd)
+{
+    char *peer_name;
+    peer_name = get_peer_name(fd);
+    if (peer_name != NULL) {
+        remove_from_block_list(peer_name);
+    }
+}
+
+static void
+report_addr(int fd, int err_level)
+{
+#ifdef __linux__
+    set_linger(fd);
+#endif
+
+    char *peer_name;
+    peer_name = get_peer_name(fd);
+    if (peer_name != NULL) {
+        LOGE("failed to handshake with %s", peer_name);
+        update_block_list(peer_name, err_level);
+    }
+}
+
+int
+setfastopen(int fd)
+{
+    int s = 0;
+#ifdef TCP_FASTOPEN
+    if (fast_open) {
+#ifdef __APPLE__
+        int opt = 1;
+#else
+        int opt = 5;
+#endif
+        s = setsockopt(fd, IPPROTO_TCP, TCP_FASTOPEN, &opt, sizeof(opt));
+
+        if (s == -1) {
+            if (errno == EPROTONOSUPPORT || errno == ENOPROTOOPT) {
+                LOGE("fast open is not supported on this platform");
+                fast_open = 0;
+            } else {
+                ERROR("setsockopt");
+            }
+        }
+    }
+#endif
+    return s;
+}
+
+#ifndef __MINGW32__
+int
+setnonblocking(int fd)
+{
+    int flags;
+    if (-1 == (flags = fcntl(fd, F_GETFL, 0))) {
+        flags = 0;
+    }
+    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+}
+
+#endif
+
+int
+create_and_bind(const char *host, const char *port, int mptcp)
+{
+    struct addrinfo hints;
+    struct addrinfo *result, *rp, *ipv4v6bindall;
+    int s, listen_sock;
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_family   = AF_UNSPEC;               /* Return IPv4 and IPv6 choices */
+    hints.ai_socktype = SOCK_STREAM;             /* We want a TCP socket */
+    hints.ai_flags    = AI_PASSIVE | AI_ADDRCONFIG; /* For wildcard IP address */
+    hints.ai_protocol = IPPROTO_TCP;
+
+    for (int i = 1; i < 8; i++) {
+        s = getaddrinfo(host, port, &hints, &result);
+        if (s == 0) {
+            break;
+        } else {
+            sleep(pow(2, i));
+            LOGE("failed to resolve server name, wait %.0f seconds", pow(2, i));
+        }
+    }
+
+    if (s != 0) {
+        LOGE("getaddrinfo: %s", gai_strerror(s));
+        return -1;
+    }
+
+    rp = result;
+
+    /*
+     * On Linux, with net.ipv6.bindv6only = 0 (the default), getaddrinfo(NULL) with
+     * AI_PASSIVE returns 0.0.0.0 and :: (in this order). AI_PASSIVE was meant to
+     * return a list of addresses to listen on, but it is impossible to listen on
+     * 0.0.0.0 and :: at the same time, if :: implies dualstack mode.
+     */
+    if (!host) {
+        ipv4v6bindall = result;
+
+        /* Loop over all address infos found until a IPV6 address is found. */
+        while (ipv4v6bindall) {
+            if (ipv4v6bindall->ai_family == AF_INET6) {
+                rp = ipv4v6bindall; /* Take first IPV6 address available */
+                break;
+            }
+            ipv4v6bindall = ipv4v6bindall->ai_next; /* Get next address info, if any */
+        }
+    }
+
+    for (/*rp = result*/; rp != NULL; rp = rp->ai_next) {
+        listen_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+        if (listen_sock == -1) {
+            continue;
+        }
+
+        if (rp->ai_family == AF_INET6) {
+            int ipv6only = host ? 1 : 0;
+            setsockopt(listen_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
+        }
+
+        int opt = 1;
+        setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+#ifdef SO_NOSIGPIPE
+        setsockopt(listen_sock, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
+#endif
+        int err = set_reuseport(listen_sock);
+        if (err == 0) {
+            LOGI("tcp port reuse enabled");
+        }
+
+        if (mptcp == 1) {
+            int err = setsockopt(listen_sock, SOL_TCP, MPTCP_ENABLED, &opt, sizeof(opt));
+            if (err == -1) {
+                ERROR("failed to enable multipath TCP");
+            }
+        }
+
+        s = bind(listen_sock, rp->ai_addr, rp->ai_addrlen);
+        if (s == 0) {
+            /* We managed to bind successfully! */
+            break;
+        } else {
+            ERROR("bind");
+        }
+
+        close(listen_sock);
+    }
+
+    if (rp == NULL) {
+        LOGE("Could not bind");
+        return -1;
+    }
+
+    freeaddrinfo(result);
+
+    return listen_sock;
+}
+
+static remote_t *
+connect_to_remote(EV_P_ struct addrinfo *res,
+                  server_t *server)
+{
+    int sockfd;
+#ifdef SET_INTERFACE
+    const char *iface = server->listen_ctx->iface;
+#endif
+
+    if (acl) {
+        char ipstr[INET6_ADDRSTRLEN];
+        memset(ipstr, 0, INET6_ADDRSTRLEN);
+
+        if (res->ai_addr->sa_family == AF_INET) {
+            struct sockaddr_in *s = (struct sockaddr_in *)res->ai_addr;
+            dns_ntop(AF_INET, &s->sin_addr, ipstr, INET_ADDRSTRLEN);
+        } else if (res->ai_addr->sa_family == AF_INET6) {
+            struct sockaddr_in6 *s = (struct sockaddr_in6 *)res->ai_addr;
+            dns_ntop(AF_INET6, &s->sin6_addr, ipstr, INET6_ADDRSTRLEN);
+        }
+
+        if (outbound_block_match_host(ipstr) == 1) {
+            if (verbose)
+                LOGI("outbound blocked %s", ipstr);
+            return NULL;
+        }
+    }
+
+    // initialize remote socks
+    sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
+    if (sockfd == -1) {
+        ERROR("socket");
+        close(sockfd);
+        return NULL;
+    }
+
+    int opt = 1;
+    setsockopt(sockfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
+#ifdef SO_NOSIGPIPE
+    setsockopt(sockfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
+#endif
+    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+
+    // setup remote socks
+
+    if (setnonblocking(sockfd) == -1)
+        ERROR("setnonblocking");
+
+    if (bind_address != NULL)
+        if (bind_to_address(sockfd, bind_address) == -1) {
+            ERROR("bind_to_address");
+            close(sockfd);
+            return NULL;
+        }
+
+#ifdef SET_INTERFACE
+    if (iface) {
+        if (setinterface(sockfd, iface) == -1) {
+            ERROR("setinterface");
+            close(sockfd);
+            return NULL;
+        }
+    }
+#endif
+
+    remote_t *remote = new_remote(sockfd);
+
+#ifdef TCP_FASTOPEN
+    if (fast_open) {
+#ifdef __APPLE__
+        ((struct sockaddr_in *)(res->ai_addr))->sin_len = sizeof(struct sockaddr_in);
+        sa_endpoints_t endpoints;
+        memset((char *)&endpoints, 0, sizeof(endpoints));
+        endpoints.sae_dstaddr    = res->ai_addr;
+        endpoints.sae_dstaddrlen = res->ai_addrlen;
+
+        struct iovec iov;
+        iov.iov_base = server->buf->array + server->buf->idx;
+        iov.iov_len  = server->buf->len;
+        size_t len;
+        int s = connectx(sockfd, &endpoints, SAE_ASSOCID_ANY, CONNECT_DATA_IDEMPOTENT,
+                         &iov, 1, &len, NULL);
+        if (s == 0) {
+            s = len;
+        }
+#else
+        ssize_t s = sendto(sockfd, server->buf->array + server->buf->idx,
+                           server->buf->len, MSG_FASTOPEN, res->ai_addr,
+                           res->ai_addrlen);
+#endif
+        if (s == -1) {
+            if (errno == CONNECT_IN_PROGRESS || errno == EAGAIN
+                || errno == EWOULDBLOCK) {
+                // The remote server doesn't support tfo or it's the first connection to the server.
+                // It will automatically fall back to conventional TCP.
+            } else if (errno == EOPNOTSUPP || errno == EPROTONOSUPPORT ||
+                       errno == ENOPROTOOPT) {
+                // Disable fast open as it's not supported
+                fast_open = 0;
+                LOGE("fast open is not supported on this platform");
+            } else {
+                ERROR("sendto");
+            }
+        } else if (s <= server->buf->len) {
+            server->buf->idx += s;
+            server->buf->len -= s;
+        } else {
+            server->buf->idx = 0;
+            server->buf->len = 0;
+        }
+    }
+#endif
+
+    if (!fast_open) {
+        int r = connect(sockfd, res->ai_addr, res->ai_addrlen);
+
+        if (r == -1 && errno != CONNECT_IN_PROGRESS) {
+            ERROR("connect");
+            close_and_free_remote(EV_A_ remote);
+            return NULL;
+        }
+    }
+
+    return remote;
+}
+
+static void
+server_recv_cb(EV_P_ ev_io *w, int revents)
+{
+    server_ctx_t *server_recv_ctx = (server_ctx_t *)w;
+    server_t *server              = server_recv_ctx->server;
+    remote_t *remote              = NULL;
+
+    int len       = server->buf->len;
+    buffer_t *buf = server->buf;
+
+    if (server->stage > STAGE_PARSE) {
+        remote = server->remote;
+        buf    = remote->buf;
+        len    = 0;
+
+        ev_timer_again(EV_A_ & server->recv_ctx->watcher);
+    }
+
+    if (len > BUF_SIZE) {
+        ERROR("out of recv buffer");
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    }
+
+    ssize_t r = recv(server->fd, buf->array + len, BUF_SIZE - len, 0);
+
+    if (r == 0) {
+        // connection closed
+        if (verbose) {
+            LOGI("server_recv close the connection");
+        }
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    } else if (r == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // no data
+            // continue to wait for recv
+            return;
+        } else {
+            ERROR("server recv");
+            close_and_free_remote(EV_A_ remote);
+            close_and_free_server(EV_A_ server);
+            return;
+        }
+    }
+
+    tx += r;
+
+    if (server->stage == STAGE_ERROR) {
+        server->buf->len = 0;
+        server->buf->idx = 0;
+        return;
+    }
+
+    // handle incomplete header part 1
+    if (server->stage == STAGE_INIT) {
+        buf->len += r;
+        if (buf->len <= enc_get_iv_len() + 1) {
+            // wait for more
+            return;
+        }
+    } else {
+        buf->len = r;
+    }
+
+    // SSR beg
+
+    if (server->obfs_plugin) {
+        obfs_class *obfs_plugin = server->obfs_plugin;
+        if (obfs_plugin->server_decode) {
+            int needsendback = 0;
+
+            if(obfs_compatible == 1)
+            {
+                char *back_buf = (char*)malloc(sizeof(buffer_t));
+                memcpy(back_buf, buf, sizeof(buffer_t));
+                buf->len = obfs_plugin->server_decode(server->obfs, &buf->array, buf->len, &buf->capacity, &needsendback);
+
+                if ((int)buf->len < 0)
+                {
+                    LOGE("obfs_compatible");
+                    memcpy(buf, back_buf, sizeof(buffer_t));
+                    free(back_buf);
+                    server->obfs_compatible_state = 1;
+                }
+            }
+            else
+            {
+                buf->len = obfs_plugin->server_decode(server->obfs, &buf->array, buf->len, &buf->capacity, &needsendback);
+                if ((int)buf->len < 0) {
+                    LOGE("server_decode");
+                    close_and_free_remote(EV_A_ remote);
+                    close_and_free_server(EV_A_ server);
+                    return;
+                }
+            }
+
+            if (needsendback) {
+                size_t capacity = BUF_SIZE;
+                char *sendback_buf = (char*)malloc(capacity);
+                obfs_class *obfs_plugin = server->obfs_plugin;
+                if (obfs_plugin->server_encode) {
+                    int len = obfs_plugin->server_encode(server->obfs, &sendback_buf, 0, &capacity);
+                    send(server->fd, sendback_buf, len, 0);
+                }
+                free(sendback_buf);
+                return;
+            }
+        }
+    }
+
+    int err = ss_decrypt(buf, server->d_ctx, BUF_SIZE);
+
+    if (err) {
+        report_addr(server->fd, MALICIOUS);
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    }
+
+    if (server->protocol_plugin) {
+        obfs_class *protocol_plugin = server->protocol_plugin;
+        if (protocol_plugin->server_post_decrypt) {
+
+            if(protocol_compatible == 1)
+            {
+                char *back_buf = (char*)malloc(sizeof(buffer_t));
+                memcpy(back_buf, buf, sizeof(buffer_t));
+                buf->len = protocol_plugin->server_post_decrypt(server->protocol, &buf->array, buf->len, &buf->capacity);
+
+                if ((int)buf->len < 0) {
+                    LOGE("protocol_compatible");
+                    memcpy(buf, back_buf, sizeof(buffer_t));
+                    free(back_buf);
+                    server->protocol_compatible_state = 1;
+                }
+                if ( buf->len == 0 )
+                {
+                    LOGE("protocol_compatible");
+                    memcpy(buf, back_buf, sizeof(buffer_t));
+                    free(back_buf);
+                    server->protocol_compatible_state = 1;
+                }
+            }
+            else
+            {
+                buf->len = protocol_plugin->server_post_decrypt(server->protocol, &buf->array, buf->len, &buf->capacity);
+                if ((int)buf->len < 0) {
+                    LOGE("server_post_decrypt");
+                    close_and_free_remote(EV_A_ remote);
+                    close_and_free_server(EV_A_ server);
+                    return;
+                }
+                if ( buf->len == 0 )
+                {
+                    LOGE("server_post_decrypt");
+                    return;
+                }
+            }
+        }
+    }
+    // SSR end
+
+    // handle incomplete header part 2
+    if (server->stage == STAGE_INIT) {
+        int ret = is_header_complete(server->buf);
+        if (ret == 1) {
+            bfree(server->header_buf);
+            ss_free(server->header_buf);
+            server->stage = STAGE_PARSE;
+        } else if (ret == -1) {
+            server->stage = STAGE_ERROR;
+            report_addr(server->fd, MALFORMED);
+            server->buf->len = 0;
+            server->buf->idx = 0;
+            return;
+        } else {
+            server->stage = STAGE_HANDSHAKE;
+        }
+    }
+
+    if (server->stage == STAGE_HANDSHAKE) {
+        size_t header_len = server->header_buf->len;
+        brealloc(server->header_buf, server->buf->len + header_len, BUF_SIZE);
+        memcpy(server->header_buf->array + header_len,
+               server->buf->array, server->buf->len);
+        server->header_buf->len = server->buf->len + header_len;
+
+        int ret = is_header_complete(server->buf);
+
+        if (ret == 1) {
+            brealloc(server->buf, server->header_buf->len, BUF_SIZE);
+            memcpy(server->buf->array, server->header_buf->array, server->header_buf->len);
+            server->buf->len = server->header_buf->len;
+            bfree(server->header_buf);
+            ss_free(server->header_buf);
+            server->stage = STAGE_PARSE;
+        } else {
+            if (ret == -1)
+                server->stage = STAGE_ERROR;
+            server->buf->len = 0;
+            server->buf->idx = 0;
+            return;
+        }
+    }
+
+    // handshake and transmit data
+    if (server->stage == STAGE_STREAM) {
+        if (server->auth && !ss_check_hash(remote->buf, server->chunk, server->d_ctx, BUF_SIZE)) {
+            LOGE("hash error");
+            report_addr(server->fd, BAD);
+            close_and_free_server(EV_A_ server);
+            close_and_free_remote(EV_A_ remote);
+            return;
+        }
+
+        int s = send(remote->fd, remote->buf->array, remote->buf->len, 0);
+        if (s == -1) {
+            if (errno == EAGAIN || errno == EWOULDBLOCK) {
+                // no data, wait for send
+                remote->buf->idx = 0;
+                ev_io_stop(EV_A_ & server_recv_ctx->io);
+                ev_io_start(EV_A_ & remote->send_ctx->io);
+            } else {
+                ERROR("server_recv_send");
+                close_and_free_remote(EV_A_ remote);
+                close_and_free_server(EV_A_ server);
+            }
+        } else if (s < remote->buf->len) {
+            remote->buf->len -= s;
+            remote->buf->idx  = s;
+            ev_io_stop(EV_A_ & server_recv_ctx->io);
+            ev_io_start(EV_A_ & remote->send_ctx->io);
+        }
+        return;
+    } else if (server->stage == STAGE_PARSE) {
+        /*
+         * Shadowsocks TCP Relay Header:
+         *
+         *    +------+----------+----------+----------------+
+         *    | ATYP | DST.ADDR | DST.PORT |    HMAC-SHA1   |
+         *    +------+----------+----------+----------------+
+         *    |  1   | Variable |    2     |      10        |
+         *    +------+----------+----------+----------------+
+         *
+         *    If ATYP & ONETIMEAUTH_FLAG(0x10) != 0, Authentication (HMAC-SHA1) is enabled.
+         *
+         *    The key of HMAC-SHA1 is (IV + KEY) and the input is the whole header.
+         *    The output of HMAC-SHA is truncated to 10 bytes (leftmost bits).
+         */
+
+        /*
+         * Shadowsocks Request's Chunk Authentication for TCP Relay's payload
+         * (No chunk authentication for response's payload):
+         *
+         *    +------+-----------+-------------+------+
+         *    | LEN  | HMAC-SHA1 |    DATA     |      ...
+         *    +------+-----------+-------------+------+
+         *    |  2   |    10     |  Variable   |      ...
+         *    +------+-----------+-------------+------+
+         *
+         *    The key of HMAC-SHA1 is (IV + CHUNK ID)
+         *    The output of HMAC-SHA is truncated to 10 bytes (leftmost bits).
+         */
+
+        int offset     = 0;
+        int need_query = 0;
+        char atyp      = server->buf->array[offset++];
+        char host[257] = { 0 };
+        uint16_t port  = 0;
+        struct addrinfo info;
+        struct sockaddr_storage storage;
+        memset(&info, 0, sizeof(struct addrinfo));
+        memset(&storage, 0, sizeof(struct sockaddr_storage));
+
+        if (auth || (atyp & ONETIMEAUTH_FLAG)) {
+            size_t header_len = parse_header_len(atyp, server->buf->array, offset);
+            size_t len        = server->buf->len;
+
+            if (header_len == 0 || len < offset + header_len + ONETIMEAUTH_BYTES) {
+                report_addr(server->fd, MALFORMED);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+
+            server->buf->len = offset + header_len + ONETIMEAUTH_BYTES;
+            if (ss_onetimeauth_verify(server->buf, server->d_ctx->evp.iv)) {
+                report_addr(server->fd, BAD);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+
+            server->buf->len = len;
+            server->auth     = 1;
+        }
+
+        // get remote addr and port
+        if ((atyp & ADDRTYPE_MASK) == 1) {
+            // IP V4
+            struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
+            size_t in_addr_len       = sizeof(struct in_addr);
+            addr->sin_family = AF_INET;
+            if (server->buf->len >= in_addr_len + 3) {
+                addr->sin_addr = *(struct in_addr *)(server->buf->array + offset);
+                dns_ntop(AF_INET, (const void *)(server->buf->array + offset),
+                         host, INET_ADDRSTRLEN);
+                offset += in_addr_len;
+            } else {
+                LOGE("invalid header with addr type %d", atyp);
+                report_addr(server->fd, MALFORMED);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+            addr->sin_port   = *(uint16_t *)(server->buf->array + offset);
+            info.ai_family   = AF_INET;
+            info.ai_socktype = SOCK_STREAM;
+            info.ai_protocol = IPPROTO_TCP;
+            info.ai_addrlen  = sizeof(struct sockaddr_in);
+            info.ai_addr     = (struct sockaddr *)addr;
+        } else if ((atyp & ADDRTYPE_MASK) == 3) {
+            // Domain name
+            uint8_t name_len = *(uint8_t *)(server->buf->array + offset);
+            if (name_len + 4 <= server->buf->len) {
+                memcpy(host, server->buf->array + offset + 1, name_len);
+                offset += name_len + 1;
+            } else {
+                LOGE("invalid name length: %d", name_len);
+                report_addr(server->fd, MALFORMED);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+            if (acl && outbound_block_match_host(host) == 1) {
+                if (verbose)
+                    LOGI("outbound blocked %s", host);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+            struct cork_ip ip;
+            if (cork_ip_init(&ip, host) != -1) {
+                info.ai_socktype = SOCK_STREAM;
+                info.ai_protocol = IPPROTO_TCP;
+                if (ip.version == 4) {
+                    struct sockaddr_in *addr = (struct sockaddr_in *)&storage;
+                    dns_pton(AF_INET, host, &(addr->sin_addr));
+                    addr->sin_port   = *(uint16_t *)(server->buf->array + offset);
+                    addr->sin_family = AF_INET;
+                    info.ai_family   = AF_INET;
+                    info.ai_addrlen  = sizeof(struct sockaddr_in);
+                    info.ai_addr     = (struct sockaddr *)addr;
+                } else if (ip.version == 6) {
+                    struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
+                    dns_pton(AF_INET6, host, &(addr->sin6_addr));
+                    addr->sin6_port   = *(uint16_t *)(server->buf->array + offset);
+                    addr->sin6_family = AF_INET6;
+                    info.ai_family    = AF_INET6;
+                    info.ai_addrlen   = sizeof(struct sockaddr_in6);
+                    info.ai_addr      = (struct sockaddr *)addr;
+                }
+            } else {
+                if (!validate_hostname(host, name_len)) {
+                    LOGE("invalid host name");
+                    report_addr(server->fd, MALFORMED);
+                    close_and_free_server(EV_A_ server);
+                    return;
+                }
+                need_query = 1;
+            }
+        } else if ((atyp & ADDRTYPE_MASK) == 4) {
+            // IP V6
+            struct sockaddr_in6 *addr = (struct sockaddr_in6 *)&storage;
+            size_t in6_addr_len       = sizeof(struct in6_addr);
+            addr->sin6_family = AF_INET6;
+            if (server->buf->len >= in6_addr_len + 3) {
+                addr->sin6_addr = *(struct in6_addr *)(server->buf->array + offset);
+                dns_ntop(AF_INET6, (const void *)(server->buf->array + offset),
+                         host, INET6_ADDRSTRLEN);
+                offset += in6_addr_len;
+            } else {
+                LOGE("invalid header with addr type %d", atyp);
+                report_addr(server->fd, MALFORMED);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+            addr->sin6_port  = *(uint16_t *)(server->buf->array + offset);
+            info.ai_family   = AF_INET6;
+            info.ai_socktype = SOCK_STREAM;
+            info.ai_protocol = IPPROTO_TCP;
+            info.ai_addrlen  = sizeof(struct sockaddr_in6);
+            info.ai_addr     = (struct sockaddr *)addr;
+        }
+
+        if (offset == 1) {
+            LOGE("invalid header with addr type %d", atyp);
+            report_addr(server->fd, MALFORMED);
+            close_and_free_server(EV_A_ server);
+            return;
+        }
+
+        port = (*(uint16_t *)(server->buf->array + offset));
+
+        offset += 2;
+
+        if (server->auth) {
+            offset += ONETIMEAUTH_BYTES;
+        }
+
+        if (server->buf->len < offset) {
+            report_addr(server->fd, MALFORMED);
+            close_and_free_server(EV_A_ server);
+            return;
+        } else {
+            server->buf->len -= offset;
+            memmove(server->buf->array, server->buf->array + offset, server->buf->len);
+        }
+
+        if (verbose) {
+            if ((atyp & ADDRTYPE_MASK) == 4)
+                LOGI("connect to [%s]:%d", host, ntohs(port));
+            else
+                LOGI("connect to %s:%d", host, ntohs(port));
+        }
+
+        if (server->auth && !ss_check_hash(server->buf, server->chunk, server->d_ctx, BUF_SIZE)) {
+            LOGE("hash error");
+            report_addr(server->fd, BAD);
+            close_and_free_server(EV_A_ server);
+            return;
+        }
+
+
+        if (!need_query) {
+            remote_t *remote = connect_to_remote(EV_A_ &info, server);
+
+            if (remote == NULL) {
+                LOGE("connect error");
+                close_and_free_server(EV_A_ server);
+                return;
+            } else {
+                server->remote = remote;
+                remote->server = server;
+
+                // XXX: should handle buffer carefully
+                if (server->buf->len > 0) {
+                    memcpy(remote->buf->array, server->buf->array, server->buf->len);
+                    remote->buf->len = server->buf->len;
+                    remote->buf->idx = 0;
+                    server->buf->len = 0;
+                    server->buf->idx = 0;
+                }
+
+                // waiting on remote connected event
+                ev_io_stop(EV_A_ & server_recv_ctx->io);
+                ev_io_start(EV_A_ & remote->send_ctx->io);
+            }
+        } else {
+            query_t *query = (query_t *)ss_malloc(sizeof(query_t));
+            query->server = server;
+            snprintf(query->hostname, 256, "%s", host);
+
+            server->stage = STAGE_RESOLVE;
+            server->query = resolv_query(host, server_resolve_cb,
+                                         query_free_cb, query, port);
+
+            ev_io_stop(EV_A_ & server_recv_ctx->io);
+        }
+
+        return;
+    }
+    // should not reach here
+    FATAL("server context error");
+}
+
+static void
+server_send_cb(EV_P_ ev_io *w, int revents)
+{
+    server_ctx_t *server_send_ctx = (server_ctx_t *)w;
+    server_t *server              = server_send_ctx->server;
+    remote_t *remote              = server->remote;
+
+    if (remote == NULL) {
+        LOGE("invalid server");
+        close_and_free_server(EV_A_ server);
+        return;
+    }
+
+    if (server->buf->len == 0) {
+        // close and free
+        if (verbose) {
+            LOGI("server_send close the connection");
+        }
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    } else {
+        // has data to send
+        ssize_t s = send(server->fd, server->buf->array + server->buf->idx,
+                         server->buf->len, 0);
+        if (s == -1) {
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                ERROR("server_send_send");
+                close_and_free_remote(EV_A_ remote);
+                close_and_free_server(EV_A_ server);
+            }
+            return;
+        } else if (s < server->buf->len) {
+            // partly sent, move memory, wait for the next time to send
+            server->buf->len -= s;
+            server->buf->idx += s;
+            return;
+        } else {
+            // all sent out, wait for reading
+            server->buf->len = 0;
+            server->buf->idx = 0;
+            ev_io_stop(EV_A_ & server_send_ctx->io);
+            if (remote != NULL) {
+                ev_io_start(EV_A_ & remote->recv_ctx->io);
+                return;
+            } else {
+                LOGE("invalid remote");
+                close_and_free_remote(EV_A_ remote);
+                close_and_free_server(EV_A_ server);
+                return;
+            }
+        }
+    }
+}
+
+static void
+block_list_clear_cb(EV_P_ ev_timer *watcher, int revents)
+{
+    clear_block_list();
+}
+
+static void
+server_timeout_cb(EV_P_ ev_timer *watcher, int revents)
+{
+    server_ctx_t *server_ctx
+        = cork_container_of(watcher, server_ctx_t, watcher);
+    server_t *server = server_ctx->server;
+    remote_t *remote = server->remote;
+
+    if (verbose) {
+        LOGI("TCP connection timeout");
+    }
+
+    if (server->stage < STAGE_PARSE) {
+        if (verbose) {
+            size_t len = server->stage ?
+                         server->header_buf->len : server->buf->len;
+#ifdef __MINGW32__
+            LOGI("incomplete header: %u", len);
+#else
+            LOGI("incomplete header: %zu", len);
+#endif
+        }
+        report_addr(server->fd, SUSPICIOUS);
+    }
+
+    close_and_free_remote(EV_A_ remote);
+    close_and_free_server(EV_A_ server);
+}
+
+static void
+query_free_cb(void *data)
+{
+    if (data != NULL) {
+        ss_free(data);
+    }
+}
+
+static void
+server_resolve_cb(struct sockaddr *addr, void *data)
+{
+    query_t *query       = (query_t *)data;
+    server_t *server     = query->server;
+    struct ev_loop *loop = server->listen_ctx->loop;
+
+    server->query = NULL;
+
+    if (addr == NULL) {
+        LOGE("unable to resolve %s", query->hostname);
+        close_and_free_server(EV_A_ server);
+    } else {
+        if (verbose) {
+            LOGI("successfully resolved %s", query->hostname);
+        }
+
+        struct addrinfo info;
+        memset(&info, 0, sizeof(struct addrinfo));
+        info.ai_socktype = SOCK_STREAM;
+        info.ai_protocol = IPPROTO_TCP;
+        info.ai_addr     = addr;
+
+        if (addr->sa_family == AF_INET) {
+            info.ai_family  = AF_INET;
+            info.ai_addrlen = sizeof(struct sockaddr_in);
+        } else if (addr->sa_family == AF_INET6) {
+            info.ai_family  = AF_INET6;
+            info.ai_addrlen = sizeof(struct sockaddr_in6);
+        }
+
+        remote_t *remote = connect_to_remote(EV_A_ &info, server);
+
+        if (remote == NULL) {
+            close_and_free_server(EV_A_ server);
+        } else {
+            server->remote = remote;
+            remote->server = server;
+
+            // XXX: should handle buffer carefully
+            if (server->buf->len > 0) {
+                memcpy(remote->buf->array, server->buf->array + server->buf->idx,
+                       server->buf->len);
+                remote->buf->len = server->buf->len;
+                remote->buf->idx = 0;
+                server->buf->len = 0;
+                server->buf->idx = 0;
+            }
+
+            // listen to remote connected event
+            ev_io_start(EV_A_ & remote->send_ctx->io);
+        }
+    }
+}
+
+static void
+remote_recv_cb(EV_P_ ev_io *w, int revents)
+{
+    remote_ctx_t *remote_recv_ctx = (remote_ctx_t *)w;
+    remote_t *remote              = remote_recv_ctx->remote;
+    server_t *server              = remote->server;
+
+    if (server == NULL) {
+        LOGE("invalid server");
+        close_and_free_remote(EV_A_ remote);
+        return;
+    }
+
+    ev_timer_again(EV_A_ & server->recv_ctx->watcher);
+
+    ssize_t r = recv(remote->fd, server->buf->array, BUF_SIZE, 0);
+
+    if (r == 0) {
+        // connection closed
+        if (verbose) {
+            LOGI("remote_recv close the connection");
+        }
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    } else if (r == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // no data
+            // continue to wait for recv
+            return;
+        } else {
+            ERROR("remote recv");
+            close_and_free_remote(EV_A_ remote);
+            close_and_free_server(EV_A_ server);
+            return;
+        }
+    }
+
+    rx += r;
+
+    server->buf->len = r;
+
+    // SSR beg
+    server_info _server_info;
+    if (server->obfs_plugin) {
+        server->obfs_plugin->get_server_info(server->obfs, &_server_info);
+        _server_info.head_len = get_head_size(server->buf->array, server->buf->len, 30);
+        server->obfs_plugin->set_server_info(server->obfs, &_server_info);
+    }
+
+    if (server->protocol_plugin && server->obfs_compatible_state == 0) {
+        obfs_class *protocol_plugin = server->protocol_plugin;
+        if (protocol_plugin->server_pre_encrypt) {
+            server->buf->len = protocol_plugin->server_pre_encrypt(server->protocol, &server->buf->array, server->buf->len, &server->buf->capacity);
+        }
+    }
+
+    int err = ss_encrypt(server->buf, server->e_ctx, BUF_SIZE);
+
+    if (err) {
+        LOGE("invalid password or cipher");
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    }
+
+    if (server->obfs_plugin && server->obfs_compatible_state == 0) {
+        obfs_class *obfs_plugin = server->obfs_plugin;
+        if (obfs_plugin->server_encode) {
+            server->buf->len = obfs_plugin->server_encode(server->obfs, &server->buf->array, server->buf->len, &server->buf->capacity);
+        }
+    }
+    // SSR end
+
+    int s = send(server->fd, server->buf->array, server->buf->len, 0);
+
+    if (s == -1) {
+        if (errno == EAGAIN || errno == EWOULDBLOCK) {
+            // no data, wait for send
+            server->buf->idx = 0;
+            ev_io_stop(EV_A_ & remote_recv_ctx->io);
+            ev_io_start(EV_A_ & server->send_ctx->io);
+        } else {
+            ERROR("remote_recv_send");
+            close_and_free_remote(EV_A_ remote);
+            close_and_free_server(EV_A_ server);
+            return;
+        }
+    } else if (s < server->buf->len) {
+        server->buf->len -= s;
+        server->buf->idx  = s;
+        ev_io_stop(EV_A_ & remote_recv_ctx->io);
+        ev_io_start(EV_A_ & server->send_ctx->io);
+    }
+
+    // Disable TCP_NODELAY after the first response are sent
+    if (!remote->recv_ctx->connected) {
+        int opt = 0;
+        setsockopt(server->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
+        setsockopt(remote->fd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
+        remote->recv_ctx->connected = 1;
+    }
+}
+
+static void
+remote_send_cb(EV_P_ ev_io *w, int revents)
+{
+    remote_ctx_t *remote_send_ctx = (remote_ctx_t *)w;
+    remote_t *remote              = remote_send_ctx->remote;
+    server_t *server              = remote->server;
+
+    if (server == NULL) {
+        LOGE("invalid server");
+        close_and_free_remote(EV_A_ remote);
+        return;
+    }
+
+    if (!remote_send_ctx->connected) {
+        struct sockaddr_storage addr;
+        socklen_t len = sizeof(struct sockaddr_storage);
+        memset(&addr, 0, len);
+        int r = getpeername(remote->fd, (struct sockaddr *)&addr, &len);
+        if (r == 0) {
+            if (verbose) {
+                LOGI("remote connected");
+            }
+            remote_send_ctx->connected = 1;
+
+            // Clear the state of this address in the block list
+            reset_addr(server->fd);
+
+            if (remote->buf->len == 0) {
+                server->stage = STAGE_STREAM;
+                ev_io_stop(EV_A_ & remote_send_ctx->io);
+                ev_io_start(EV_A_ & server->recv_ctx->io);
+                ev_io_start(EV_A_ & remote->recv_ctx->io);
+                return;
+            }
+        } else {
+            ERROR("getpeername");
+            // not connected
+            close_and_free_remote(EV_A_ remote);
+            close_and_free_server(EV_A_ server);
+            return;
+        }
+    }
+
+    if (remote->buf->len == 0) {
+        // close and free
+        if (verbose) {
+            LOGI("remote_send close the connection");
+        }
+        close_and_free_remote(EV_A_ remote);
+        close_and_free_server(EV_A_ server);
+        return;
+    } else {
+        // has data to send
+        ssize_t s = send(remote->fd, remote->buf->array + remote->buf->idx,
+                         remote->buf->len, 0);
+        if (s == -1) {
+            if (errno != EAGAIN && errno != EWOULDBLOCK) {
+                ERROR("remote_send_send");
+                // close and free
+                close_and_free_remote(EV_A_ remote);
+                close_and_free_server(EV_A_ server);
+            }
+            return;
+        } else if (s < remote->buf->len) {
+            // partly sent, move memory, wait for the next time to send
+            remote->buf->len -= s;
+            remote->buf->idx += s;
+            return;
+        } else {
+            // all sent out, wait for reading
+            remote->buf->len = 0;
+            remote->buf->idx = 0;
+            ev_io_stop(EV_A_ & remote_send_ctx->io);
+            if (server != NULL) {
+                ev_io_start(EV_A_ & server->recv_ctx->io);
+                if (server->stage != STAGE_STREAM) {
+                    server->stage = STAGE_STREAM;
+                    ev_io_start(EV_A_ & remote->recv_ctx->io);
+                }
+            } else {
+                LOGE("invalid server");
+                close_and_free_remote(EV_A_ remote);
+                close_and_free_server(EV_A_ server);
+            }
+            return;
+        }
+    }
+}
+
+static remote_t *
+new_remote(int fd)
+{
+    if (verbose) {
+        remote_conn++;
+    }
+
+    remote_t *remote;
+
+    remote                      = ss_malloc(sizeof(remote_t));
+    remote->recv_ctx            = ss_malloc(sizeof(remote_ctx_t));
+    remote->send_ctx            = ss_malloc(sizeof(remote_ctx_t));
+    remote->buf                 = ss_malloc(sizeof(buffer_t));
+    remote->fd                  = fd;
+    remote->recv_ctx->remote    = remote;
+    remote->recv_ctx->connected = 0;
+    remote->send_ctx->remote    = remote;
+    remote->send_ctx->connected = 0;
+    remote->server              = NULL;
+
+    ev_io_init(&remote->recv_ctx->io, remote_recv_cb, fd, EV_READ);
+    ev_io_init(&remote->send_ctx->io, remote_send_cb, fd, EV_WRITE);
+
+    balloc(remote->buf, BUF_SIZE);
+
+    return remote;
+}
+
+static void
+free_remote(remote_t *remote)
+{
+    if (remote->server != NULL) {
+        remote->server->remote = NULL;
+    }
+    if (remote->buf != NULL) {
+        bfree(remote->buf);
+        ss_free(remote->buf);
+    }
+    ss_free(remote->recv_ctx);
+    ss_free(remote->send_ctx);
+    ss_free(remote);
+}
+
+static void
+close_and_free_remote(EV_P_ remote_t *remote)
+{
+    if (remote != NULL) {
+        ev_io_stop(EV_A_ & remote->send_ctx->io);
+        ev_io_stop(EV_A_ & remote->recv_ctx->io);
+        close(remote->fd);
+        free_remote(remote);
+        if (verbose) {
+            remote_conn--;
+            LOGI("current remote connection: %d", remote_conn);
+        }
+    }
+}
+
+static server_t *
+new_server(int fd, listen_ctx_t *listener)
+{
+    if (verbose) {
+        server_conn++;
+    }
+
+    server_t *server;
+    server = ss_malloc(sizeof(server_t));
+
+    memset(server, 0, sizeof(server_t));
+
+    server->recv_ctx            = ss_malloc(sizeof(server_ctx_t));
+    server->send_ctx            = ss_malloc(sizeof(server_ctx_t));
+    server->buf                 = ss_malloc(sizeof(buffer_t));
+    server->header_buf          = ss_malloc(sizeof(buffer_t));
+    server->fd                  = fd;
+    server->recv_ctx->server    = server;
+    server->recv_ctx->connected = 0;
+    server->send_ctx->server    = server;
+    server->send_ctx->connected = 0;
+    server->stage               = STAGE_INIT;
+    server->query               = NULL;
+    server->listen_ctx          = listener;
+    server->remote              = NULL;
+
+    if (listener->method) {
+        server->e_ctx = ss_malloc(sizeof(enc_ctx_t));
+        server->d_ctx = ss_malloc(sizeof(enc_ctx_t));
+        enc_ctx_init(listener->method, server->e_ctx, 1);
+        enc_ctx_init(listener->method, server->d_ctx, 0);
+    } else {
+        server->e_ctx = NULL;
+        server->d_ctx = NULL;
+    }
+
+    int request_timeout = min(MAX_REQUEST_TIMEOUT, listener->timeout)
+                          + rand() % MAX_REQUEST_TIMEOUT;
+
+    ev_io_init(&server->recv_ctx->io, server_recv_cb, fd, EV_READ);
+    ev_io_init(&server->send_ctx->io, server_send_cb, fd, EV_WRITE);
+    ev_timer_init(&server->recv_ctx->watcher, server_timeout_cb,
+                  request_timeout, listener->timeout);
+
+    balloc(server->buf, BUF_SIZE);
+    balloc(server->header_buf, BUF_SIZE);
+
+    server->chunk = (chunk_t *)malloc(sizeof(chunk_t));
+    memset(server->chunk, 0, sizeof(chunk_t));
+    server->chunk->buf = ss_malloc(sizeof(buffer_t));
+    memset(server->chunk->buf, 0, sizeof(buffer_t));
+
+    cork_dllist_add(&connections, &server->entries);
+
+    return server;
+}
+
+static void
+free_server(server_t *server)
+{
+    cork_dllist_remove(&server->entries);
+
+    if (server->chunk != NULL) {
+        if (server->chunk->buf != NULL) {
+            bfree(server->chunk->buf);
+            ss_free(server->chunk->buf);
+        }
+        ss_free(server->chunk);
+    }
+    if (server->remote != NULL) {
+        server->remote->server = NULL;
+    }
+    if (server->e_ctx != NULL) {
+        cipher_context_release(&server->e_ctx->evp);
+        ss_free(server->e_ctx);
+    }
+    if (server->d_ctx != NULL) {
+        cipher_context_release(&server->d_ctx->evp);
+        ss_free(server->d_ctx);
+    }
+    if (server->buf != NULL) {
+        bfree(server->buf);
+        ss_free(server->buf);
+    }
+    if (server->header_buf != NULL) {
+        bfree(server->header_buf);
+        ss_free(server->header_buf);
+    }
+
+    ss_free(server->recv_ctx);
+    ss_free(server->send_ctx);
+    ss_free(server);
+}
+
+static void
+close_and_free_server(EV_P_ server_t *server)
+{
+    if (server != NULL) {
+        if (server->query != NULL) {
+            resolv_cancel(server->query);
+            server->query = NULL;
+        }
+        ev_io_stop(EV_A_ & server->send_ctx->io);
+        ev_io_stop(EV_A_ & server->recv_ctx->io);
+        ev_timer_stop(EV_A_ & server->recv_ctx->watcher);
+        close(server->fd);
+        free_server(server);
+        if (verbose) {
+            server_conn--;
+            LOGI("current server connection: %d", server_conn);
+        }
+    }
+}
+
+static void
+signal_cb(EV_P_ ev_signal *w, int revents)
+{
+    if (revents & EV_SIGNAL) {
+        switch (w->signum) {
+        case SIGINT:
+        case SIGTERM:
+            ev_unloop(EV_A_ EVUNLOOP_ALL);
+        }
+    }
+}
+
+static void
+accept_cb(EV_P_ ev_io *w, int revents)
+{
+    listen_ctx_t *listener = (listen_ctx_t *)w;
+    int serverfd           = accept(listener->fd, NULL, NULL);
+    if (serverfd == -1) {
+        ERROR("accept");
+        return;
+    }
+
+    char *peer_name = get_peer_name(serverfd);
+    if (peer_name != NULL) {
+        int in_white_list = 0;
+        if (acl) {
+            if ((get_acl_mode() == BLACK_LIST && acl_match_host(peer_name) == 1)
+                || (get_acl_mode() == WHITE_LIST && acl_match_host(peer_name) >= 0)) {
+                LOGE("Access denied from %s", peer_name);
+                close(serverfd);
+                return;
+            } else if (acl_match_host(peer_name) == -1) {
+                in_white_list = 1;
+            }
+        }
+        if (!in_white_list && check_block_list(peer_name)) {
+            LOGE("block all requests from %s", peer_name);
+#ifdef __linux__
+            set_linger(serverfd);
+#endif
+            close(serverfd);
+            return;
+        }
+    }
+
+    int opt = 1;
+    setsockopt(serverfd, SOL_TCP, TCP_NODELAY, &opt, sizeof(opt));
+#ifdef SO_NOSIGPIPE
+    setsockopt(serverfd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
+#endif
+    setnonblocking(serverfd);
+
+    if (verbose) {
+        LOGI("accept a connection");
+    }
+
+    server_t *server = new_server(serverfd, listener);
+
+    // SSR beg
+    server->obfs_plugin = new_obfs_class(server->listen_ctx->obfs_name);
+    if (server->obfs_plugin) {
+        server->obfs = server->obfs_plugin->new_obfs();
+        server->obfs_compatible_state = 0;
+    }
+    server->protocol_plugin = new_obfs_class(server->listen_ctx->protocol_name);
+    if (server->protocol_plugin) {
+        server->protocol = server->protocol_plugin->new_obfs();
+        server->protocol_compatible_state = 0;
+    }
+    server_info _server_info;
+    memset(&_server_info, 0, sizeof(server_info));
+    _server_info.param = server->listen_ctx->obfs_param;
+    if(server->obfs_plugin)
+        _server_info.g_data = server->obfs_plugin->init_data();
+    _server_info.head_len = 7;
+    _server_info.iv = server->e_ctx->evp.iv;
+    _server_info.iv_len = enc_get_iv_len();
+    _server_info.key = enc_get_key();
+    _server_info.key_len = enc_get_key_len();
+    _server_info.tcp_mss = 1460;
+
+    if (server->obfs_plugin)
+        server->obfs_plugin->set_server_info(server->obfs, &_server_info);
+
+    _server_info.param = server->listen_ctx->protocol_param;
+    if (server->protocol_plugin)
+        _server_info.g_data = server->protocol_plugin->init_data();
+
+    if (server->protocol_plugin)
+        server->protocol_plugin->set_server_info(server->protocol, &_server_info);
+    // SSR end
+
+    ev_io_start(EV_A_ & server->recv_ctx->io);
+    ev_timer_start(EV_A_ & server->recv_ctx->watcher);
+}
+
+int
+main(int argc, char **argv)
+{
+    int i, c;
+    int pid_flags   = 0;
+    int mptcp       = 0;
+    int firewall    = 0;
+    int mtu         = 0;
+    char *user      = NULL;
+    char *password  = NULL;
+    char *timeout   = NULL;
+    char *protocol = NULL; // SSR
+    char *protocol_param = NULL; // SSR
+    char *method = NULL;
+    char *obfs = NULL; // SSR
+    char *obfs_param = NULL; // SSR
+    char *pid_path  = NULL;
+    char *conf_path = NULL;
+    char *iface     = NULL;
+
+    int server_num = 0;
+    const char *server_host[MAX_REMOTE_NUM];
+
+    char *nameservers[MAX_DNS_NUM + 1];
+    int nameserver_num = 0;
+
+    int option_index                    = 0;
+    static struct option long_options[] = {
+        { "fast-open",       no_argument,       0, 0 },
+        { "acl",             required_argument, 0, 0 },
+        { "manager-address", required_argument, 0, 0 },
+        { "mtu",             required_argument, 0, 0 },
+        { "help",            no_argument,       0, 0 },
+#ifdef __linux__
+        { "mptcp",           no_argument,       0, 0 },
+        { "firewall",        no_argument,       0, 0 },
+#endif
+        {                 0,                 0, 0, 0 }
+    };
+
+    opterr = 0;
+
+    USE_TTY();
+
+    while ((c = getopt_long(argc, argv, "f:s:p:l:k:t:m:b:c:i:d:a:n:O:o:G:g:huUvA6",
+                            long_options, &option_index)) != -1) {
+        switch (c) {
+        case 0:
+            if (option_index == 0) {
+                fast_open = 1;
+            } else if (option_index == 1) {
+                LOGI("initializing acl...");
+                acl = !init_acl(optarg);
+            } else if (option_index == 2) {
+                manager_address = optarg;
+            } else if (option_index == 3) {
+                mtu = atoi(optarg);
+                LOGI("set MTU to %d", mtu);
+            } else if (option_index == 4) {
+                usage();
+                exit(EXIT_SUCCESS);
+            } else if (option_index == 5) {
+                mptcp = 1;
+                LOGI("enable multipath TCP");
+            } else if (option_index == 6) {
+                firewall = 1;
+                LOGI("enable firewall rules");
+            }
+            break;
+        case 's':
+            if (server_num < MAX_REMOTE_NUM) {
+                server_host[server_num++] = optarg;
+            }
+            break;
+        case 'b':
+            bind_address = optarg;
+            break;
+        case 'p':
+            server_port = optarg;
+            break;
+        case 'k':
+            password = optarg;
+            break;
+        case 'f':
+            pid_flags = 1;
+            pid_path  = optarg;
+            break;
+        case 't':
+            timeout = optarg;
+            break;
+        // SSR beg
+        case 'O':
+            protocol = optarg;
+            break;
+        case 'm':
+            method = optarg;
+            break;
+        case 'o':
+            obfs = optarg;
+            break;
+        case 'G':
+            protocol_param = optarg;
+            break;
+        case 'g':
+            obfs_param = optarg;
+            break;
+        // SSR end
+        case 'c':
+            conf_path = optarg;
+            break;
+        case 'i':
+            iface = optarg;
+            break;
+        case 'd':
+            if (nameserver_num < MAX_DNS_NUM) {
+                nameservers[nameserver_num++] = optarg;
+            }
+            break;
+        case 'a':
+            user = optarg;
+            break;
+#ifdef HAVE_SETRLIMIT
+        case 'n':
+            nofile = atoi(optarg);
+            break;
+#endif
+        case 'u':
+            mode = TCP_AND_UDP;
+            break;
+        case 'U':
+            mode = UDP_ONLY;
+            break;
+        case 'v':
+            verbose = 1;
+            break;
+        case 'h':
+            usage();
+            exit(EXIT_SUCCESS);
+        case 'A':
+            auth = 1;
+            break;
+        case '6':
+            ipv6first = 1;
+            break;
+        case '?':
+            // The option character is not recognized.
+            LOGE("Unrecognized option: %s", optarg);
+            opterr = 1;
+            break;
+        }
+    }
+
+    if (opterr) {
+        usage();
+        exit(EXIT_FAILURE);
+    }
+
+    if (argc == 1) {
+        if (conf_path == NULL) {
+            conf_path = DEFAULT_CONF_PATH;
+        }
+    }
+
+    if (conf_path != NULL) {
+        jconf_t *conf = read_jconf(conf_path);
+        if (server_num == 0) {
+            server_num = conf->remote_num;
+            for (i = 0; i < server_num; i++)
+                server_host[i] = conf->remote_addr[i].host;
+        }
+        if (server_port == NULL) {
+            server_port = conf->remote_port;
+        }
+        if (password == NULL) {
+            password = conf->password;
+        }
+        // SSR beg
+        if (protocol == NULL) {
+            protocol = conf->protocol;
+            LOGI("protocol %s", protocol);
+        }
+        if (protocol_param == NULL) {
+            protocol_param = conf->protocol_param;
+            LOGI("protocol_param %s", obfs_param);
+        }
+        if (method == NULL) {
+            method = conf->method;
+            LOGI("method %s", method);
+        }
+        if (obfs == NULL) {
+            obfs = conf->obfs;
+            LOGI("obfs %s", obfs);
+        }
+        if (obfs_param == NULL) {
+            obfs_param = conf->obfs_param;
+            LOGI("obfs_param %s", obfs_param);
+        }
+        // SSR end
+        if (timeout == NULL) {
+            timeout = conf->timeout;
+        }
+        if (user == NULL) {
+            user = conf->user;
+        }
+        if (auth == 0) {
+            auth = conf->auth;
+        }
+        if (mode == TCP_ONLY) {
+            mode = conf->mode;
+        }
+        if (mtu == 0) {
+            mtu = conf->mtu;
+        }
+        if (mptcp == 0) {
+            mptcp = conf->mptcp;
+        }
+#ifdef TCP_FASTOPEN
+        if (fast_open == 0) {
+            fast_open = conf->fast_open;
+        }
+#endif
+#ifdef HAVE_SETRLIMIT
+        if (nofile == 0) {
+            nofile = conf->nofile;
+        }
+#endif
+        if (conf->nameserver != NULL) {
+            nameservers[nameserver_num++] = conf->nameserver;
+        }
+        if (ipv6first == 0) {
+            ipv6first = conf->ipv6_first;
+        }
+    }
+
+    //_compatible
+    if(strlen(protocol)>11)
+    {
+        char *text;
+        text = (char*)malloc(12);
+        memcpy(text, protocol + strlen(protocol) - 11, 12);
+
+        if(strcmp(text, "_compatible") == 0)
+        {
+            free(text);
+            text = (char*)malloc(strlen(protocol) - 11);
+            memcpy(text, protocol, strlen(protocol) - 11);
+            int length = strlen(protocol) - 11;
+            free(protocol);
+            obfs = (char*)malloc(length);
+            memset(protocol, 0x00, length);
+            memcpy(protocol, text, length);
+            LOGI("protocol compatible enable, %s", protocol);
+            free(text);
+            protocol_compatible = 1;
+        }
+    }
+
+    if(strlen(obfs)>11)
+    {
+        char *text;
+        text = (char*)malloc(12);
+        memcpy(text, obfs + strlen(obfs) - 11, 12);
+
+        if(strcmp(text, "_compatible") == 0)
+        {
+            free(text);
+            text = (char*)malloc(strlen(obfs) - 11);
+            memcpy(text, obfs, strlen(obfs) - 11);
+            int length = strlen(obfs) - 11;
+            free(obfs);
+            obfs = (char*)malloc(length);
+            memset(obfs, 0x00, length);
+            memcpy(obfs, text, length);
+            LOGI("obfs compatible enable, %s", obfs);
+            free(text);
+            obfs_compatible = 1;
+        }
+    }
+
+
+    if (server_num == 0) {
+        server_host[server_num++] = NULL;
+    }
+
+    if (server_num == 0 || server_port == NULL || password == NULL) {
+        usage();
+        exit(EXIT_FAILURE);
+    }
+
+    if (protocol && strcmp(protocol, "verify_sha1") == 0) {
+        auth = 1;
+        protocol = NULL;
+    }
+
+    if (method == NULL) {
+        method = "rc4-md5";
+    }
+
+    if (timeout == NULL) {
+        timeout = "60";
+    }
+
+#ifdef HAVE_SETRLIMIT
+    /*
+     * no need to check the return value here since we will show
+     * the user an error message if setrlimit(2) fails
+     */
+    if (nofile > 1024) {
+        if (verbose) {
+            LOGI("setting NOFILE to %d", nofile);
+        }
+        set_nofile(nofile);
+    }
+#endif
+
+    if (pid_flags) {
+        USE_SYSLOG(argv[0]);
+        daemonize(pid_path);
+    }
+
+    if (ipv6first) {
+        LOGI("resolving hostname to IPv6 address first");
+    }
+
+    if (fast_open == 1) {
+#ifdef TCP_FASTOPEN
+        LOGI("using tcp fast open");
+#else
+        LOGE("tcp fast open is not supported by this environment");
+        fast_open = 0;
+#endif
+    }
+
+    if (auth) {
+        LOGI("onetime authentication enabled");
+    }
+
+    if (mode != TCP_ONLY) {
+        LOGI("UDP relay enabled");
+    }
+
+    if (mode == UDP_ONLY) {
+        LOGI("TCP relay disabled");
+    }
+
+#ifdef __MINGW32__
+    winsock_init();
+#else
+    // ignore SIGPIPE
+    signal(SIGPIPE, SIG_IGN);
+    signal(SIGCHLD, SIG_IGN);
+    signal(SIGABRT, SIG_IGN);
+#endif
+
+    struct ev_signal sigint_watcher;
+    struct ev_signal sigterm_watcher;
+    ev_signal_init(&sigint_watcher, signal_cb, SIGINT);
+    ev_signal_init(&sigterm_watcher, signal_cb, SIGTERM);
+    ev_signal_start(EV_DEFAULT, &sigint_watcher);
+    ev_signal_start(EV_DEFAULT, &sigterm_watcher);
+
+    // setup keys
+    LOGI("initializing ciphers... %s", method);
+    int m = enc_init(password, method);
+
+    // initialize ev loop
+    struct ev_loop *loop = EV_DEFAULT;
+
+    // setup udns
+    if (nameserver_num == 0) {
+#ifdef __MINGW32__
+        nameservers[nameserver_num++] = "8.8.8.8";
+        resolv_init(loop, nameservers, nameserver_num, ipv6first);
+#else
+        resolv_init(loop, NULL, 0, ipv6first);
+#endif
+    } else {
+        resolv_init(loop, nameservers, nameserver_num, ipv6first);
+    }
+
+    for (int i = 0; i < nameserver_num; i++)
+        LOGI("using nameserver: %s", nameservers[i]);
+
+    // initialize listen context
+    listen_ctx_t listen_ctx_list[server_num];
+
+    // bind to each interface
+    while (server_num > 0) {
+        int index        = --server_num;
+        const char *host = server_host[index];
+
+        if (mode != UDP_ONLY) {
+            // Bind to port
+            int listenfd;
+            listenfd = create_and_bind(host, server_port, mptcp);
+            if (listenfd == -1) {
+                FATAL("bind() error");
+            }
+            if (listen(listenfd, SSMAXCONN) == -1) {
+                FATAL("listen() error");
+            }
+            setfastopen(listenfd);
+            setnonblocking(listenfd);
+            listen_ctx_t *listen_ctx = &listen_ctx_list[index];
+
+            // Setup proxy context
+            listen_ctx->timeout = atoi(timeout);
+            listen_ctx->fd      = listenfd;
+            listen_ctx->method  = m;
+            listen_ctx->iface   = iface;
+
+            // SSR beg
+            listen_ctx->protocol_name = protocol;
+            listen_ctx->protocol_param = protocol_param;
+            listen_ctx->method = m;
+            listen_ctx->obfs_name = obfs;
+            listen_ctx->obfs_param = obfs_param;
+            listen_ctx->list_protocol_global = malloc(sizeof(void *));
+            listen_ctx->list_obfs_global = malloc(sizeof(void *));
+            memset(listen_ctx->list_protocol_global, 0, sizeof(void *));
+            memset(listen_ctx->list_obfs_global, 0, sizeof(void *));
+            // SSR end
+
+            listen_ctx->loop    = loop;
+
+            ev_io_init(&listen_ctx->io, accept_cb, listenfd, EV_READ);
+            ev_io_start(loop, &listen_ctx->io);
+        }
+
+        // Setup UDP
+        if (mode != TCP_ONLY) {
+            init_udprelay(server_host[index], server_port, mtu, m,
+                          auth, atoi(timeout), iface, protocol, protocol_param);
+        }
+
+        if (host && strcmp(host, ":") > 0)
+            LOGI("listening at [%s]:%s", host, server_port);
+        else
+            LOGI("listening at %s:%s", host ? host : "*", server_port);
+    }
+
+    if (manager_address != NULL) {
+        ev_timer_init(&stat_update_watcher, stat_update_cb, UPDATE_INTERVAL, UPDATE_INTERVAL);
+        ev_timer_start(EV_DEFAULT, &stat_update_watcher);
+    }
+
+    ev_timer_init(&block_list_watcher, block_list_clear_cb, UPDATE_INTERVAL, UPDATE_INTERVAL);
+    ev_timer_start(EV_DEFAULT, &block_list_watcher);
+
+    // setuid
+    if (user != NULL && ! run_as(user)) {
+        FATAL("failed to switch user");
+    }
+
+#ifndef __MINGW32__
+    if (geteuid() == 0){
+        LOGI("running from root user");
+    } else if (firewall) {
+        LOGE("firewall setup requires running from root user");
+        exit(-1);
+    }
+#endif
+
+    // init block list
+    init_block_list(firewall);
+
+    // Init connections
+    cork_dllist_init(&connections);
+
+    // start ev loop
+    ev_run(loop, 0);
+
+    if (verbose) {
+        LOGI("closed gracefully");
+    }
+
+    // Free block list
+    free_block_list();
+
+    if (manager_address != NULL) {
+        ev_timer_stop(EV_DEFAULT, &stat_update_watcher);
+    }
+    ev_timer_stop(EV_DEFAULT, &block_list_watcher);
+
+    // Clean up
+    for (int i = 0; i <= server_num; i++) {
+        listen_ctx_t *listen_ctx = &listen_ctx_list[i];
+        if (mode != UDP_ONLY) {
+            ev_io_stop(loop, &listen_ctx->io);
+            close(listen_ctx->fd);
+        }
+    }
+
+    if (mode != UDP_ONLY) {
+        free_connections(loop);
+    }
+
+    if (mode != TCP_ONLY) {
+        free_udprelay();
+    }
+
+    resolv_shutdown(loop);
+
+#ifdef __MINGW32__
+    winsock_cleanup();
+#endif
+
+    ev_signal_stop(EV_DEFAULT, &sigint_watcher);
+    ev_signal_stop(EV_DEFAULT, &sigterm_watcher);
+
+    return 0;
+}
diff --git a/server/server.h b/server/server.h
new file mode 100644
index 0000000..4cd3cf6
--- /dev/null
+++ b/server/server.h
@@ -0,0 +1,115 @@
+/*
+ * server.h - Define shadowsocks server's buffers and callbacks
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _SERVER_H
+#define _SERVER_H
+
+#include <ev.h>
+#include <time.h>
+#include <libcork/ds.h>
+
+#include "encrypt.h"
+#include "jconf.h"
+#include "resolv.h"
+#include "obfs.h"
+#include "protocol.h"
+
+#include "common.h"
+
+typedef struct listen_ctx {
+    ev_io io;
+    int fd;
+    int timeout;
+    int method;
+    char *iface;
+    struct ev_loop *loop;
+
+    // SSR
+    char *protocol_name;
+    char *protocol_param;
+    char *obfs_name;
+    char *obfs_param;
+    void **list_protocol_global;
+    void **list_obfs_global;
+} listen_ctx_t;
+
+typedef struct server_ctx {
+    ev_io io;
+    ev_timer watcher;
+    int connected;
+    struct server *server;
+} server_ctx_t;
+
+typedef struct server {
+    int fd;
+    int stage;
+    buffer_t *buf;
+    ssize_t buf_capacity;
+    buffer_t *header_buf;
+
+    int auth;
+    struct chunk *chunk;
+
+    struct enc_ctx *e_ctx;
+    struct enc_ctx *d_ctx;
+    struct server_ctx *recv_ctx;
+    struct server_ctx *send_ctx;
+    struct listen_ctx *listen_ctx;
+    struct remote *remote;
+
+    struct ResolvQuery *query;
+
+    struct cork_dllist_item entries;
+
+    // SSR
+    obfs *protocol;
+    obfs *obfs;
+    obfs_class *protocol_plugin;
+    obfs_class *obfs_plugin;
+    int obfs_compatible_state;
+    int protocol_compatible_state;
+} server_t;
+
+typedef struct query {
+    server_t *server;
+    char hostname[257];
+} query_t;
+
+typedef struct remote_ctx {
+    ev_io io;
+    int connected;
+    struct remote *remote;
+} remote_ctx_t;
+
+typedef struct remote {
+    int fd;
+    buffer_t *buf;
+    ssize_t buf_capacity;
+    struct remote_ctx *recv_ctx;
+    struct remote_ctx *send_ctx;
+    struct server *server;
+
+    // SSR
+    int remote_index;
+} remote_t;
+
+#endif // _SERVER_H
diff --git a/server/tls.c b/server/tls.c
new file mode 100644
index 0000000..5c42216
--- /dev/null
+++ b/server/tls.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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.
+ */
+/*
+ * This is a minimal TLS implementation intended only to parse the server name
+ * extension.  This was created based primarily on Wireshark dissection of a
+ * TLS handshake and RFC4366.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdio.h>
+#include <stdlib.h> /* malloc() */
+#include <string.h> /* strncpy() */
+
+#ifndef __MINGW32__
+#include <sys/socket.h>
+#else
+#include <win32.h>
+#endif
+
+#include "tls.h"
+#include "protocol.h"
+#include "utils.h"
+
+#define SERVER_NAME_LEN 256
+#define TLS_HEADER_LEN 5
+#define TLS_HANDSHAKE_CONTENT_TYPE 0x16
+#define TLS_HANDSHAKE_TYPE_CLIENT_HELLO 0x01
+
+#ifndef MIN
+#define MIN(X, Y) ((X) < (Y) ? (X) : (Y))
+#endif
+
+extern int verbose;
+
+static int parse_tls_header(const char *, size_t, char **);
+static int parse_extensions(const char *, size_t, char **);
+static int parse_server_name_extension(const char *, size_t, char **);
+
+static const protocol_t tls_protocol_st = {
+    .default_port =               443,
+    .parse_packet = &parse_tls_header,
+};
+const protocol_t *const tls_protocol = &tls_protocol_st;
+
+/* Parse a TLS packet for the Server Name Indication extension in the client
+ * hello handshake, returning the first servername found (pointer to static
+ * array)
+ *
+ * Returns:
+ *  >=0  - length of the hostname and updates *hostname
+ *         caller is responsible for freeing *hostname
+ *  -1   - Incomplete request
+ *  -2   - No Host header included in this request
+ *  -3   - Invalid hostname pointer
+ *  -4   - malloc failure
+ *  < -4 - Invalid TLS client hello
+ */
+static int
+parse_tls_header(const char *data, size_t data_len, char **hostname)
+{
+    char tls_content_type;
+    char tls_version_major;
+    char tls_version_minor;
+    size_t pos = TLS_HEADER_LEN;
+    size_t len;
+
+    if (hostname == NULL)
+        return -3;
+
+    /* Check that our TCP payload is at least large enough for a TLS header */
+    if (data_len < TLS_HEADER_LEN)
+        return -1;
+
+    /* SSL 2.0 compatible Client Hello
+     *
+     * High bit of first byte (length) and content type is Client Hello
+     *
+     * See RFC5246 Appendix E.2
+     */
+    if (data[0] & 0x80 && data[2] == 1) {
+        if (verbose)
+            LOGI("Received SSL 2.0 Client Hello which can not support SNI.");
+        return -2;
+    }
+
+    tls_content_type = data[0];
+    if (tls_content_type != TLS_HANDSHAKE_CONTENT_TYPE) {
+        if (verbose)
+            LOGI("Request did not begin with TLS handshake.");
+        return -5;
+    }
+
+    tls_version_major = data[1];
+    tls_version_minor = data[2];
+    if (tls_version_major < 3) {
+        if (verbose)
+            LOGI("Received SSL %d.%d handshake which can not support SNI.",
+                 tls_version_major, tls_version_minor);
+
+        return -2;
+    }
+
+    /* TLS record length */
+    len = ((unsigned char)data[3] << 8) +
+          (unsigned char)data[4] + TLS_HEADER_LEN;
+    data_len = MIN(data_len, len);
+
+    /* Check we received entire TLS record length */
+    if (data_len < len)
+        return -1;
+
+    /*
+     * Handshake
+     */
+    if (pos + 1 > data_len) {
+        return -5;
+    }
+    if (data[pos] != TLS_HANDSHAKE_TYPE_CLIENT_HELLO) {
+        if (verbose)
+            LOGI("Not a client hello");
+
+        return -5;
+    }
+
+    /* Skip past fixed length records:
+     * 1	Handshake Type
+     * 3	Length
+     * 2	Version (again)
+     * 32	Random
+     * to	Session ID Length
+     */
+    pos += 38;
+
+    /* Session ID */
+    if (pos + 1 > data_len)
+        return -5;
+    len  = (unsigned char)data[pos];
+    pos += 1 + len;
+
+    /* Cipher Suites */
+    if (pos + 2 > data_len)
+        return -5;
+    len  = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
+    pos += 2 + len;
+
+    /* Compression Methods */
+    if (pos + 1 > data_len)
+        return -5;
+    len  = (unsigned char)data[pos];
+    pos += 1 + len;
+
+    if (pos == data_len && tls_version_major == 3 && tls_version_minor == 0) {
+        if (verbose)
+            LOGI("Received SSL 3.0 handshake without extensions");
+        return -2;
+    }
+
+    /* Extensions */
+    if (pos + 2 > data_len)
+        return -5;
+    len  = ((unsigned char)data[pos] << 8) + (unsigned char)data[pos + 1];
+    pos += 2;
+
+    if (pos + len > data_len)
+        return -5;
+    return parse_extensions(data + pos, len, hostname);
+}
+
+static int
+parse_extensions(const char *data, size_t data_len, char **hostname)
+{
+    size_t pos = 0;
+    size_t len;
+
+    /* Parse each 4 bytes for the extension header */
+    while (pos + 4 <= data_len) {
+        /* Extension Length */
+        len = ((unsigned char)data[pos + 2] << 8) +
+              (unsigned char)data[pos + 3];
+
+        /* Check if it's a server name extension */
+        if (data[pos] == 0x00 && data[pos + 1] == 0x00) {
+            /* There can be only one extension of each type, so we break
+             * our state and move p to beinnging of the extension here */
+            if (pos + 4 + len > data_len)
+                return -5;
+            return parse_server_name_extension(data + pos + 4, len, hostname);
+        }
+        pos += 4 + len; /* Advance to the next extension header */
+    }
+    /* Check we ended where we expected to */
+    if (pos != data_len)
+        return -5;
+
+    return -2;
+}
+
+static int
+parse_server_name_extension(const char *data, size_t data_len,
+                            char **hostname)
+{
+    size_t pos = 2; /* skip server name list length */
+    size_t len;
+
+    while (pos + 3 < data_len) {
+        len = ((unsigned char)data[pos + 1] << 8) +
+              (unsigned char)data[pos + 2];
+
+        if (pos + 3 + len > data_len)
+            return -5;
+
+        switch (data[pos]) { /* name type */
+        case 0x00:     /* host_name */
+            *hostname = malloc(len + 1);
+            if (*hostname == NULL) {
+                ERROR("malloc() failure");
+                return -4;
+            }
+
+            strncpy(*hostname, data + pos + 3, len);
+
+            (*hostname)[len] = '\0';
+
+            return len;
+        default:
+            if (verbose)
+                LOGI("Unknown server name extension name type: %d",
+                     data[pos]);
+        }
+        pos += 3 + len;
+    }
+    /* Check we ended where we expected to */
+    if (pos != data_len)
+        return -5;
+
+    return -2;
+}
diff --git a/server/tls.h b/server/tls.h
new file mode 100644
index 0000000..3998913
--- /dev/null
+++ b/server/tls.h
@@ -0,0 +1,33 @@
+/*
+ * Copyright (c) 2011 and 2012, Dustin Lundquist <dustin@null-ptr.net>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ *    this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * 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 HOLDER 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 TLS_H
+#define TLS_H
+
+#include "protocol.h"
+
+const protocol_t *const tls_protocol;
+
+#endif
diff --git a/server/tls1.2_ticket.c b/server/tls1.2_ticket.c
new file mode 100644
index 0000000..88970c0
--- /dev/null
+++ b/server/tls1.2_ticket.c
@@ -0,0 +1,609 @@
+
+#include "tls1.2_ticket.h"
+#include "list.c"
+
+typedef struct tls12_ticket_auth_global_data {
+    uint8_t local_client_id[32];
+    List client_data;
+    time_t startup_time;
+}tls12_ticket_auth_global_data;
+
+typedef struct tls12_ticket_auth_local_data {
+    int handshake_status;
+    char *send_buffer;
+    int send_buffer_size;
+    char *recv_buffer;
+    int recv_buffer_size;
+}tls12_ticket_auth_local_data;
+
+void tls12_ticket_auth_local_data_init(tls12_ticket_auth_local_data* local) {
+    local->handshake_status = 0;
+    local->send_buffer = malloc(0);
+    local->send_buffer_size = 0;
+    local->recv_buffer = malloc(0);
+    local->recv_buffer_size = 0;
+}
+
+void * tls12_ticket_auth_init_data() {
+    tls12_ticket_auth_global_data *global = (tls12_ticket_auth_global_data*)malloc(sizeof(tls12_ticket_auth_global_data));
+    rand_bytes(global->local_client_id, 32);
+    global->client_data = list_init(22);
+    global->startup_time = time(NULL);
+    return global;
+}
+
+obfs * tls12_ticket_auth_new_obfs() {
+    obfs * self = new_obfs();
+    self->l_data = malloc(sizeof(tls12_ticket_auth_local_data));
+    tls12_ticket_auth_local_data_init((tls12_ticket_auth_local_data*)self->l_data);
+    return self;
+}
+
+void tls12_ticket_auth_dispose(obfs *self) {
+    tls12_ticket_auth_local_data *local = (tls12_ticket_auth_local_data*)self->l_data;
+    if (local->send_buffer != NULL) {
+        free(local->send_buffer);
+        local->send_buffer = NULL;
+    }
+    if (local->recv_buffer != NULL) {
+        free(local->recv_buffer);
+        local->recv_buffer = NULL;
+    }
+    free(local);
+    dispose_obfs(self);
+}
+
+int tls12_ticket_pack_auth_data(tls12_ticket_auth_global_data *global, server_info *server, char *outdata) {
+    int out_size = 32;
+    time_t t = time(NULL);
+    outdata[0] = t >> 24;
+    outdata[1] = t >> 16;
+    outdata[2] = t >> 8;
+    outdata[3] = t;
+    rand_bytes((uint8_t*)outdata + 4, 18);
+
+    uint8_t *key = (uint8_t*)malloc(server->key_len + 32);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    memcpy(key, server->key, server->key_len);
+    memcpy(key + server->key_len, global->local_client_id, 32);
+    ss_sha1_hmac_with_key(hash, outdata, out_size - OBFS_HMAC_SHA1_LEN, key, server->key_len + 32);
+    free(key);
+    memcpy(outdata + out_size - OBFS_HMAC_SHA1_LEN, hash, OBFS_HMAC_SHA1_LEN);
+    return out_size;
+}
+
+void tls12_ticket_auth_pack_data(char *encryptdata, int datalength, int start, int len, char *out_buffer, int outlength) {
+    out_buffer[outlength] = 0x17;
+    out_buffer[outlength + 1] = 0x3;
+    out_buffer[outlength + 2] = 0x3;
+    out_buffer[outlength + 3] = len >> 8;
+    out_buffer[outlength + 4] = len;
+    memcpy(out_buffer + outlength + 5, encryptdata + start, len);
+}
+
+int tls12_ticket_auth_client_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity) {
+    char *encryptdata = *pencryptdata;
+    tls12_ticket_auth_local_data *local = (tls12_ticket_auth_local_data*)self->l_data;
+    tls12_ticket_auth_global_data *global = (tls12_ticket_auth_global_data*)self->server.g_data;
+    char * out_buffer = NULL;
+
+    if (local->handshake_status == 8) {
+        if (datalength < 1024) {
+            if (*capacity < datalength + 5) {
+                *pencryptdata = (char*)realloc(*pencryptdata, *capacity = (datalength + 5) * 2);
+                encryptdata = *pencryptdata;
+            }
+            memmove(encryptdata + 5, encryptdata, datalength);
+            encryptdata[0] = 0x17;
+            encryptdata[1] = 0x3;
+            encryptdata[2] = 0x3;
+            encryptdata[3] = datalength >> 8;
+            encryptdata[4] = datalength;
+            return datalength + 5;
+        } else {
+            out_buffer = (char*)malloc(datalength + 2048);
+            int start = 0;
+            int outlength = 0;
+            int len;
+            while (datalength - start > 2048) {
+                len = xorshift128plus() % 4096 + 100;
+                if (len > datalength - start)
+                    len = datalength - start;
+                tls12_ticket_auth_pack_data(encryptdata, datalength, start, len, out_buffer, outlength);
+                outlength += len + 5;
+                start += len;
+            }
+            if (datalength - start > 0) {
+                len = datalength - start;
+                tls12_ticket_auth_pack_data(encryptdata, datalength, start, len, out_buffer, outlength);
+                outlength += len + 5;
+            }
+            if (*capacity < outlength) {
+                *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+                encryptdata = *pencryptdata;
+            }
+            memcpy(encryptdata, out_buffer, outlength);
+            free(out_buffer);
+            return outlength;
+        }
+    }
+    local->send_buffer = (char*)realloc(local->send_buffer, local->send_buffer_size + datalength + 5);
+    memcpy(local->send_buffer + local->send_buffer_size + 5, encryptdata, datalength);
+    local->send_buffer[local->send_buffer_size] = 0x17;
+    local->send_buffer[local->send_buffer_size + 1] = 0x3;
+    local->send_buffer[local->send_buffer_size + 2] = 0x3;
+    local->send_buffer[local->send_buffer_size + 3] = datalength >> 8;
+    local->send_buffer[local->send_buffer_size + 4] = datalength;
+    local->send_buffer_size += datalength + 5;
+
+    if (local->handshake_status == 0) {
+#define CSTR_DECL(name, len, str) const char* name = str; const int len = sizeof(str) - 1;
+        CSTR_DECL(tls_data0, tls_data0_len, "\x00\x1c\xc0\x2b\xc0\x2f\xcc\xa9\xcc\xa8\xcc\x14\xcc\x13\xc0\x0a\xc0\x14\xc0\x09\xc0\x13\x00\x9c\x00\x35\x00\x2f\x00\x0a\x01\x00"
+                );
+        CSTR_DECL(tls_data1, tls_data1_len, "\xff\x01\x00\x01\x00"
+                );
+        CSTR_DECL(tls_data2, tls_data2_len, "\x00\x17\x00\x00\x00\x23\x00\xd0");
+        CSTR_DECL(tls_data3, tls_data3_len, "\x00\x0d\x00\x16\x00\x14\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x03\x01\x03\x03\x02\x01\x02\x03\x00\x05\x00\x05\x01\x00\x00\x00\x00\x00\x12\x00\x00\x75\x50\x00\x00\x00\x0b\x00\x02\x01\x00\x00\x0a\x00\x06\x00\x04\x00\x17\x00\x18"
+                //"00150066000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" // padding
+                );
+        uint8_t tls_data[2048];
+        int tls_data_len = 0;
+        memcpy(tls_data, tls_data1, tls_data1_len);
+        tls_data_len += tls_data1_len;
+
+        char hosts[1024];
+        char * phost[128];
+        int host_num = 0;
+        int pos;
+
+        char sni[256] = {0};
+        if (self->server.param && strlen(self->server.param) == 0)
+            self->server.param = NULL;
+        strncpy(hosts, self->server.param ? self->server.param : self->server.host, sizeof hosts);
+        phost[host_num++] = hosts;
+        for (pos = 0; hosts[pos]; ++pos) {
+            if (hosts[pos] == ',') {
+                phost[host_num++] = &hosts[pos + 1];
+            }
+        }
+        host_num = xorshift128plus() % host_num;
+
+        sprintf(sni, "%s", phost[host_num]);
+        int sni_len = strlen(sni);
+        if (sni_len > 0 && sni[sni_len - 1] >= '0' && sni[sni_len - 1] <= '9')
+            sni_len = 0;
+        tls_data[tls_data_len] = '\0';
+        tls_data[tls_data_len + 1] = '\0';
+        tls_data[tls_data_len + 2] = (sni_len + 5) >> 8;
+        tls_data[tls_data_len + 3] = (sni_len + 5);
+        tls_data[tls_data_len + 4] = (sni_len + 3) >> 8;
+        tls_data[tls_data_len + 5] = (sni_len + 3);
+        tls_data[tls_data_len + 6] = '\0';
+        tls_data[tls_data_len + 7] = sni_len >> 8;
+        tls_data[tls_data_len + 8] = sni_len;
+        memcpy(tls_data + tls_data_len + 9, sni, sni_len);
+        tls_data_len += 9 + sni_len;
+        memcpy(tls_data + tls_data_len, tls_data2, tls_data2_len);
+        tls_data_len += tls_data2_len;
+        rand_bytes(tls_data + tls_data_len, 208);
+        tls_data_len += 208;
+        memcpy(tls_data + tls_data_len, tls_data3, tls_data3_len);
+        tls_data_len += tls_data3_len;
+
+        datalength = 11 + 32 + 1 + 32 + tls_data0_len + 2 + tls_data_len;
+        out_buffer = (char*)malloc(datalength);
+        char *pdata = out_buffer + datalength - tls_data_len;
+        int len = tls_data_len;
+        memcpy(pdata, tls_data, tls_data_len);
+        pdata[-1] = tls_data_len;
+        pdata[-2] = tls_data_len >> 8;
+        pdata -= 2; len += 2;
+        memcpy(pdata - tls_data0_len, tls_data0, tls_data0_len);
+        pdata -= tls_data0_len; len += tls_data0_len;
+        memcpy(pdata - 32, global->local_client_id, 32);
+        pdata -= 32; len += 32;
+        pdata[-1] = 0x20;
+        pdata -= 1; len += 1;
+        tls12_ticket_pack_auth_data(global, &self->server, pdata - 32);
+        pdata -= 32; len += 32;
+        pdata[-1] = 0x3;
+        pdata[-2] = 0x3; // tls version
+        pdata -= 2; len += 2;
+        pdata[-1] = len;
+        pdata[-2] = len >> 8;
+        pdata[-3] = 0;
+        pdata[-4] = 1;
+        pdata -= 4; len += 4;
+
+        pdata[-1] = len;
+        pdata[-2] = len >> 8;
+        pdata -= 2; len += 2;
+        pdata[-1] = 0x1;
+        pdata[-2] = 0x3; // tls version
+        pdata -= 2; len += 2;
+        pdata[-1] = 0x16; // tls handshake
+        pdata -= 1; len += 1;
+
+        local->handshake_status = 1;
+    } else if (datalength == 0) {
+        datalength = local->send_buffer_size + 43;
+        out_buffer = (char*)malloc(datalength);
+        char *pdata = out_buffer;
+        memcpy(pdata, "\x14\x03\x03\x00\x01\x01", 6);
+        pdata += 6;
+        memcpy(pdata, "\x16\x03\x03\x00\x20", 5);
+        pdata += 5;
+        rand_bytes((uint8_t*)pdata, 22);
+        pdata += 22;
+
+        uint8_t *key = (uint8_t*)malloc(self->server.key_len + 32);
+        char hash[ONETIMEAUTH_BYTES * 2];
+        memcpy(key, self->server.key, self->server.key_len);
+        memcpy(key + self->server.key_len, global->local_client_id, 32);
+        ss_sha1_hmac_with_key(hash, out_buffer, pdata - out_buffer, key, self->server.key_len + 32);
+        free(key);
+        memcpy(pdata, hash, OBFS_HMAC_SHA1_LEN);
+
+        pdata += OBFS_HMAC_SHA1_LEN;
+        memcpy(pdata, local->send_buffer, local->send_buffer_size);
+        free(local->send_buffer);
+        local->send_buffer = NULL;
+
+        local->handshake_status = 8;
+    } else {
+        return 0;
+    }
+    if (*capacity < datalength) {
+        *pencryptdata = (char*)realloc(*pencryptdata, *capacity = datalength * 2);
+        encryptdata = *pencryptdata;
+    }
+    memmove(encryptdata, out_buffer, datalength);
+    free(out_buffer);
+    return datalength;
+}
+
+int tls12_ticket_auth_server_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity) {
+    char *encryptdata = *pencryptdata;
+    tls12_ticket_auth_local_data *local = (tls12_ticket_auth_local_data*)self->l_data;
+    tls12_ticket_auth_global_data *global = (tls12_ticket_auth_global_data*)self->server.g_data;
+    char * out_buffer = NULL;
+
+    if (local->handshake_status == 8) {
+        if (datalength < 1024) {
+            if (*capacity < datalength + 5) {
+                *pencryptdata = (char*)realloc(*pencryptdata, *capacity = (datalength + 5) * 2);
+                encryptdata = *pencryptdata;
+            }
+            memmove(encryptdata + 5, encryptdata, datalength);
+            encryptdata[0] = 0x17;
+            encryptdata[1] = 0x3;
+            encryptdata[2] = 0x3;
+            encryptdata[3] = datalength >> 8;
+            encryptdata[4] = datalength;
+            return datalength + 5;
+        } else {
+            out_buffer = (char*)malloc(datalength + 2048);
+            int start = 0;
+            int outlength = 0;
+            int len;
+            while (datalength - start > 2048) {
+                len = xorshift128plus() % 4096 + 100;
+                if (len > datalength - start)
+                    len = datalength - start;
+                tls12_ticket_auth_pack_data(encryptdata, datalength, start, len, out_buffer, outlength);
+                outlength += len + 5;
+                start += len;
+            }
+            if (datalength - start > 0) {
+                len = datalength - start;
+                tls12_ticket_auth_pack_data(encryptdata, datalength, start, len, out_buffer, outlength);
+                outlength += len + 5;
+            }
+            if (*capacity < outlength) {
+                *pencryptdata = (char*)realloc(*pencryptdata, *capacity = outlength * 2);
+                encryptdata = *pencryptdata;
+            }
+            memcpy(encryptdata, out_buffer, outlength);
+            free(out_buffer);
+            return outlength;
+        }
+    }
+
+    local->handshake_status = 3;
+
+    out_buffer = (char*)malloc(43 + 86);
+    int data_len = 0;
+    char *p_data = out_buffer + 86;
+
+    memcpy(p_data - 10, "\xc0\x2f\x00\x00\x05\xff\x01\x00\x01\x00", 10);
+    p_data -= 10;data_len += 10;
+
+    memcpy(p_data - 32, global->local_client_id, 32);
+    p_data -= 32;data_len += 32;
+
+    p_data[-1] = 0x20;
+    p_data -= 1;data_len += 1;
+
+    tls12_ticket_pack_auth_data(global, &self->server, p_data - 32);
+    p_data -= 32;data_len += 32;
+
+    p_data[-1] = 0x3;
+    p_data[-2] = 0x3; // tls version
+    p_data -= 2;data_len += 2;
+
+    p_data[-1] = data_len;
+    p_data[-2] = data_len >> 8;
+    p_data[-3] = 0x00;
+    p_data[-4] = 0x02;
+    p_data -= 4; data_len += 4;
+
+    p_data[-1] = data_len;
+    p_data[-2] = data_len >> 8;
+    p_data[-3] = 0x03;
+    p_data[-4] = 0x03;
+    p_data[-5] = 0x16;
+    p_data -= 5; data_len += 5;
+
+    memcpy(out_buffer, p_data, data_len);
+    char *pdata = out_buffer + 86;
+
+    memcpy(pdata, "\x14\x03\x03\x00\x01\x01", 6);
+    pdata += 6;
+    memcpy(pdata, "\x16\x03\x03\x00\x20", 5);
+    pdata += 5;
+    rand_bytes((uint8_t*)pdata, 22);
+    pdata += 22;
+
+    uint8_t *key = (uint8_t*)malloc(self->server.key_len + 32);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    memcpy(key, self->server.key, self->server.key_len);
+    memcpy(key + self->server.key_len, global->local_client_id, 32);
+    ss_sha1_hmac_with_key(hash, out_buffer, 43 + 86, key, self->server.key_len + 32);
+    free(key);
+    memcpy(pdata, hash, OBFS_HMAC_SHA1_LEN);
+
+    memmove(encryptdata, out_buffer, 43 + 86);
+    free(out_buffer);
+    return 43 + 86;
+}
+
+int tls12_ticket_auth_client_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback) {
+    char *encryptdata = *pencryptdata;
+    tls12_ticket_auth_local_data *local = (tls12_ticket_auth_local_data*)self->l_data;
+    tls12_ticket_auth_global_data *global = (tls12_ticket_auth_global_data*)self->server.g_data;
+
+    *needsendback = 0;
+
+    if (local->handshake_status == 8) {
+        local->recv_buffer_size += datalength;
+        local->recv_buffer = (char*)realloc(local->recv_buffer, local->recv_buffer_size);
+        memcpy(local->recv_buffer + local->recv_buffer_size - datalength, encryptdata, datalength);
+        datalength = 0;
+        while (local->recv_buffer_size > 5) {
+            if (local->recv_buffer[0] != 0x17)
+                return -1;
+            int size = ((int)(unsigned char)local->recv_buffer[3] << 8) + (unsigned char)local->recv_buffer[4];
+            if (size + 5 > local->recv_buffer_size)
+                break;
+            if (*capacity < datalength + size) {
+                *pencryptdata = (char*)realloc(*pencryptdata, *capacity = (datalength + size) * 2);
+                encryptdata = *pencryptdata;
+            }
+            memcpy(encryptdata + datalength, local->recv_buffer + 5, size);
+            datalength += size;
+            local->recv_buffer_size -= 5 + size;
+            memmove(local->recv_buffer, local->recv_buffer + 5 + size, local->recv_buffer_size);
+        }
+        return datalength;
+    }
+    if (datalength < 11 + 32 + 1 + 32) {
+        return -1;
+    }
+
+    uint8_t *key = (uint8_t*)malloc(self->server.key_len + 32);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    memcpy(key, self->server.key, self->server.key_len);
+    memcpy(key + self->server.key_len, global->local_client_id, 32);
+    ss_sha1_hmac_with_key(hash, encryptdata + 11, 22, key, self->server.key_len + 32);
+    free(key);
+
+    if (memcmp(encryptdata + 33, hash, OBFS_HMAC_SHA1_LEN)) {
+        return -1;
+    }
+
+    *needsendback = 1;
+    return 0;
+}
+
+int tls12_ticket_auth_server_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback) {
+    char *encryptdata = *pencryptdata;
+    tls12_ticket_auth_local_data *local = (tls12_ticket_auth_local_data*)self->l_data;
+    tls12_ticket_auth_global_data *global = (tls12_ticket_auth_global_data*)self->server.g_data;
+
+    *needsendback = 0;
+
+    if (local->handshake_status == 8) {
+        if(datalength != 0)
+        {
+            local->recv_buffer = (char*)realloc(local->recv_buffer, local->recv_buffer_size + datalength);
+            memmove(local->recv_buffer + local->recv_buffer_size, encryptdata, datalength);
+            local->recv_buffer_size += datalength;
+        }
+        datalength = 0;
+
+        while (local->recv_buffer_size > 5) {
+            if (local->recv_buffer[0] != 0x17 || local->recv_buffer[1] != 0x03 || local->recv_buffer[2] != 0x03)
+            {
+                LOGE("server_decode data error, wrong tls version 3");
+                return -1;
+            }
+            int size = ((int)(unsigned char)local->recv_buffer[3] << 8) + (unsigned char)local->recv_buffer[4];
+            if (size + 5 > local->recv_buffer_size)
+                break;
+            if (*capacity < local->recv_buffer_size + size) {
+                *pencryptdata = (char*)realloc(*pencryptdata, *capacity = (local->recv_buffer_size + size) * 2);
+                encryptdata = *pencryptdata;
+            }
+            memcpy(encryptdata + datalength, local->recv_buffer + 5, size);
+            datalength += size;
+            local->recv_buffer_size -= 5 + size;
+            memmove(local->recv_buffer, local->recv_buffer + 5 + size, local->recv_buffer_size);
+        }
+        return datalength;
+    }
+
+    if (local->handshake_status == 3) {
+
+        char *verify = encryptdata;
+
+        if(datalength < 43)
+        {
+            LOGE("server_decode data error, too short:%d", (int)datalength);
+            return -1;
+        }
+
+        if(encryptdata[0] != 0x14 || encryptdata[1] != 0x03 || encryptdata[2] != 0x03 || encryptdata[3] != 0x00 || encryptdata[4] != 0x01 || encryptdata[5] != 0x01)
+        {
+            LOGE("server_decode data error, wrong tls version");
+            return -1;
+        }
+
+        encryptdata += 6;
+
+        if(encryptdata[0] != 0x16 || encryptdata[1] != 0x03 || encryptdata[2] != 0x03 || encryptdata[3] != 0x00 || encryptdata[4] != 0x20)
+        {
+            LOGE("server_decode data error, wrong tls version 2");
+            return -1;
+        }
+
+        uint8_t *key = (uint8_t*)malloc(self->server.key_len + 32);
+        char hash[ONETIMEAUTH_BYTES * 2];
+        memcpy(key, self->server.key, self->server.key_len);
+        memcpy(key + self->server.key_len, global->local_client_id, 32);
+        ss_sha1_hmac_with_key(hash, verify, 33, key, self->server.key_len + 32);
+        free(key);
+
+        if (memcmp(verify + 33, hash, OBFS_HMAC_SHA1_LEN) != 0) {
+            LOGE("server_decode data error, hash Mismatch %d",(int)memcmp(verify + 33, hash, OBFS_HMAC_SHA1_LEN));
+            return -1;
+        }
+
+        local->recv_buffer_size = datalength - 43;
+        local->recv_buffer = (char*)realloc(local->recv_buffer, local->recv_buffer_size);
+        memmove(local->recv_buffer, encryptdata + 37, datalength - 43);
+
+        local->handshake_status = 8;
+        return tls12_ticket_auth_server_decode(self, pencryptdata, 0, capacity, needsendback);
+    }
+
+    local->handshake_status = 2;
+    if(encryptdata[0] != 0x16 || encryptdata[1] != 0x03 || encryptdata[2] != 0x01)
+    {
+        return -1;
+    }
+
+    encryptdata += 3;
+
+    {
+        int size = ((int)(unsigned char)encryptdata[0] << 8) + (unsigned char)encryptdata[1];
+        if(size != datalength - 5)
+        {
+            LOGE("tls_auth wrong tls head size");
+            return -1;
+        }
+    }
+
+    encryptdata += 2;
+
+    if(encryptdata[0] != 0x01 || encryptdata[1] != 0x00)
+    {
+        LOGE("tls_auth not client hello message");
+        return -1;
+    }
+
+    encryptdata += 2;
+
+    {
+        int size = ((int)(unsigned char)encryptdata[0] << 8) + (unsigned char)encryptdata[1];
+        if(size != datalength - 9)
+        {
+            LOGE("tls_auth wrong message size");
+            return -1;
+        }
+    }
+
+    encryptdata += 2;
+
+    if(encryptdata[0] != 0x03 || encryptdata[1] != 0x03)
+    {
+        LOGE("tls_auth wrong tls version");
+        return -1;
+    }
+
+    encryptdata += 2;
+
+    char *verifyid = encryptdata;
+
+    encryptdata += 32;
+
+    int sessionid_len = encryptdata[0];
+    if(sessionid_len < 32)
+    {
+        LOGE("tls_auth wrong sessionid_len");
+        return -1;
+    }
+
+    char *sessionid = encryptdata + 1;
+    memcpy(global->local_client_id , sessionid, sessionid_len);
+
+    uint8_t *key = (uint8_t*)malloc(self->server.key_len + sessionid_len);
+    char hash[ONETIMEAUTH_BYTES * 2];
+    memcpy(key, self->server.key, self->server.key_len);
+    memcpy(key + self->server.key_len, global->local_client_id, sessionid_len);
+    ss_sha1_hmac_with_key(hash, verifyid, 22, key, self->server.key_len + sessionid_len);
+    free(key);
+
+    encryptdata += (sessionid_len + 1);
+
+    long utc_time = ((int)(unsigned char)verifyid[0] << 24) + ((int)(unsigned char)verifyid[1] << 16) + ((int)(unsigned char)verifyid[2] << 8) + (unsigned char)verifyid[3];
+    time_t t = time(NULL);
+
+
+    if (self->server.param && strlen(self->server.param) == 0)
+    {
+        self->server.param = NULL;
+    }
+
+    int max_time_dif = 0;
+    int time_dif = utc_time - t;
+    if(self->server.param)
+    {
+        max_time_dif = atoi(self->server.param);
+    }
+
+    if(max_time_dif > 0 && (time_dif < -max_time_dif || time_dif > max_time_dif || utc_time - global->startup_time < -max_time_dif / 2))
+    {
+        LOGE("tls_auth wrong time");
+        return -1;
+    }
+
+    if (memcmp(verifyid + 22, hash, OBFS_HMAC_SHA1_LEN)) {
+        LOGE("tls_auth wrong sha1");
+        return -1;
+    }
+
+    int search_result = global->client_data->have_same_cmp(global->client_data, verifyid);
+    if(search_result != 0)
+    {
+        LOGE("replay attack detect!");
+        return -1;
+    }
+
+    global->client_data->add_back(global->client_data, verifyid);
+
+    encryptdata += 48;
+
+    *needsendback = 1;
+
+    return 0;
+}
diff --git a/server/tls1.2_ticket.h b/server/tls1.2_ticket.h
new file mode 100644
index 0000000..10a57c9
--- /dev/null
+++ b/server/tls1.2_ticket.h
@@ -0,0 +1,20 @@
+/*
+ * http_simple.h - Define shadowsocksR server's buffers and callbacks
+ *
+ * Copyright (C) 2015 - 2016, Break Wa11 <mmgac001@gmail.com>
+ */
+
+#ifndef _TLS1_2_TICKET_H
+#define _TLS1_2_TICKET_H
+
+void * tls12_ticket_auth_init_data();
+obfs * tls12_ticket_auth_new_obfs();
+void tls12_ticket_auth_dispose(obfs *self);
+
+int tls12_ticket_auth_client_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity);
+int tls12_ticket_auth_client_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback);
+
+int tls12_ticket_auth_server_encode(obfs *self, char **pencryptdata, int datalength, size_t* capacity);
+int tls12_ticket_auth_server_decode(obfs *self, char **pencryptdata, int datalength, size_t* capacity, int *needsendback);
+
+#endif // _TLS1_2_TICKET_H
diff --git a/server/udprelay.c b/server/udprelay.c
new file mode 100644
index 0000000..d9251ee
--- /dev/null
+++ b/server/udprelay.c
@@ -0,0 +1,1452 @@
+/*
+ * udprelay.c - Setup UDP relay for both client and server
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <signal.h>
+#include <string.h>
+#include <strings.h>
+#include <time.h>
+#include <unistd.h>
+
+#ifndef __MINGW32__
+#include <arpa/inet.h>
+#include <errno.h>
+#include <netdb.h>
+#include <netinet/in.h>
+#include <pthread.h>
+#endif
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#if defined(HAVE_SYS_IOCTL_H) && defined(HAVE_NET_IF_H) && defined(__linux__)
+#include <net/if.h>
+#include <sys/ioctl.h>
+#define SET_INTERFACE
+#endif
+
+#ifdef __MINGW32__
+#include "win32.h"
+#endif
+
+#include <libcork/core.h>
+#include <udns.h>
+
+#include "utils.h"
+#include "netutils.h"
+#include "cache.h"
+#include "udprelay.h"
+
+#ifdef MODULE_REMOTE
+#define MAX_UDP_CONN_NUM 512
+#else
+#define MAX_UDP_CONN_NUM 256
+#endif
+
+#ifdef MODULE_REMOTE
+#ifdef MODULE_
+#error "MODULE_REMOTE and MODULE_LOCAL should not be both defined"
+#endif
+#endif
+
+#ifndef EAGAIN
+#define EAGAIN EWOULDBLOCK
+#endif
+
+#ifndef EWOULDBLOCK
+#define EWOULDBLOCK EAGAIN
+#endif
+
+static void server_recv_cb(EV_P_ ev_io *w, int revents);
+static void remote_recv_cb(EV_P_ ev_io *w, int revents);
+static void remote_timeout_cb(EV_P_ ev_timer *watcher, int revents);
+
+static char *hash_key(const int af, const struct sockaddr_storage *addr);
+#ifdef MODULE_REMOTE
+static void query_resolve_cb(struct sockaddr *addr, void *data);
+#endif
+static void close_and_free_remote(EV_P_ remote_ctx_t *ctx);
+static remote_ctx_t *new_remote(int fd, server_ctx_t *server_ctx);
+
+#ifdef ANDROID
+extern uint64_t tx;
+extern uint64_t rx;
+extern int vpn;
+#endif
+
+extern int verbose;
+#ifdef MODULE_REMOTE
+extern uint64_t tx;
+extern uint64_t rx;
+#endif
+
+static int packet_size                               = DEFAULT_PACKET_SIZE;
+static int buf_size                                  = DEFAULT_PACKET_SIZE * 2;
+static int server_num                                = 0;
+static server_ctx_t *server_ctx_list[MAX_REMOTE_NUM] = { NULL };
+
+#ifndef __MINGW32__
+static int
+setnonblocking(int fd)
+{
+    int flags;
+    if (-1 == (flags = fcntl(fd, F_GETFL, 0))) {
+        flags = 0;
+    }
+    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
+}
+
+#endif
+
+#if defined(MODULE_REMOTE) && defined(SO_BROADCAST)
+static int
+set_broadcast(int socket_fd)
+{
+    int opt = 1;
+    return setsockopt(socket_fd, SOL_SOCKET, SO_BROADCAST, &opt, sizeof(opt));
+}
+
+#endif
+
+#ifdef SO_NOSIGPIPE
+static int
+set_nosigpipe(int socket_fd)
+{
+    int opt = 1;
+    return setsockopt(socket_fd, SOL_SOCKET, SO_NOSIGPIPE, &opt, sizeof(opt));
+}
+
+#endif
+
+#ifdef MODULE_REDIR
+
+#ifndef IP_TRANSPARENT
+#define IP_TRANSPARENT       19
+#endif
+
+#ifndef IP_RECVORIGDSTADDR
+#define IP_RECVORIGDSTADDR   20
+#endif
+
+static int
+get_dstaddr(struct msghdr *msg, struct sockaddr_storage *dstaddr)
+{
+    struct cmsghdr *cmsg;
+
+    for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
+        if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_RECVORIGDSTADDR) {
+            memcpy(dstaddr, CMSG_DATA(cmsg), sizeof(struct sockaddr_in));
+            dstaddr->ss_family = AF_INET;
+            return 0;
+        } else if (cmsg->cmsg_level == SOL_IPV6 && cmsg->cmsg_type == IP_RECVORIGDSTADDR) {
+            memcpy(dstaddr, CMSG_DATA(cmsg), sizeof(struct sockaddr_in6));
+            dstaddr->ss_family = AF_INET6;
+            return 0;
+        }
+    }
+
+    return 1;
+}
+
+#endif
+
+#define HASH_KEY_LEN sizeof(struct sockaddr_storage) + sizeof(int)
+static char *
+hash_key(const int af, const struct sockaddr_storage *addr)
+{
+    size_t addr_len = sizeof(struct sockaddr_storage);
+    static char key[HASH_KEY_LEN];
+
+    memset(key, 0, HASH_KEY_LEN);
+    memcpy(key, &af, sizeof(int));
+    memcpy(key + sizeof(int), (const uint8_t *)addr, addr_len);
+
+    return key;
+}
+
+#if defined(MODULE_REDIR) || defined(MODULE_REMOTE)
+static int
+construct_udprealy_header(const struct sockaddr_storage *in_addr,
+                          char *addr_header)
+{
+    int addr_header_len = 0;
+    if (in_addr->ss_family == AF_INET) {
+        struct sockaddr_in *addr = (struct sockaddr_in *)in_addr;
+        size_t addr_len          = sizeof(struct in_addr);
+        addr_header[addr_header_len++] = 1;
+        memcpy(addr_header + addr_header_len, &addr->sin_addr, addr_len);
+        addr_header_len += addr_len;
+        memcpy(addr_header + addr_header_len, &addr->sin_port, 2);
+        addr_header_len += 2;
+    } else if (in_addr->ss_family == AF_INET6) {
+        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)in_addr;
+        size_t addr_len           = sizeof(struct in6_addr);
+        addr_header[addr_header_len++] = 4;
+        memcpy(addr_header + addr_header_len, &addr->sin6_addr, addr_len);
+        addr_header_len += addr_len;
+        memcpy(addr_header + addr_header_len, &addr->sin6_port, 2);
+        addr_header_len += 2;
+    } else {
+        return 0;
+    }
+    return addr_header_len;
+}
+
+#endif
+
+static int
+parse_udprealy_header(const char *buf, const size_t buf_len,
+                      char *host, char *port, struct sockaddr_storage *storage)
+{
+    const uint8_t atyp = *(uint8_t *)buf;
+    int offset         = 1;
+
+    // get remote addr and port
+    if ((atyp & ADDRTYPE_MASK) == 1) {
+        // IP V4
+        size_t in_addr_len = sizeof(struct in_addr);
+        if (buf_len >= in_addr_len + 3) {
+            if (storage != NULL) {
+                struct sockaddr_in *addr = (struct sockaddr_in *)storage;
+                addr->sin_family = AF_INET;
+                addr->sin_addr   = *(struct in_addr *)(buf + offset);
+                addr->sin_port   = *(uint16_t *)(buf + offset + in_addr_len);
+            }
+            if (host != NULL) {
+                dns_ntop(AF_INET, (const void *)(buf + offset),
+                         host, INET_ADDRSTRLEN);
+            }
+            offset += in_addr_len;
+        }
+    } else if ((atyp & ADDRTYPE_MASK) == 3) {
+        // Domain name
+        uint8_t name_len = *(uint8_t *)(buf + offset);
+        if (name_len + 4 <= buf_len) {
+            if (storage != NULL) {
+                char tmp[257] = { 0 };
+                struct cork_ip ip;
+                memcpy(tmp, buf + offset + 1, name_len);
+                if (cork_ip_init(&ip, tmp) != -1) {
+                    if (ip.version == 4) {
+                        struct sockaddr_in *addr = (struct sockaddr_in *)storage;
+                        dns_pton(AF_INET, tmp, &(addr->sin_addr));
+                        addr->sin_port   = *(uint16_t *)(buf + offset + 1 + name_len);
+                        addr->sin_family = AF_INET;
+                    } else if (ip.version == 6) {
+                        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)storage;
+                        dns_pton(AF_INET, tmp, &(addr->sin6_addr));
+                        addr->sin6_port   = *(uint16_t *)(buf + offset + 1 + name_len);
+                        addr->sin6_family = AF_INET6;
+                    }
+                }
+            }
+            if (host != NULL) {
+                memcpy(host, buf + offset + 1, name_len);
+            }
+            offset += 1 + name_len;
+        }
+    } else if ((atyp & ADDRTYPE_MASK) == 4) {
+        // IP V6
+        size_t in6_addr_len = sizeof(struct in6_addr);
+        if (buf_len >= in6_addr_len + 3) {
+            if (storage != NULL) {
+                struct sockaddr_in6 *addr = (struct sockaddr_in6 *)storage;
+                addr->sin6_family = AF_INET6;
+                addr->sin6_addr   = *(struct in6_addr *)(buf + offset);
+                addr->sin6_port   = *(uint16_t *)(buf + offset + in6_addr_len);
+            }
+            if (host != NULL) {
+                dns_ntop(AF_INET6, (const void *)(buf + offset),
+                         host, INET6_ADDRSTRLEN);
+            }
+            offset += in6_addr_len;
+        }
+    }
+
+    if (offset == 1) {
+        LOGE("[udp] invalid header with addr type %d", atyp);
+        return 0;
+    }
+
+    if (port != NULL) {
+        sprintf(port, "%d", ntohs(*(uint16_t *)(buf + offset)));
+    }
+    offset += 2;
+
+    return offset;
+}
+
+static char *
+get_addr_str(const struct sockaddr *sa)
+{
+    static char s[SS_ADDRSTRLEN];
+    memset(s, 0, SS_ADDRSTRLEN);
+    char addr[INET6_ADDRSTRLEN] = { 0 };
+    char port[PORTSTRLEN]       = { 0 };
+    uint16_t p;
+
+    switch (sa->sa_family) {
+    case AF_INET:
+        dns_ntop(AF_INET, &(((struct sockaddr_in *)sa)->sin_addr),
+                 addr, INET_ADDRSTRLEN);
+        p = ntohs(((struct sockaddr_in *)sa)->sin_port);
+        sprintf(port, "%d", p);
+        break;
+
+    case AF_INET6:
+        dns_ntop(AF_INET6, &(((struct sockaddr_in6 *)sa)->sin6_addr),
+                 addr, INET6_ADDRSTRLEN);
+        p = ntohs(((struct sockaddr_in *)sa)->sin_port);
+        sprintf(port, "%d", p);
+        break;
+
+    default:
+        strncpy(s, "Unknown AF", SS_ADDRSTRLEN);
+    }
+
+    int addr_len = strlen(addr);
+    int port_len = strlen(port);
+    memcpy(s, addr, addr_len);
+    memcpy(s + addr_len + 1, port, port_len);
+    s[addr_len] = ':';
+
+    return s;
+}
+
+int
+create_remote_socket(int ipv6)
+{
+    int remote_sock;
+
+    if (ipv6) {
+        // Try to bind IPv6 first
+        struct sockaddr_in6 addr;
+        memset(&addr, 0, sizeof(struct sockaddr_in6));
+        addr.sin6_family = AF_INET6;
+        addr.sin6_addr   = in6addr_any;
+        addr.sin6_port   = 0;
+        remote_sock      = socket(AF_INET6, SOCK_DGRAM, 0);
+        if (remote_sock == -1) {
+            ERROR("[udp] cannot create socket");
+            return -1;
+        }
+        if (bind(remote_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+            FATAL("[udp] cannot bind remote");
+            return -1;
+        }
+    } else {
+        // Or else bind to IPv4
+        struct sockaddr_in addr;
+        memset(&addr, 0, sizeof(struct sockaddr_in));
+        addr.sin_family      = AF_INET;
+        addr.sin_addr.s_addr = INADDR_ANY;
+        addr.sin_port        = 0;
+        remote_sock          = socket(AF_INET, SOCK_DGRAM, 0);
+        if (remote_sock == -1) {
+            ERROR("[udp] cannot create socket");
+            return -1;
+        }
+
+        if (bind(remote_sock, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+            FATAL("[udp] cannot bind remote");
+            return -1;
+        }
+    }
+    return remote_sock;
+}
+
+int
+create_server_socket(const char *host, const char *port)
+{
+    struct addrinfo hints;
+    struct addrinfo *result, *rp, *ipv4v6bindall;
+    int s, server_sock;
+
+    memset(&hints, 0, sizeof(struct addrinfo));
+    hints.ai_family   = AF_UNSPEC;               /* Return IPv4 and IPv6 choices */
+    hints.ai_socktype = SOCK_DGRAM;              /* We want a UDP socket */
+    hints.ai_flags    = AI_PASSIVE | AI_ADDRCONFIG; /* For wildcard IP address */
+    hints.ai_protocol = IPPROTO_UDP;
+
+    s = getaddrinfo(host, port, &hints, &result);
+    if (s != 0) {
+        LOGE("[udp] getaddrinfo: %s", gai_strerror(s));
+        return -1;
+    }
+
+    rp = result;
+
+    /*
+     * On Linux, with net.ipv6.bindv6only = 0 (the default), getaddrinfo(NULL) with
+     * AI_PASSIVE returns 0.0.0.0 and :: (in this order). AI_PASSIVE was meant to
+     * return a list of addresses to listen on, but it is impossible to listen on
+     * 0.0.0.0 and :: at the same time, if :: implies dualstack mode.
+     */
+    if (!host) {
+        ipv4v6bindall = result;
+
+        /* Loop over all address infos found until a IPV6 address is found. */
+        while (ipv4v6bindall) {
+            if (ipv4v6bindall->ai_family == AF_INET6) {
+                rp = ipv4v6bindall; /* Take first IPV6 address available */
+                break;
+            }
+            ipv4v6bindall = ipv4v6bindall->ai_next; /* Get next address info, if any */
+        }
+    }
+
+    for (/*rp = result*/; rp != NULL; rp = rp->ai_next) {
+        server_sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+        if (server_sock == -1) {
+            continue;
+        }
+
+        if (rp->ai_family == AF_INET6) {
+            int ipv6only = host ? 1 : 0;
+            setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &ipv6only, sizeof(ipv6only));
+        }
+
+        int opt = 1;
+        setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+#ifdef SO_NOSIGPIPE
+        set_nosigpipe(server_sock);
+#endif
+        int err = set_reuseport(server_sock);
+        if (err == 0) {
+            LOGI("udp port reuse enabled");
+        }
+#ifdef IP_TOS
+        // Set QoS flag
+        int tos = 46;
+        setsockopt(server_sock, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+#endif
+
+#ifdef MODULE_REDIR
+        if (setsockopt(server_sock, SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt))) {
+            ERROR("[udp] setsockopt IP_TRANSPARENT");
+            exit(EXIT_FAILURE);
+        }
+        if (setsockopt(server_sock, IPPROTO_IP, IP_RECVORIGDSTADDR, &opt, sizeof(opt))) {
+            FATAL("[udp] setsockopt IP_RECVORIGDSTADDR");
+        }
+#endif
+
+        s = bind(server_sock, rp->ai_addr, rp->ai_addrlen);
+        if (s == 0) {
+            /* We managed to bind successfully! */
+            break;
+        } else {
+            ERROR("[udp] bind");
+        }
+
+        close(server_sock);
+    }
+
+    if (rp == NULL) {
+        LOGE("[udp] cannot bind");
+        return -1;
+    }
+
+    freeaddrinfo(result);
+
+    return server_sock;
+}
+
+remote_ctx_t *
+new_remote(int fd, server_ctx_t *server_ctx)
+{
+    remote_ctx_t *ctx = ss_malloc(sizeof(remote_ctx_t));
+    memset(ctx, 0, sizeof(remote_ctx_t));
+
+    ctx->fd         = fd;
+    ctx->server_ctx = server_ctx;
+
+    ev_io_init(&ctx->io, remote_recv_cb, fd, EV_READ);
+    ev_timer_init(&ctx->watcher, remote_timeout_cb, server_ctx->timeout,
+                  server_ctx->timeout);
+
+    return ctx;
+}
+
+server_ctx_t *
+new_server_ctx(int fd)
+{
+    server_ctx_t *ctx = ss_malloc(sizeof(server_ctx_t));
+    memset(ctx, 0, sizeof(server_ctx_t));
+
+    ctx->fd = fd;
+
+    ev_io_init(&ctx->io, server_recv_cb, fd, EV_READ);
+
+    return ctx;
+}
+
+#ifdef MODULE_REMOTE
+struct query_ctx *
+new_query_ctx(char *buf, size_t len)
+{
+    struct query_ctx *ctx = ss_malloc(sizeof(struct query_ctx));
+    memset(ctx, 0, sizeof(struct query_ctx));
+    ctx->buf = ss_malloc(sizeof(buffer_t));
+    balloc(ctx->buf, len);
+    memcpy(ctx->buf->array, buf, len);
+    ctx->buf->len = len;
+    return ctx;
+}
+
+void
+close_and_free_query(EV_P_ struct query_ctx *ctx)
+{
+    if (ctx != NULL) {
+        if (ctx->query != NULL) {
+            resolv_cancel(ctx->query);
+            ctx->query = NULL;
+        }
+        if (ctx->buf != NULL) {
+            bfree(ctx->buf);
+            ss_free(ctx->buf);
+        }
+        ss_free(ctx);
+    }
+}
+
+#endif
+
+void
+close_and_free_remote(EV_P_ remote_ctx_t *ctx)
+{
+    if (ctx != NULL) {
+        ev_timer_stop(EV_A_ & ctx->watcher);
+        ev_io_stop(EV_A_ & ctx->io);
+        close(ctx->fd);
+        ss_free(ctx);
+    }
+}
+
+static void
+remote_timeout_cb(EV_P_ ev_timer *watcher, int revents)
+{
+    remote_ctx_t *remote_ctx
+        = cork_container_of(watcher, remote_ctx_t, watcher);
+
+    if (verbose) {
+        LOGI("[udp] connection timeout");
+    }
+
+    char *key = hash_key(remote_ctx->af, &remote_ctx->src_addr);
+    cache_remove(remote_ctx->server_ctx->conn_cache, key, HASH_KEY_LEN);
+}
+
+#ifdef MODULE_REMOTE
+static void
+query_resolve_cb(struct sockaddr *addr, void *data)
+{
+    struct query_ctx *query_ctx = (struct query_ctx *)data;
+    struct ev_loop *loop        = query_ctx->server_ctx->loop;
+
+    if (verbose) {
+        LOGI("[udp] udns resolved");
+    }
+
+    query_ctx->query = NULL;
+
+    if (addr == NULL) {
+        LOGE("[udp] udns returned an error");
+    } else {
+        remote_ctx_t *remote_ctx = query_ctx->remote_ctx;
+        int cache_hit            = 0;
+
+        // Lookup in the conn cache
+        if (remote_ctx == NULL) {
+            char *key = hash_key(AF_UNSPEC, &query_ctx->src_addr);
+            cache_lookup(query_ctx->server_ctx->conn_cache, key, HASH_KEY_LEN, (void *)&remote_ctx);
+        }
+
+        if (remote_ctx == NULL) {
+            int remotefd = create_remote_socket(addr->sa_family == AF_INET6);
+            if (remotefd != -1) {
+                setnonblocking(remotefd);
+#ifdef SO_BROADCAST
+                set_broadcast(remotefd);
+#endif
+#ifdef SO_NOSIGPIPE
+                set_nosigpipe(remotefd);
+#endif
+#ifdef IP_TOS
+                // Set QoS flag
+                int tos = 46;
+                setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+#endif
+#ifdef SET_INTERFACE
+                if (query_ctx->server_ctx->iface) {
+                    if (setinterface(remotefd, query_ctx->server_ctx->iface) == -1)
+                        ERROR("setinterface");
+                }
+#endif
+                remote_ctx                  = new_remote(remotefd, query_ctx->server_ctx);
+                remote_ctx->src_addr        = query_ctx->src_addr;
+                remote_ctx->server_ctx      = query_ctx->server_ctx;
+                remote_ctx->addr_header_len = query_ctx->addr_header_len;
+                memcpy(remote_ctx->addr_header, query_ctx->addr_header,
+                       query_ctx->addr_header_len);
+            } else {
+                ERROR("[udp] bind() error");
+            }
+        } else {
+            cache_hit = 1;
+        }
+
+        if (remote_ctx != NULL) {
+            memcpy(&remote_ctx->dst_addr, addr, sizeof(struct sockaddr_storage));
+
+            size_t addr_len = get_sockaddr_len(addr);
+            int s           = sendto(remote_ctx->fd, query_ctx->buf->array, query_ctx->buf->len,
+                                     0, addr, addr_len);
+
+            if (s == -1) {
+                ERROR("[udp] sendto_remote");
+                if (!cache_hit) {
+                    close_and_free_remote(EV_A_ remote_ctx);
+                }
+            } else {
+                if (!cache_hit) {
+                    // Add to conn cache
+                    char *key = hash_key(AF_UNSPEC, &remote_ctx->src_addr);
+                    cache_insert(query_ctx->server_ctx->conn_cache, key, HASH_KEY_LEN, (void *)remote_ctx);
+                    ev_io_start(EV_A_ & remote_ctx->io);
+                    ev_timer_start(EV_A_ & remote_ctx->watcher);
+                }
+            }
+        }
+    }
+
+    // clean up
+    close_and_free_query(EV_A_ query_ctx);
+}
+
+#endif
+
+static void
+remote_recv_cb(EV_P_ ev_io *w, int revents)
+{
+    ssize_t r;
+    remote_ctx_t *remote_ctx = (remote_ctx_t *)w;
+    server_ctx_t *server_ctx = remote_ctx->server_ctx;
+
+    // server has been closed
+    if (server_ctx == NULL) {
+        LOGE("[udp] invalid server");
+        close_and_free_remote(EV_A_ remote_ctx);
+        return;
+    }
+
+    struct sockaddr_storage src_addr;
+    socklen_t src_addr_len = sizeof(struct sockaddr_storage);
+    memset(&src_addr, 0, src_addr_len);
+
+    buffer_t *buf = ss_malloc(sizeof(buffer_t));
+    balloc(buf, buf_size);
+
+    // recv
+    r = recvfrom(remote_ctx->fd, buf->array, buf_size, 0, (struct sockaddr *)&src_addr, &src_addr_len);
+
+    if (r == -1) {
+        // error on recv
+        // simply drop that packet
+        ERROR("[udp] remote_recv_recvfrom");
+        goto CLEAN_UP;
+    } else if (r > packet_size) {
+        LOGE("[udp] remote_recv_recvfrom fragmentation");
+        goto CLEAN_UP;
+    }
+
+    buf->len = r;
+
+#ifdef MODULE_LOCAL
+    int err = ss_decrypt_all(buf, server_ctx->method, 0, buf_size);
+    if (err) {
+        // drop the packet silently
+        goto CLEAN_UP;
+    }
+
+    //SSR beg
+    if (server_ctx->protocol_plugin) {
+        obfs_class *protocol_plugin = server_ctx->protocol_plugin;
+        if (protocol_plugin->client_udp_post_decrypt) {
+            buf->len = protocol_plugin->client_udp_post_decrypt(server_ctx->protocol, &buf->array, buf->len, &buf->capacity);
+            if ((int)buf->len < 0) {
+                LOGE("client_udp_post_decrypt");
+                close_and_free_remote(EV_A_ remote_ctx);
+                return;
+            }
+            if ( buf->len == 0 )
+                return;
+        }
+    }
+    // SSR end
+
+#ifdef MODULE_REDIR
+    struct sockaddr_storage dst_addr;
+    memset(&dst_addr, 0, sizeof(struct sockaddr_storage));
+    int len = parse_udprealy_header(buf->array, buf->len, NULL, NULL, &dst_addr);
+
+    if (dst_addr.ss_family != AF_INET && dst_addr.ss_family != AF_INET6) {
+        LOGI("[udp] ss-redir does not support domain name");
+        goto CLEAN_UP;
+    }
+
+    if (verbose) {
+        char src[SS_ADDRSTRLEN];
+        char dst[SS_ADDRSTRLEN];
+        strcpy(src, get_addr_str((struct sockaddr *)&src_addr));
+        strcpy(dst, get_addr_str((struct sockaddr *)&dst_addr));
+        LOGI("[udp] recv %s via %s", dst, src);
+    }
+#else
+    int len = parse_udprealy_header(buf->array, buf->len, NULL, NULL, NULL);
+#endif
+
+    if (len == 0) {
+        LOGI("[udp] error in parse header");
+        // error in parse header
+        goto CLEAN_UP;
+    }
+
+    // server may return using a different address type other than the type we
+    // have used during sending
+#if defined(MODULE_TUNNEL) || defined(MODULE_REDIR)
+    // Construct packet
+    buf->len -= len;
+    memmove(buf->array, buf->array + len, buf->len);
+#else
+#ifdef ANDROID
+    rx += buf->len;
+#endif
+    // Construct packet
+    brealloc(buf, buf->len + 3, buf_size);
+    memmove(buf->array + 3, buf->array, buf->len);
+    memset(buf->array, 0, 3);
+    buf->len += 3;
+#endif
+
+#endif
+
+#ifdef MODULE_REMOTE
+
+    rx += buf->len;
+
+    char addr_header_buf[512];
+    char *addr_header   = remote_ctx->addr_header;
+    int addr_header_len = remote_ctx->addr_header_len;
+
+    if (remote_ctx->af == AF_INET || remote_ctx->af == AF_INET6) {
+        addr_header_len = construct_udprealy_header(&src_addr, addr_header_buf);
+        addr_header     = addr_header_buf;
+    }
+
+    // Construct packet
+    brealloc(buf, buf->len + addr_header_len, buf_size);
+    memmove(buf->array + addr_header_len, buf->array, buf->len);
+    memcpy(buf->array, addr_header, addr_header_len);
+    buf->len += addr_header_len;
+
+    int err = ss_encrypt_all(buf, server_ctx->method, 0, buf_size);
+    if (err) {
+        // drop the packet silently
+        goto CLEAN_UP;
+    }
+
+#endif
+
+    if (buf->len > packet_size) {
+        LOGE("[udp] remote_recv_sendto fragmentation");
+        goto CLEAN_UP;
+    }
+
+    size_t remote_src_addr_len = get_sockaddr_len((struct sockaddr *)&remote_ctx->src_addr);
+
+#ifdef MODULE_REDIR
+
+    size_t remote_dst_addr_len = get_sockaddr_len((struct sockaddr *)&dst_addr);
+
+    int src_fd = socket(remote_ctx->src_addr.ss_family, SOCK_DGRAM, 0);
+    if (src_fd < 0) {
+        ERROR("[udp] remote_recv_socket");
+        goto CLEAN_UP;
+    }
+    int opt = 1;
+    if (setsockopt(src_fd, SOL_IP, IP_TRANSPARENT, &opt, sizeof(opt))) {
+        ERROR("[udp] remote_recv_setsockopt");
+        close(src_fd);
+        goto CLEAN_UP;
+    }
+    if (setsockopt(src_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt))) {
+        ERROR("[udp] remote_recv_setsockopt");
+        close(src_fd);
+        goto CLEAN_UP;
+    }
+#ifdef IP_TOS
+    // Set QoS flag
+    int tos = 46;
+    setsockopt(src_fd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+#endif
+    if (bind(src_fd, (struct sockaddr *)&dst_addr, remote_dst_addr_len) != 0) {
+        ERROR("[udp] remote_recv_bind");
+        close(src_fd);
+        goto CLEAN_UP;
+    }
+
+    int s = sendto(src_fd, buf->array, buf->len, 0,
+                   (struct sockaddr *)&remote_ctx->src_addr, remote_src_addr_len);
+    if (s == -1) {
+        ERROR("[udp] remote_recv_sendto");
+        close(src_fd);
+        goto CLEAN_UP;
+    }
+    close(src_fd);
+
+#else
+
+    int s = sendto(server_ctx->fd, buf->array, buf->len, 0,
+                   (struct sockaddr *)&remote_ctx->src_addr, remote_src_addr_len);
+    if (s == -1) {
+        ERROR("[udp] remote_recv_sendto");
+        goto CLEAN_UP;
+    }
+
+#endif
+
+    // handle the UDP packet successfully,
+    // triger the timer
+    ev_timer_again(EV_A_ & remote_ctx->watcher);
+
+CLEAN_UP:
+
+    bfree(buf);
+    ss_free(buf);
+}
+
+static void
+server_recv_cb(EV_P_ ev_io *w, int revents)
+{
+    server_ctx_t *server_ctx = (server_ctx_t *)w;
+    struct sockaddr_storage src_addr;
+    memset(&src_addr, 0, sizeof(struct sockaddr_storage));
+
+    buffer_t *buf = ss_malloc(sizeof(buffer_t));
+    balloc(buf, buf_size);
+
+    socklen_t src_addr_len = sizeof(struct sockaddr_storage);
+    unsigned int offset    = 0;
+
+#ifdef MODULE_REDIR
+    char control_buffer[64] = { 0 };
+    struct msghdr msg;
+    memset(&msg, 0, sizeof(struct msghdr));
+    struct iovec iov[1];
+    struct sockaddr_storage dst_addr;
+    memset(&dst_addr, 0, sizeof(struct sockaddr_storage));
+
+    msg.msg_name       = &src_addr;
+    msg.msg_namelen    = src_addr_len;
+    msg.msg_control    = control_buffer;
+    msg.msg_controllen = sizeof(control_buffer);
+
+    iov[0].iov_base = buf->array;
+    iov[0].iov_len  = buf_size;
+    msg.msg_iov     = iov;
+    msg.msg_iovlen  = 1;
+
+    buf->len = recvmsg(server_ctx->fd, &msg, 0);
+    if (buf->len == -1) {
+        ERROR("[udp] server_recvmsg");
+        goto CLEAN_UP;
+    } else if (buf->len > packet_size) {
+        ERROR("[udp] UDP server_recv_recvmsg fragmentation");
+        goto CLEAN_UP;
+    }
+
+    if (get_dstaddr(&msg, &dst_addr)) {
+        LOGE("[udp] unable to get dest addr");
+        goto CLEAN_UP;
+    }
+
+    src_addr_len = msg.msg_namelen;
+#else
+    ssize_t r;
+    r = recvfrom(server_ctx->fd, buf->array, buf_size,
+                 0, (struct sockaddr *)&src_addr, &src_addr_len);
+
+    if (r == -1) {
+        // error on recv
+        // simply drop that packet
+        ERROR("[udp] server_recv_recvfrom");
+        goto CLEAN_UP;
+    } else if (r > packet_size) {
+        ERROR("[udp] server_recv_recvfrom fragmentation");
+        goto CLEAN_UP;
+    }
+
+    buf->len = r;
+#endif
+
+#ifdef MODULE_REMOTE
+    tx += buf->len;
+
+    int err = ss_decrypt_all(buf, server_ctx->method, server_ctx->auth, buf_size);
+    if (err) {
+        // drop the packet silently
+        goto CLEAN_UP;
+    }
+#endif
+
+#ifdef MODULE_LOCAL
+#if !defined(MODULE_TUNNEL) && !defined(MODULE_REDIR)
+#ifdef ANDROID
+    tx += buf->len;
+#endif
+    uint8_t frag = *(uint8_t *)(buf->array + 2);
+    offset += 3;
+#endif
+#endif
+
+    /*
+     *
+     * SOCKS5 UDP Request
+     * +----+------+------+----------+----------+----------+
+     * |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
+     * +----+------+------+----------+----------+----------+
+     * | 2  |  1   |  1   | Variable |    2     | Variable |
+     * +----+------+------+----------+----------+----------+
+     *
+     * SOCKS5 UDP Response
+     * +----+------+------+----------+----------+----------+
+     * |RSV | FRAG | ATYP | DST.ADDR | DST.PORT |   DATA   |
+     * +----+------+------+----------+----------+----------+
+     * | 2  |  1   |  1   | Variable |    2     | Variable |
+     * +----+------+------+----------+----------+----------+
+     *
+     * shadowsocks UDP Request (before encrypted)
+     * +------+----------+----------+----------+-------------+
+     * | ATYP | DST.ADDR | DST.PORT |   DATA   |  HMAC-SHA1  |
+     * +------+----------+----------+----------+-------------+
+     * |  1   | Variable |    2     | Variable |     10      |
+     * +------+----------+----------+----------+-------------+
+     *
+     * If ATYP & ONETIMEAUTH_FLAG(0x10) != 0, Authentication (HMAC-SHA1) is enabled.
+     *
+     * The key of HMAC-SHA1 is (IV + KEY) and the input is the whole packet.
+     * The output of HMAC-SHA is truncated to 10 bytes (leftmost bits).
+     *
+     * shadowsocks UDP Response (before encrypted)
+     * +------+----------+----------+----------+
+     * | ATYP | DST.ADDR | DST.PORT |   DATA   |
+     * +------+----------+----------+----------+
+     * |  1   | Variable |    2     | Variable |
+     * +------+----------+----------+----------+
+     *
+     * shadowsocks UDP Request and Response (after encrypted)
+     * +-------+--------------+
+     * |   IV  |    PAYLOAD   |
+     * +-------+--------------+
+     * | Fixed |   Variable   |
+     * +-------+--------------+
+     *
+     */
+
+#ifdef MODULE_REDIR
+    if (verbose) {
+        char src[SS_ADDRSTRLEN];
+        char dst[SS_ADDRSTRLEN];
+        strcpy(src, get_addr_str((struct sockaddr *)&src_addr));
+        strcpy(dst, get_addr_str((struct sockaddr *)&dst_addr));
+        LOGI("[udp] redir to %s from %s", dst, src);
+    }
+
+    char addr_header[512] = { 0 };
+    int addr_header_len   = construct_udprealy_header(&dst_addr, addr_header);
+
+    if (addr_header_len == 0) {
+        LOGE("[udp] failed to parse tproxy addr");
+        goto CLEAN_UP;
+    }
+
+    // reconstruct the buffer
+    brealloc(buf, buf->len + addr_header_len, buf_size);
+    memmove(buf->array + addr_header_len, buf->array, buf->len);
+    memcpy(buf->array, addr_header, addr_header_len);
+    buf->len += addr_header_len;
+
+#elif MODULE_TUNNEL
+
+    char addr_header[512] = { 0 };
+    char *host            = server_ctx->tunnel_addr.host;
+    char *port            = server_ctx->tunnel_addr.port;
+    uint16_t port_num     = (uint16_t)atoi(port);
+    uint16_t port_net_num = htons(port_num);
+    int addr_header_len   = 0;
+
+    struct cork_ip ip;
+    if (cork_ip_init(&ip, host) != -1) {
+        if (ip.version == 4) {
+            // send as IPv4
+            struct in_addr host_addr;
+            memset(&host_addr, 0, sizeof(struct in_addr));
+            int host_len = sizeof(struct in_addr);
+
+            if (dns_pton(AF_INET, host, &host_addr) == -1) {
+                FATAL("IP parser error");
+            }
+            addr_header[addr_header_len++] = 1;
+            memcpy(addr_header + addr_header_len, &host_addr, host_len);
+            addr_header_len += host_len;
+        } else if (ip.version == 6) {
+            // send as IPv6
+            struct in6_addr host_addr;
+            memset(&host_addr, 0, sizeof(struct in6_addr));
+            int host_len = sizeof(struct in6_addr);
+
+            if (dns_pton(AF_INET6, host, &host_addr) == -1) {
+                FATAL("IP parser error");
+            }
+            addr_header[addr_header_len++] = 4;
+            memcpy(addr_header + addr_header_len, &host_addr, host_len);
+            addr_header_len += host_len;
+        } else {
+            FATAL("IP parser error");
+        }
+    } else {
+        // send as domain
+        int host_len = strlen(host);
+
+        addr_header[addr_header_len++] = 3;
+        addr_header[addr_header_len++] = host_len;
+        memcpy(addr_header + addr_header_len, host, host_len);
+        addr_header_len += host_len;
+    }
+    memcpy(addr_header + addr_header_len, &port_net_num, 2);
+    addr_header_len += 2;
+
+    // reconstruct the buffer
+    brealloc(buf, buf->len + addr_header_len, buf_size);
+    memmove(buf->array + addr_header_len, buf->array, buf->len);
+    memcpy(buf->array, addr_header, addr_header_len);
+    buf->len += addr_header_len;
+
+#else
+
+    char host[257] = { 0 };
+    char port[64]  = { 0 };
+    struct sockaddr_storage dst_addr;
+    memset(&dst_addr, 0, sizeof(struct sockaddr_storage));
+
+    int addr_header_len = parse_udprealy_header(buf->array + offset, buf->len - offset,
+                                                host, port, &dst_addr);
+    if (addr_header_len == 0) {
+        // error in parse header
+        goto CLEAN_UP;
+    }
+
+    char *addr_header = buf->array + offset;
+#endif
+
+#ifdef MODULE_LOCAL
+    char *key = hash_key(server_ctx->remote_addr->sa_family, &src_addr);
+#else
+    char *key = hash_key(dst_addr.ss_family, &src_addr);
+#endif
+
+    struct cache *conn_cache = server_ctx->conn_cache;
+
+    remote_ctx_t *remote_ctx = NULL;
+    cache_lookup(conn_cache, key, HASH_KEY_LEN, (void *)&remote_ctx);
+
+    if (remote_ctx != NULL) {
+        if (sockaddr_cmp(&src_addr, &remote_ctx->src_addr, sizeof(src_addr))) {
+            remote_ctx = NULL;
+        }
+    }
+
+    // reset the timer
+    if (remote_ctx != NULL) {
+        ev_timer_again(EV_A_ & remote_ctx->watcher);
+    }
+
+    if (remote_ctx == NULL) {
+        if (verbose) {
+#ifdef MODULE_REDIR
+            char src[SS_ADDRSTRLEN];
+            char dst[SS_ADDRSTRLEN];
+            strcpy(src, get_addr_str((struct sockaddr *)&src_addr));
+            strcpy(dst, get_addr_str((struct sockaddr *)&dst_addr));
+            LOGI("[udp] cache miss: %s <-> %s", dst, src);
+#else
+            LOGI("[udp] cache miss: %s:%s <-> %s", host, port,
+                 get_addr_str((struct sockaddr *)&src_addr));
+#endif
+        }
+    } else {
+        if (verbose) {
+#ifdef MODULE_REDIR
+            char src[SS_ADDRSTRLEN];
+            char dst[SS_ADDRSTRLEN];
+            strcpy(src, get_addr_str((struct sockaddr *)&src_addr));
+            strcpy(dst, get_addr_str((struct sockaddr *)&dst_addr));
+            LOGI("[udp] cache hit: %s <-> %s", dst, src);
+#else
+            LOGI("[udp] cache hit: %s:%s <-> %s", host, port,
+                 get_addr_str((struct sockaddr *)&src_addr));
+#endif
+        }
+    }
+
+#ifdef MODULE_LOCAL
+
+#if !defined(MODULE_TUNNEL) && !defined(MODULE_REDIR)
+    if (frag) {
+        LOGE("[udp] drop a message since frag is not 0, but %d", frag);
+        goto CLEAN_UP;
+    }
+#endif
+
+    const struct sockaddr *remote_addr = server_ctx->remote_addr;
+    const int remote_addr_len          = server_ctx->remote_addr_len;
+
+    if (remote_ctx == NULL) {
+        // Bind to any port
+        int remotefd = create_remote_socket(remote_addr->sa_family == AF_INET6);
+        if (remotefd < 0) {
+            ERROR("[udp] udprelay bind() error");
+            goto CLEAN_UP;
+        }
+        setnonblocking(remotefd);
+
+#ifdef SO_NOSIGPIPE
+        set_nosigpipe(remotefd);
+#endif
+#ifdef IP_TOS
+        // Set QoS flag
+        int tos = 46;
+        setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+#endif
+#ifdef SET_INTERFACE
+        if (server_ctx->iface) {
+            if (setinterface(remotefd, server_ctx->iface) == -1)
+                ERROR("setinterface");
+        }
+#endif
+
+#ifdef ANDROID
+        if (vpn) {
+            if (protect_socket(remotefd) == -1) {
+                ERROR("protect_socket");
+                close(remotefd);
+                goto CLEAN_UP;
+            }
+        }
+#endif
+
+        // Init remote_ctx
+        remote_ctx                  = new_remote(remotefd, server_ctx);
+        remote_ctx->src_addr        = src_addr;
+        remote_ctx->af              = remote_addr->sa_family;
+        remote_ctx->addr_header_len = addr_header_len;
+        memcpy(remote_ctx->addr_header, addr_header, addr_header_len);
+
+        // Add to conn cache
+        cache_insert(conn_cache, key, HASH_KEY_LEN, (void *)remote_ctx);
+
+        // Start remote io
+        ev_io_start(EV_A_ & remote_ctx->io);
+        ev_timer_start(EV_A_ & remote_ctx->watcher);
+    }
+
+    if (offset > 0) {
+        buf->len -= offset;
+        memmove(buf->array, buf->array + offset, buf->len);
+    }
+
+    if (server_ctx->auth) {
+        buf->array[0] |= ONETIMEAUTH_FLAG;
+    }
+
+    // SSR beg
+    if (server_ctx->protocol_plugin) {
+        obfs_class *protocol_plugin = server_ctx->protocol_plugin;
+        if (protocol_plugin->client_udp_pre_encrypt) {
+            buf->len = protocol_plugin->client_udp_pre_encrypt(server_ctx->protocol, &buf->array, buf->len, &buf->capacity);
+        }
+    }
+    //SSR end
+
+    int err = ss_encrypt_all(buf, server_ctx->method, server_ctx->auth, buf->len);
+
+    if (err) {
+        // drop the packet silently
+        goto CLEAN_UP;
+    }
+
+    if (buf->len > packet_size) {
+        LOGE("[udp] server_recv_sendto fragmentation");
+        goto CLEAN_UP;
+    }
+
+    int s = sendto(remote_ctx->fd, buf->array, buf->len, 0, remote_addr, remote_addr_len);
+
+    if (s == -1) {
+        ERROR("[udp] server_recv_sendto");
+    }
+
+#else
+
+    int cache_hit  = 0;
+    int need_query = 0;
+
+    if (buf->len - addr_header_len > packet_size) {
+        LOGE("[udp] server_recv_sendto fragmentation");
+        goto CLEAN_UP;
+    }
+
+    if (remote_ctx != NULL) {
+        cache_hit = 1;
+        // detect destination mismatch
+        if (remote_ctx->addr_header_len != addr_header_len
+            || memcmp(addr_header, remote_ctx->addr_header, addr_header_len) != 0) {
+            if (dst_addr.ss_family != AF_INET && dst_addr.ss_family != AF_INET6) {
+                need_query = 1;
+            }
+        } else {
+            memcpy(&dst_addr, &remote_ctx->dst_addr, sizeof(struct sockaddr_storage));
+        }
+    } else {
+        if (dst_addr.ss_family == AF_INET || dst_addr.ss_family == AF_INET6) {
+            int remotefd = create_remote_socket(dst_addr.ss_family == AF_INET6);
+            if (remotefd != -1) {
+                setnonblocking(remotefd);
+#ifdef SO_BROADCAST
+                set_broadcast(remotefd);
+#endif
+#ifdef SO_NOSIGPIPE
+                set_nosigpipe(remotefd);
+#endif
+#ifdef IP_TOS
+                // Set QoS flag
+                int tos = 46;
+                setsockopt(remotefd, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+#endif
+#ifdef SET_INTERFACE
+                if (server_ctx->iface) {
+                    if (setinterface(remotefd, server_ctx->iface) == -1)
+                        ERROR("setinterface");
+                }
+#endif
+                remote_ctx                  = new_remote(remotefd, server_ctx);
+                remote_ctx->src_addr        = src_addr;
+                remote_ctx->server_ctx      = server_ctx;
+                remote_ctx->addr_header_len = addr_header_len;
+                memcpy(remote_ctx->addr_header, addr_header, addr_header_len);
+                memcpy(&remote_ctx->dst_addr, &dst_addr, sizeof(struct sockaddr_storage));
+            } else {
+                ERROR("[udp] bind() error");
+                goto CLEAN_UP;
+            }
+        }
+    }
+
+    if (remote_ctx != NULL && !need_query) {
+        size_t addr_len = get_sockaddr_len((struct sockaddr *)&dst_addr);
+        int s           = sendto(remote_ctx->fd, buf->array + addr_header_len,
+                                 buf->len - addr_header_len, 0,
+                                 (struct sockaddr *)&dst_addr, addr_len);
+
+        if (s == -1) {
+            ERROR("[udp] sendto_remote");
+            if (!cache_hit) {
+                close_and_free_remote(EV_A_ remote_ctx);
+            }
+        } else {
+            if (!cache_hit) {
+                // Add to conn cache
+                remote_ctx->af = dst_addr.ss_family;
+                char *key = hash_key(remote_ctx->af, &remote_ctx->src_addr);
+                cache_insert(server_ctx->conn_cache, key, HASH_KEY_LEN, (void *)remote_ctx);
+
+                ev_io_start(EV_A_ & remote_ctx->io);
+                ev_timer_start(EV_A_ & remote_ctx->watcher);
+            }
+        }
+    } else {
+        struct addrinfo hints;
+        memset(&hints, 0, sizeof(struct addrinfo));
+        hints.ai_family   = AF_UNSPEC;
+        hints.ai_socktype = SOCK_DGRAM;
+        hints.ai_protocol = IPPROTO_UDP;
+
+        struct query_ctx *query_ctx = new_query_ctx(buf->array + addr_header_len,
+                                                    buf->len - addr_header_len);
+        query_ctx->server_ctx      = server_ctx;
+        query_ctx->addr_header_len = addr_header_len;
+        query_ctx->src_addr        = src_addr;
+        memcpy(query_ctx->addr_header, addr_header, addr_header_len);
+
+        if (need_query) {
+            query_ctx->remote_ctx = remote_ctx;
+        }
+
+        struct ResolvQuery *query = resolv_query(host, query_resolve_cb,
+                                                 NULL, query_ctx, htons(atoi(port)));
+        if (query == NULL) {
+            ERROR("[udp] unable to create DNS query");
+            close_and_free_query(EV_A_ query_ctx);
+            goto CLEAN_UP;
+        }
+        query_ctx->query = query;
+    }
+#endif
+
+CLEAN_UP:
+    bfree(buf);
+    ss_free(buf);
+}
+
+void
+free_cb(void *key, void *element)
+{
+    remote_ctx_t *remote_ctx = (remote_ctx_t *)element;
+
+    if (verbose) {
+        LOGI("[udp] one connection freed");
+    }
+
+    close_and_free_remote(EV_DEFAULT, remote_ctx);
+}
+
+int
+init_udprelay(const char *server_host, const char *server_port,
+#ifdef MODULE_LOCAL
+              const struct sockaddr *remote_addr, const int remote_addr_len,
+#ifdef MODULE_TUNNEL
+              const ss_addr_t tunnel_addr,
+#endif
+#endif
+              int mtu, int method, int auth, int timeout, const char *iface, const char *protocol, const char *protocol_param)
+{
+    // Initialize ev loop
+    struct ev_loop *loop = EV_DEFAULT;
+
+    // Initialize MTU
+    if (mtu > 0) {
+        packet_size = mtu - 1 - 28 - 2 - 64;
+        buf_size    = packet_size * 2;
+    }
+
+    // Initialize cache
+    struct cache *conn_cache;
+    cache_create(&conn_cache, MAX_UDP_CONN_NUM, free_cb);
+
+    // ////////////////////////////////////////////////
+    // Setup server context
+
+    // Bind to port
+    int serverfd = create_server_socket(server_host, server_port);
+    if (serverfd < 0) {
+        FATAL("[udp] bind() error");
+    }
+    setnonblocking(serverfd);
+    if (protocol != NULL && strcmp(protocol, "verify_sha1") == 0) {
+        auth = 1;
+        protocol = NULL;
+    }
+
+    server_ctx_t *server_ctx = new_server_ctx(serverfd);
+#ifdef MODULE_REMOTE
+    server_ctx->loop = loop;
+#endif
+    server_ctx->auth       = auth;
+    server_ctx->timeout    = max(timeout, MIN_UDP_TIMEOUT);
+    server_ctx->method     = method;
+    server_ctx->iface      = iface;
+    server_ctx->conn_cache = conn_cache;
+#ifdef MODULE_LOCAL
+    server_ctx->remote_addr     = remote_addr;
+    server_ctx->remote_addr_len = remote_addr_len;
+    //SSR beg
+    server_ctx->protocol_plugin = new_obfs_class((char *)protocol);
+    if (server_ctx->protocol_plugin) {
+        server_ctx->protocol = server_ctx->protocol_plugin->new_obfs();
+        server_ctx->protocol_global = server_ctx->protocol_plugin->init_data();
+    }
+
+    server_info _server_info;
+    memset(&_server_info, 0, sizeof(server_info));
+    strcpy(_server_info.host, inet_ntoa(((struct sockaddr_in*)remote_addr)->sin_addr));
+    _server_info.port = ((struct sockaddr_in*)remote_addr)->sin_port;
+    _server_info.port = _server_info.port >> 8 | _server_info.port << 8;
+    _server_info.g_data = server_ctx->protocol_global;
+    _server_info.param = (char *)protocol_param;
+    _server_info.key = enc_get_key();
+    _server_info.key_len = enc_get_key_len();
+
+    if (server_ctx->protocol_plugin)
+        server_ctx->protocol_plugin->set_server_info(server_ctx->protocol, &_server_info);
+    //SSR end
+#ifdef MODULE_TUNNEL
+    server_ctx->tunnel_addr = tunnel_addr;
+#endif
+#endif
+
+    ev_io_start(loop, &server_ctx->io);
+
+    server_ctx_list[server_num++] = server_ctx;
+
+    return 0;
+}
+
+void
+free_udprelay()
+{
+    struct ev_loop *loop = EV_DEFAULT;
+    while (server_num-- > 0) {
+        server_ctx_t *server_ctx = server_ctx_list[server_num];
+
+#ifdef MODULE_LOCAL
+        //SSR beg
+        if (server_ctx->protocol_plugin) {
+            server_ctx->protocol_plugin->dispose(server_ctx->protocol);
+            server_ctx->protocol = NULL;
+            free_obfs_class(server_ctx->protocol_plugin);
+            server_ctx->protocol_plugin = NULL;
+        }
+        //SSR end
+#endif
+
+        ev_io_stop(loop, &server_ctx->io);
+        close(server_ctx->fd);
+        cache_delete(server_ctx->conn_cache, 0);
+        ss_free(server_ctx);
+        server_ctx_list[server_num] = NULL;
+    }
+}
diff --git a/server/udprelay.h b/server/udprelay.h
new file mode 100644
index 0000000..89876d4
--- /dev/null
+++ b/server/udprelay.h
@@ -0,0 +1,95 @@
+/*
+ * udprelay.h - Define UDP relay's buffers and callbacks
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef _UDPRELAY_H
+#define _UDPRELAY_H
+
+#include <ev.h>
+#include <time.h>
+
+#include "encrypt.h"
+#include "jconf.h"
+#include "obfs.h"
+
+#ifdef MODULE_REMOTE
+#include "resolv.h"
+#endif
+
+#include "cache.h"
+
+#include "common.h"
+
+#define MAX_UDP_PACKET_SIZE (65507)
+
+#define DEFAULT_PACKET_SIZE 1397 // 1492 - 1 - 28 - 2 - 64 = 1397, the default MTU for UDP relay
+
+typedef struct server_ctx {
+    ev_io io;
+    int fd;
+    int method;
+    int auth;
+    int timeout;
+    const char *iface;
+    struct cache *conn_cache;
+#ifdef MODULE_LOCAL
+    const struct sockaddr *remote_addr;
+    int remote_addr_len;
+#ifdef MODULE_TUNNEL
+    ss_addr_t tunnel_addr;
+#endif
+#endif
+#ifdef MODULE_REMOTE
+    struct ev_loop *loop;
+#endif
+    // SSR
+    obfs *protocol;
+    obfs_class *protocol_plugin;
+    void *protocol_global;
+} server_ctx_t;
+
+#ifdef MODULE_REMOTE
+typedef struct query_ctx {
+    struct ResolvQuery *query;
+    struct sockaddr_storage src_addr;
+    buffer_t *buf;
+    int addr_header_len;
+    char addr_header[384];
+    struct server_ctx *server_ctx;
+    struct remote_ctx *remote_ctx;
+} query_ctx_t;
+#endif
+
+typedef struct remote_ctx {
+    ev_io io;
+    ev_timer watcher;
+    int af;
+    int fd;
+    int addr_header_len;
+    char addr_header[384];
+    struct sockaddr_storage src_addr;
+#ifdef MODULE_REMOTE
+    struct sockaddr_storage dst_addr;
+#endif
+    struct server_ctx *server_ctx;
+} remote_ctx_t;
+
+#endif // _UDPRELAY_H
diff --git a/server/uthash.h b/server/uthash.h
new file mode 100644
index 0000000..45d1f9f
--- /dev/null
+++ b/server/uthash.h
@@ -0,0 +1,1074 @@
+/*
+Copyright (c) 2003-2016, Troy D. Hanson     http://troydhanson.github.com/uthash/
+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.
+
+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 UTHASH_H
+#define UTHASH_H
+
+#define UTHASH_VERSION 2.0.1
+
+#include <string.h>   /* memcmp,strlen */
+#include <stddef.h>   /* ptrdiff_t */
+#include <stdlib.h>   /* exit() */
+
+/* These macros use decltype or the earlier __typeof GNU extension.
+   As decltype is only available in newer compilers (VS2010 or gcc 4.3+
+   when compiling c++ source) this code uses whatever method is needed
+   or, for VS2008 where neither is available, uses casting workarounds. */
+#if defined(_MSC_VER)   /* MS compiler */
+#if _MSC_VER >= 1600 && defined(__cplusplus)  /* VS2010 or newer in C++ mode */
+#define DECLTYPE(x) (decltype(x))
+#else                   /* VS2008 or older (or VS2010 in C mode) */
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#endif
+#elif defined(__BORLANDC__) || defined(__LCC__) || defined(__WATCOMC__)
+#define NO_DECLTYPE
+#define DECLTYPE(x)
+#else                   /* GNU, Sun and other compilers */
+#define DECLTYPE(x) (__typeof(x))
+#endif
+
+#ifdef NO_DECLTYPE
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  char **_da_dst = (char**)(&(dst));                                             \
+  *_da_dst = (char*)(src);                                                       \
+} while (0)
+#else
+#define DECLTYPE_ASSIGN(dst,src)                                                 \
+do {                                                                             \
+  (dst) = DECLTYPE(dst)(src);                                                    \
+} while (0)
+#endif
+
+/* a number of the hash function use uint32_t which isn't defined on Pre VS2010 */
+#if defined(_WIN32)
+#if defined(_MSC_VER) && _MSC_VER >= 1600
+#include <stdint.h>
+#elif defined(__WATCOMC__) || defined(__MINGW32__) || defined(__CYGWIN__)
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#endif
+#elif defined(__GNUC__) && !defined(__VXWORKS__)
+#include <stdint.h>
+#else
+typedef unsigned int uint32_t;
+typedef unsigned char uint8_t;
+#endif
+
+#ifndef uthash_fatal
+#define uthash_fatal(msg) exit(-1)        /* fatal error (out of memory,etc) */
+#endif
+#ifndef uthash_malloc
+#define uthash_malloc(sz) malloc(sz)      /* malloc fcn                      */
+#endif
+#ifndef uthash_free
+#define uthash_free(ptr,sz) free(ptr)     /* free fcn                        */
+#endif
+#ifndef uthash_strlen
+#define uthash_strlen(s) strlen(s)
+#endif
+#ifndef uthash_memcmp
+#define uthash_memcmp(a,b,n) memcmp(a,b,n)
+#endif
+
+#ifndef uthash_noexpand_fyi
+#define uthash_noexpand_fyi(tbl)          /* can be defined to log noexpand  */
+#endif
+#ifndef uthash_expand_fyi
+#define uthash_expand_fyi(tbl)            /* can be defined to log expands   */
+#endif
+
+/* initial number of buckets */
+#define HASH_INITIAL_NUM_BUCKETS 32U     /* initial number of buckets        */
+#define HASH_INITIAL_NUM_BUCKETS_LOG2 5U /* lg2 of initial number of buckets */
+#define HASH_BKT_CAPACITY_THRESH 10U     /* expand when bucket count reaches */
+
+/* calculate the element whose hash handle address is hhp */
+#define ELMT_FROM_HH(tbl,hhp) ((void*)(((char*)(hhp)) - ((tbl)->hho)))
+/* calculate the hash handle from element address elp */
+#define HH_FROM_ELMT(tbl,elp) ((UT_hash_handle *)(((char*)(elp)) + ((tbl)->hho)))
+
+#define HASH_VALUE(keyptr,keylen,hashv)                                          \
+do {                                                                             \
+  HASH_FCN(keyptr, keylen, hashv);                                               \
+} while (0)
+
+#define HASH_FIND_BYHASHVALUE(hh,head,keyptr,keylen,hashval,out)                 \
+do {                                                                             \
+  (out) = NULL;                                                                  \
+  if (head) {                                                                    \
+    unsigned _hf_bkt;                                                            \
+    HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _hf_bkt);                  \
+    if (HASH_BLOOM_TEST((head)->hh.tbl, hashval) != 0) {                         \
+      HASH_FIND_IN_BKT((head)->hh.tbl, hh, (head)->hh.tbl->buckets[ _hf_bkt ], keyptr, keylen, hashval, out); \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+
+#define HASH_FIND(hh,head,keyptr,keylen,out)                                     \
+do {                                                                             \
+  unsigned _hf_hashv;                                                            \
+  HASH_VALUE(keyptr, keylen, _hf_hashv);                                         \
+  HASH_FIND_BYHASHVALUE(hh, head, keyptr, keylen, _hf_hashv, out);               \
+} while (0)
+
+#ifdef HASH_BLOOM
+#define HASH_BLOOM_BITLEN (1UL << HASH_BLOOM)
+#define HASH_BLOOM_BYTELEN (HASH_BLOOM_BITLEN/8UL) + (((HASH_BLOOM_BITLEN%8UL)!=0UL) ? 1UL : 0UL)
+#define HASH_BLOOM_MAKE(tbl)                                                     \
+do {                                                                             \
+  (tbl)->bloom_nbits = HASH_BLOOM;                                               \
+  (tbl)->bloom_bv = (uint8_t*)uthash_malloc(HASH_BLOOM_BYTELEN);                 \
+  if (!((tbl)->bloom_bv))  { uthash_fatal( "out of memory"); }                   \
+  memset((tbl)->bloom_bv, 0, HASH_BLOOM_BYTELEN);                                \
+  (tbl)->bloom_sig = HASH_BLOOM_SIGNATURE;                                       \
+} while (0)
+
+#define HASH_BLOOM_FREE(tbl)                                                     \
+do {                                                                             \
+  uthash_free((tbl)->bloom_bv, HASH_BLOOM_BYTELEN);                              \
+} while (0)
+
+#define HASH_BLOOM_BITSET(bv,idx) (bv[(idx)/8U] |= (1U << ((idx)%8U)))
+#define HASH_BLOOM_BITTEST(bv,idx) (bv[(idx)/8U] & (1U << ((idx)%8U)))
+
+#define HASH_BLOOM_ADD(tbl,hashv)                                                \
+  HASH_BLOOM_BITSET((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U)))
+
+#define HASH_BLOOM_TEST(tbl,hashv)                                               \
+  HASH_BLOOM_BITTEST((tbl)->bloom_bv, (hashv & (uint32_t)((1ULL << (tbl)->bloom_nbits) - 1U)))
+
+#else
+#define HASH_BLOOM_MAKE(tbl)
+#define HASH_BLOOM_FREE(tbl)
+#define HASH_BLOOM_ADD(tbl,hashv)
+#define HASH_BLOOM_TEST(tbl,hashv) (1)
+#define HASH_BLOOM_BYTELEN 0U
+#endif
+
+#define HASH_MAKE_TABLE(hh,head)                                                 \
+do {                                                                             \
+  (head)->hh.tbl = (UT_hash_table*)uthash_malloc(                                \
+                  sizeof(UT_hash_table));                                        \
+  if (!((head)->hh.tbl))  { uthash_fatal( "out of memory"); }                    \
+  memset((head)->hh.tbl, 0, sizeof(UT_hash_table));                              \
+  (head)->hh.tbl->tail = &((head)->hh);                                          \
+  (head)->hh.tbl->num_buckets = HASH_INITIAL_NUM_BUCKETS;                        \
+  (head)->hh.tbl->log2_num_buckets = HASH_INITIAL_NUM_BUCKETS_LOG2;              \
+  (head)->hh.tbl->hho = (char*)(&(head)->hh) - (char*)(head);                    \
+  (head)->hh.tbl->buckets = (UT_hash_bucket*)uthash_malloc(                      \
+          HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  if (! (head)->hh.tbl->buckets) { uthash_fatal( "out of memory"); }             \
+  memset((head)->hh.tbl->buckets, 0,                                             \
+          HASH_INITIAL_NUM_BUCKETS*sizeof(struct UT_hash_bucket));               \
+  HASH_BLOOM_MAKE((head)->hh.tbl);                                               \
+  (head)->hh.tbl->signature = HASH_SIGNATURE;                                    \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,replaced,cmpfcn) \
+do {                                                                             \
+  (replaced) = NULL;                                                             \
+  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+  if (replaced) {                                                                \
+     HASH_DELETE(hh, head, replaced);                                            \
+  }                                                                              \
+  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn); \
+} while (0)
+
+#define HASH_REPLACE_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add,replaced) \
+do {                                                                             \
+  (replaced) = NULL;                                                             \
+  HASH_FIND_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, replaced); \
+  if (replaced) {                                                                \
+     HASH_DELETE(hh, head, replaced);                                            \
+  }                                                                              \
+  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add); \
+} while (0)
+
+#define HASH_REPLACE(hh,head,fieldname,keylen_in,add,replaced)                   \
+do {                                                                             \
+  unsigned _hr_hashv;                                                            \
+  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \
+  HASH_REPLACE_BYHASHVALUE(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced); \
+} while (0)
+
+#define HASH_REPLACE_INORDER(hh,head,fieldname,keylen_in,add,replaced,cmpfcn)    \
+do {                                                                             \
+  unsigned _hr_hashv;                                                            \
+  HASH_VALUE(&((add)->fieldname), keylen_in, _hr_hashv);                         \
+  HASH_REPLACE_BYHASHVALUE_INORDER(hh, head, fieldname, keylen_in, _hr_hashv, add, replaced, cmpfcn); \
+} while (0)
+
+#define HASH_APPEND_LIST(hh, head, add)                                          \
+do {                                                                             \
+  (add)->hh.next = NULL;                                                         \
+  (add)->hh.prev = ELMT_FROM_HH((head)->hh.tbl, (head)->hh.tbl->tail);           \
+  (head)->hh.tbl->tail->next = (add);                                            \
+  (head)->hh.tbl->tail = &((add)->hh);                                           \
+} while (0)
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh,head,keyptr,keylen_in,hashval,add,cmpfcn) \
+do {                                                                             \
+  unsigned _ha_bkt;                                                              \
+  (add)->hh.hashv = (hashval);                                                   \
+  (add)->hh.key = (char*) (keyptr);                                              \
+  (add)->hh.keylen = (unsigned) (keylen_in);                                     \
+  if (!(head)) {                                                                 \
+    (add)->hh.next = NULL;                                                       \
+    (add)->hh.prev = NULL;                                                       \
+    (head) = (add);                                                              \
+    HASH_MAKE_TABLE(hh, head);                                                   \
+  } else {                                                                       \
+    struct UT_hash_handle *_hs_iter = &(head)->hh;                               \
+    (add)->hh.tbl = (head)->hh.tbl;                                              \
+    do {                                                                         \
+      if (cmpfcn(DECLTYPE(head) ELMT_FROM_HH((head)->hh.tbl, _hs_iter), add) > 0) \
+        break;                                                                   \
+    } while ((_hs_iter = _hs_iter->next));                                       \
+    if (_hs_iter) {                                                              \
+      (add)->hh.next = _hs_iter;                                                 \
+      if (((add)->hh.prev = _hs_iter->prev)) {                                   \
+        HH_FROM_ELMT((head)->hh.tbl, _hs_iter->prev)->next = (add);              \
+      } else {                                                                   \
+        (head) = (add);                                                          \
+      }                                                                          \
+      _hs_iter->prev = (add);                                                    \
+    } else {                                                                     \
+      HASH_APPEND_LIST(hh, head, add);                                           \
+    }                                                                            \
+  }                                                                              \
+  (head)->hh.tbl->num_items++;                                                   \
+  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \
+  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh);                 \
+  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \
+  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \
+  HASH_FSCK(hh, head);                                                           \
+} while (0)
+
+#define HASH_ADD_KEYPTR_INORDER(hh,head,keyptr,keylen_in,add,cmpfcn)             \
+do {                                                                             \
+  unsigned _hs_hashv;                                                            \
+  HASH_VALUE(keyptr, keylen_in, _hs_hashv);                                      \
+  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, keyptr, keylen_in, _hs_hashv, add, cmpfcn); \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE_INORDER(hh,head,fieldname,keylen_in,hashval,add,cmpfcn) \
+  HASH_ADD_KEYPTR_BYHASHVALUE_INORDER(hh, head, &((add)->fieldname), keylen_in, hashval, add, cmpfcn)
+
+#define HASH_ADD_INORDER(hh,head,fieldname,keylen_in,add,cmpfcn)                 \
+  HASH_ADD_KEYPTR_INORDER(hh, head, &((add)->fieldname), keylen_in, add, cmpfcn)
+
+#define HASH_ADD_KEYPTR_BYHASHVALUE(hh,head,keyptr,keylen_in,hashval,add)        \
+do {                                                                             \
+  unsigned _ha_bkt;                                                              \
+  (add)->hh.hashv = (hashval);                                                   \
+  (add)->hh.key = (char*) (keyptr);                                              \
+  (add)->hh.keylen = (unsigned) (keylen_in);                                     \
+  if (!(head)) {                                                                 \
+    (add)->hh.next = NULL;                                                       \
+    (add)->hh.prev = NULL;                                                       \
+    (head) = (add);                                                              \
+    HASH_MAKE_TABLE(hh, head);                                                   \
+  } else {                                                                       \
+    (add)->hh.tbl = (head)->hh.tbl;                                              \
+    HASH_APPEND_LIST(hh, head, add);                                             \
+  }                                                                              \
+  (head)->hh.tbl->num_items++;                                                   \
+  HASH_TO_BKT(hashval, (head)->hh.tbl->num_buckets, _ha_bkt);                    \
+  HASH_ADD_TO_BKT((head)->hh.tbl->buckets[_ha_bkt], &(add)->hh);                 \
+  HASH_BLOOM_ADD((head)->hh.tbl, hashval);                                       \
+  HASH_EMIT_KEY(hh, head, keyptr, keylen_in);                                    \
+  HASH_FSCK(hh, head);                                                           \
+} while (0)
+
+#define HASH_ADD_KEYPTR(hh,head,keyptr,keylen_in,add)                            \
+do {                                                                             \
+  unsigned _ha_hashv;                                                            \
+  HASH_VALUE(keyptr, keylen_in, _ha_hashv);                                      \
+  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, keyptr, keylen_in, _ha_hashv, add);      \
+} while (0)
+
+#define HASH_ADD_BYHASHVALUE(hh,head,fieldname,keylen_in,hashval,add)            \
+  HASH_ADD_KEYPTR_BYHASHVALUE(hh, head, &((add)->fieldname), keylen_in, hashval, add)
+
+#define HASH_ADD(hh,head,fieldname,keylen_in,add)                                \
+  HASH_ADD_KEYPTR(hh, head, &((add)->fieldname), keylen_in, add)
+
+#define HASH_TO_BKT(hashv,num_bkts,bkt)                                          \
+do {                                                                             \
+  bkt = ((hashv) & ((num_bkts) - 1U));                                           \
+} while (0)
+
+/* delete "delptr" from the hash table.
+ * "the usual" patch-up process for the app-order doubly-linked-list.
+ * The use of _hd_hh_del below deserves special explanation.
+ * These used to be expressed using (delptr) but that led to a bug
+ * if someone used the same symbol for the head and deletee, like
+ *  HASH_DELETE(hh,users,users);
+ * We want that to work, but by changing the head (users) below
+ * we were forfeiting our ability to further refer to the deletee (users)
+ * in the patch-up process. Solution: use scratch space to
+ * copy the deletee pointer, then the latter references are via that
+ * scratch pointer rather than through the repointed (users) symbol.
+ */
+#define HASH_DELETE(hh,head,delptr)                                              \
+do {                                                                             \
+    struct UT_hash_handle *_hd_hh_del;                                           \
+    if ( ((delptr)->hh.prev == NULL) && ((delptr)->hh.next == NULL) )  {         \
+        uthash_free((head)->hh.tbl->buckets,                                     \
+                    (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
+        HASH_BLOOM_FREE((head)->hh.tbl);                                         \
+        uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                      \
+        head = NULL;                                                             \
+    } else {                                                                     \
+        unsigned _hd_bkt;                                                        \
+        _hd_hh_del = &((delptr)->hh);                                            \
+        if ((delptr) == ELMT_FROM_HH((head)->hh.tbl,(head)->hh.tbl->tail)) {     \
+            (head)->hh.tbl->tail =                                               \
+                (UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) +               \
+                (head)->hh.tbl->hho);                                            \
+        }                                                                        \
+        if ((delptr)->hh.prev != NULL) {                                         \
+            ((UT_hash_handle*)((ptrdiff_t)((delptr)->hh.prev) +                  \
+                    (head)->hh.tbl->hho))->next = (delptr)->hh.next;             \
+        } else {                                                                 \
+            DECLTYPE_ASSIGN(head,(delptr)->hh.next);                             \
+        }                                                                        \
+        if (_hd_hh_del->next != NULL) {                                          \
+            ((UT_hash_handle*)((ptrdiff_t)_hd_hh_del->next +                     \
+                    (head)->hh.tbl->hho))->prev =                                \
+                    _hd_hh_del->prev;                                            \
+        }                                                                        \
+        HASH_TO_BKT( _hd_hh_del->hashv, (head)->hh.tbl->num_buckets, _hd_bkt);   \
+        HASH_DEL_IN_BKT(hh,(head)->hh.tbl->buckets[_hd_bkt], _hd_hh_del);        \
+        (head)->hh.tbl->num_items--;                                             \
+    }                                                                            \
+    HASH_FSCK(hh,head);                                                          \
+} while (0)
+
+
+/* convenience forms of HASH_FIND/HASH_ADD/HASH_DEL */
+#define HASH_FIND_STR(head,findstr,out)                                          \
+    HASH_FIND(hh,head,findstr,(unsigned)uthash_strlen(findstr),out)
+#define HASH_ADD_STR(head,strfield,add)                                          \
+    HASH_ADD(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add)
+#define HASH_REPLACE_STR(head,strfield,add,replaced)                             \
+    HASH_REPLACE(hh,head,strfield[0],(unsigned)uthash_strlen(add->strfield),add,replaced)
+#define HASH_FIND_INT(head,findint,out)                                          \
+    HASH_FIND(hh,head,findint,sizeof(int),out)
+#define HASH_ADD_INT(head,intfield,add)                                          \
+    HASH_ADD(hh,head,intfield,sizeof(int),add)
+#define HASH_REPLACE_INT(head,intfield,add,replaced)                             \
+    HASH_REPLACE(hh,head,intfield,sizeof(int),add,replaced)
+#define HASH_FIND_PTR(head,findptr,out)                                          \
+    HASH_FIND(hh,head,findptr,sizeof(void *),out)
+#define HASH_ADD_PTR(head,ptrfield,add)                                          \
+    HASH_ADD(hh,head,ptrfield,sizeof(void *),add)
+#define HASH_REPLACE_PTR(head,ptrfield,add,replaced)                             \
+    HASH_REPLACE(hh,head,ptrfield,sizeof(void *),add,replaced)
+#define HASH_DEL(head,delptr)                                                    \
+    HASH_DELETE(hh,head,delptr)
+
+/* HASH_FSCK checks hash integrity on every add/delete when HASH_DEBUG is defined.
+ * This is for uthash developer only; it compiles away if HASH_DEBUG isn't defined.
+ */
+#ifdef HASH_DEBUG
+#define HASH_OOPS(...) do { fprintf(stderr,__VA_ARGS__); exit(-1); } while (0)
+#define HASH_FSCK(hh,head)                                                       \
+do {                                                                             \
+    struct UT_hash_handle *_thh;                                                 \
+    if (head) {                                                                  \
+        unsigned _bkt_i;                                                         \
+        unsigned _count;                                                         \
+        char *_prev;                                                             \
+        _count = 0;                                                              \
+        for( _bkt_i = 0; _bkt_i < (head)->hh.tbl->num_buckets; _bkt_i++) {       \
+            unsigned _bkt_count = 0;                                             \
+            _thh = (head)->hh.tbl->buckets[_bkt_i].hh_head;                      \
+            _prev = NULL;                                                        \
+            while (_thh) {                                                       \
+               if (_prev != (char*)(_thh->hh_prev)) {                            \
+                   HASH_OOPS("invalid hh_prev %p, actual %p\n",                  \
+                    _thh->hh_prev, _prev );                                      \
+               }                                                                 \
+               _bkt_count++;                                                     \
+               _prev = (char*)(_thh);                                            \
+               _thh = _thh->hh_next;                                             \
+            }                                                                    \
+            _count += _bkt_count;                                                \
+            if ((head)->hh.tbl->buckets[_bkt_i].count !=  _bkt_count) {          \
+               HASH_OOPS("invalid bucket count %u, actual %u\n",                 \
+                (head)->hh.tbl->buckets[_bkt_i].count, _bkt_count);              \
+            }                                                                    \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            HASH_OOPS("invalid hh item count %u, actual %u\n",                   \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+        /* traverse hh in app order; check next/prev integrity, count */         \
+        _count = 0;                                                              \
+        _prev = NULL;                                                            \
+        _thh =  &(head)->hh;                                                     \
+        while (_thh) {                                                           \
+           _count++;                                                             \
+           if (_prev !=(char*)(_thh->prev)) {                                    \
+              HASH_OOPS("invalid prev %p, actual %p\n",                          \
+                    _thh->prev, _prev );                                         \
+           }                                                                     \
+           _prev = (char*)ELMT_FROM_HH((head)->hh.tbl, _thh);                    \
+           _thh = ( _thh->next ?  (UT_hash_handle*)((char*)(_thh->next) +        \
+                                  (head)->hh.tbl->hho) : NULL );                 \
+        }                                                                        \
+        if (_count != (head)->hh.tbl->num_items) {                               \
+            HASH_OOPS("invalid app item count %u, actual %u\n",                  \
+                (head)->hh.tbl->num_items, _count );                             \
+        }                                                                        \
+    }                                                                            \
+} while (0)
+#else
+#define HASH_FSCK(hh,head)
+#endif
+
+/* When compiled with -DHASH_EMIT_KEYS, length-prefixed keys are emitted to
+ * the descriptor to which this macro is defined for tuning the hash function.
+ * The app can #include <unistd.h> to get the prototype for write(2). */
+#ifdef HASH_EMIT_KEYS
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)                                   \
+do {                                                                             \
+    unsigned _klen = fieldlen;                                                   \
+    write(HASH_EMIT_KEYS, &_klen, sizeof(_klen));                                \
+    write(HASH_EMIT_KEYS, keyptr, (unsigned long)fieldlen);                      \
+} while (0)
+#else
+#define HASH_EMIT_KEY(hh,head,keyptr,fieldlen)
+#endif
+
+/* default to Jenkin's hash unless overridden e.g. DHASH_FUNCTION=HASH_SAX */
+#ifdef HASH_FUNCTION
+#define HASH_FCN HASH_FUNCTION
+#else
+#define HASH_FCN HASH_JEN
+#endif
+
+/* The Bernstein hash function, used in Perl prior to v5.6. Note (x<<5+x)=x*33. */
+#define HASH_BER(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _hb_keylen=(unsigned)keylen;                                          \
+  const unsigned char *_hb_key=(const unsigned char*)(key);                      \
+  (hashv) = 0;                                                                   \
+  while (_hb_keylen-- != 0U) {                                                   \
+      (hashv) = (((hashv) << 5) + (hashv)) + *_hb_key++;                         \
+  }                                                                              \
+} while (0)
+
+
+/* SAX/FNV/OAT/JEN hash functions are macro variants of those listed at
+ * http://eternallyconfuzzled.com/tuts/algorithms/jsw_tut_hashing.aspx */
+#define HASH_SAX(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _sx_i;                                                                \
+  const unsigned char *_hs_key=(const unsigned char*)(key);                      \
+  hashv = 0;                                                                     \
+  for(_sx_i=0; _sx_i < keylen; _sx_i++) {                                        \
+      hashv ^= (hashv << 5) + (hashv >> 2) + _hs_key[_sx_i];                     \
+  }                                                                              \
+} while (0)
+/* FNV-1a variation */
+#define HASH_FNV(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _fn_i;                                                                \
+  const unsigned char *_hf_key=(const unsigned char*)(key);                      \
+  hashv = 2166136261U;                                                           \
+  for(_fn_i=0; _fn_i < keylen; _fn_i++) {                                        \
+      hashv = hashv ^ _hf_key[_fn_i];                                            \
+      hashv = hashv * 16777619U;                                                 \
+  }                                                                              \
+} while (0)
+
+#define HASH_OAT(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _ho_i;                                                                \
+  const unsigned char *_ho_key=(const unsigned char*)(key);                      \
+  hashv = 0;                                                                     \
+  for(_ho_i=0; _ho_i < keylen; _ho_i++) {                                        \
+      hashv += _ho_key[_ho_i];                                                   \
+      hashv += (hashv << 10);                                                    \
+      hashv ^= (hashv >> 6);                                                     \
+  }                                                                              \
+  hashv += (hashv << 3);                                                         \
+  hashv ^= (hashv >> 11);                                                        \
+  hashv += (hashv << 15);                                                        \
+} while (0)
+
+#define HASH_JEN_MIX(a,b,c)                                                      \
+do {                                                                             \
+  a -= b; a -= c; a ^= ( c >> 13 );                                              \
+  b -= c; b -= a; b ^= ( a << 8 );                                               \
+  c -= a; c -= b; c ^= ( b >> 13 );                                              \
+  a -= b; a -= c; a ^= ( c >> 12 );                                              \
+  b -= c; b -= a; b ^= ( a << 16 );                                              \
+  c -= a; c -= b; c ^= ( b >> 5 );                                               \
+  a -= b; a -= c; a ^= ( c >> 3 );                                               \
+  b -= c; b -= a; b ^= ( a << 10 );                                              \
+  c -= a; c -= b; c ^= ( b >> 15 );                                              \
+} while (0)
+
+#define HASH_JEN(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned _hj_i,_hj_j,_hj_k;                                                    \
+  unsigned const char *_hj_key=(unsigned const char*)(key);                      \
+  hashv = 0xfeedbeefu;                                                           \
+  _hj_i = _hj_j = 0x9e3779b9u;                                                   \
+  _hj_k = (unsigned)(keylen);                                                    \
+  while (_hj_k >= 12U) {                                                         \
+    _hj_i +=    (_hj_key[0] + ( (unsigned)_hj_key[1] << 8 )                      \
+        + ( (unsigned)_hj_key[2] << 16 )                                         \
+        + ( (unsigned)_hj_key[3] << 24 ) );                                      \
+    _hj_j +=    (_hj_key[4] + ( (unsigned)_hj_key[5] << 8 )                      \
+        + ( (unsigned)_hj_key[6] << 16 )                                         \
+        + ( (unsigned)_hj_key[7] << 24 ) );                                      \
+    hashv += (_hj_key[8] + ( (unsigned)_hj_key[9] << 8 )                         \
+        + ( (unsigned)_hj_key[10] << 16 )                                        \
+        + ( (unsigned)_hj_key[11] << 24 ) );                                     \
+                                                                                 \
+     HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                          \
+                                                                                 \
+     _hj_key += 12;                                                              \
+     _hj_k -= 12U;                                                               \
+  }                                                                              \
+  hashv += (unsigned)(keylen);                                                   \
+  switch ( _hj_k ) {                                                             \
+     case 11: hashv += ( (unsigned)_hj_key[10] << 24 ); /* FALLTHROUGH */        \
+     case 10: hashv += ( (unsigned)_hj_key[9] << 16 );  /* FALLTHROUGH */        \
+     case 9:  hashv += ( (unsigned)_hj_key[8] << 8 );   /* FALLTHROUGH */        \
+     case 8:  _hj_j += ( (unsigned)_hj_key[7] << 24 );  /* FALLTHROUGH */        \
+     case 7:  _hj_j += ( (unsigned)_hj_key[6] << 16 );  /* FALLTHROUGH */        \
+     case 6:  _hj_j += ( (unsigned)_hj_key[5] << 8 );   /* FALLTHROUGH */        \
+     case 5:  _hj_j += _hj_key[4];                      /* FALLTHROUGH */        \
+     case 4:  _hj_i += ( (unsigned)_hj_key[3] << 24 );  /* FALLTHROUGH */        \
+     case 3:  _hj_i += ( (unsigned)_hj_key[2] << 16 );  /* FALLTHROUGH */        \
+     case 2:  _hj_i += ( (unsigned)_hj_key[1] << 8 );   /* FALLTHROUGH */        \
+     case 1:  _hj_i += _hj_key[0];                                               \
+  }                                                                              \
+  HASH_JEN_MIX(_hj_i, _hj_j, hashv);                                             \
+} while (0)
+
+/* The Paul Hsieh hash function */
+#undef get16bits
+#if (defined(__GNUC__) && defined(__i386__)) || defined(__WATCOMC__)             \
+  || defined(_MSC_VER) || defined (__BORLANDC__) || defined (__TURBOC__)
+#define get16bits(d) (*((const uint16_t *) (d)))
+#endif
+
+#if !defined (get16bits)
+#define get16bits(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)             \
+                       +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+#define HASH_SFH(key,keylen,hashv)                                               \
+do {                                                                             \
+  unsigned const char *_sfh_key=(unsigned const char*)(key);                     \
+  uint32_t _sfh_tmp, _sfh_len = (uint32_t)keylen;                                \
+                                                                                 \
+  unsigned _sfh_rem = _sfh_len & 3U;                                             \
+  _sfh_len >>= 2;                                                                \
+  hashv = 0xcafebabeu;                                                           \
+                                                                                 \
+  /* Main loop */                                                                \
+  for (;_sfh_len > 0U; _sfh_len--) {                                             \
+    hashv    += get16bits (_sfh_key);                                            \
+    _sfh_tmp  = ((uint32_t)(get16bits (_sfh_key+2)) << 11) ^ hashv;              \
+    hashv     = (hashv << 16) ^ _sfh_tmp;                                        \
+    _sfh_key += 2U*sizeof (uint16_t);                                            \
+    hashv    += hashv >> 11;                                                     \
+  }                                                                              \
+                                                                                 \
+  /* Handle end cases */                                                         \
+  switch (_sfh_rem) {                                                            \
+    case 3: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 16;                                                \
+            hashv ^= (uint32_t)(_sfh_key[sizeof (uint16_t)]) << 18;              \
+            hashv += hashv >> 11;                                                \
+            break;                                                               \
+    case 2: hashv += get16bits (_sfh_key);                                       \
+            hashv ^= hashv << 11;                                                \
+            hashv += hashv >> 17;                                                \
+            break;                                                               \
+    case 1: hashv += *_sfh_key;                                                  \
+            hashv ^= hashv << 10;                                                \
+            hashv += hashv >> 1;                                                 \
+  }                                                                              \
+                                                                                 \
+    /* Force "avalanching" of final 127 bits */                                  \
+    hashv ^= hashv << 3;                                                         \
+    hashv += hashv >> 5;                                                         \
+    hashv ^= hashv << 4;                                                         \
+    hashv += hashv >> 17;                                                        \
+    hashv ^= hashv << 25;                                                        \
+    hashv += hashv >> 6;                                                         \
+} while (0)
+
+#ifdef HASH_USING_NO_STRICT_ALIASING
+/* The MurmurHash exploits some CPU's (x86,x86_64) tolerance for unaligned reads.
+ * For other types of CPU's (e.g. Sparc) an unaligned read causes a bus error.
+ * MurmurHash uses the faster approach only on CPU's where we know it's safe.
+ *
+ * Note the preprocessor built-in defines can be emitted using:
+ *
+ *   gcc -m64 -dM -E - < /dev/null                  (on gcc)
+ *   cc -## a.c (where a.c is a simple test file)   (Sun Studio)
+ */
+#if (defined(__i386__) || defined(__x86_64__)  || defined(_M_IX86))
+#define MUR_GETBLOCK(p,i) p[i]
+#else /* non intel */
+#define MUR_PLUS0_ALIGNED(p) (((unsigned long)p & 3UL) == 0UL)
+#define MUR_PLUS1_ALIGNED(p) (((unsigned long)p & 3UL) == 1UL)
+#define MUR_PLUS2_ALIGNED(p) (((unsigned long)p & 3UL) == 2UL)
+#define MUR_PLUS3_ALIGNED(p) (((unsigned long)p & 3UL) == 3UL)
+#define WP(p) ((uint32_t*)((unsigned long)(p) & ~3UL))
+#if (defined(__BIG_ENDIAN__) || defined(SPARC) || defined(__ppc__) || defined(__ppc64__))
+#define MUR_THREE_ONE(p) ((((*WP(p))&0x00ffffff) << 8) | (((*(WP(p)+1))&0xff000000) >> 24))
+#define MUR_TWO_TWO(p)   ((((*WP(p))&0x0000ffff) <<16) | (((*(WP(p)+1))&0xffff0000) >> 16))
+#define MUR_ONE_THREE(p) ((((*WP(p))&0x000000ff) <<24) | (((*(WP(p)+1))&0xffffff00) >>  8))
+#else /* assume little endian non-intel */
+#define MUR_THREE_ONE(p) ((((*WP(p))&0xffffff00) >> 8) | (((*(WP(p)+1))&0x000000ff) << 24))
+#define MUR_TWO_TWO(p)   ((((*WP(p))&0xffff0000) >>16) | (((*(WP(p)+1))&0x0000ffff) << 16))
+#define MUR_ONE_THREE(p) ((((*WP(p))&0xff000000) >>24) | (((*(WP(p)+1))&0x00ffffff) <<  8))
+#endif
+#define MUR_GETBLOCK(p,i) (MUR_PLUS0_ALIGNED(p) ? ((p)[i]) :           \
+                            (MUR_PLUS1_ALIGNED(p) ? MUR_THREE_ONE(p) : \
+                             (MUR_PLUS2_ALIGNED(p) ? MUR_TWO_TWO(p) :  \
+                                                      MUR_ONE_THREE(p))))
+#endif
+#define MUR_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r))))
+#define MUR_FMIX(_h) \
+do {                 \
+  _h ^= _h >> 16;    \
+  _h *= 0x85ebca6bu; \
+  _h ^= _h >> 13;    \
+  _h *= 0xc2b2ae35u; \
+  _h ^= _h >> 16;    \
+} while (0)
+
+#define HASH_MUR(key,keylen,hashv)                                     \
+do {                                                                   \
+  const uint8_t *_mur_data = (const uint8_t*)(key);                    \
+  const int _mur_nblocks = (int)(keylen) / 4;                          \
+  uint32_t _mur_h1 = 0xf88D5353u;                                      \
+  uint32_t _mur_c1 = 0xcc9e2d51u;                                      \
+  uint32_t _mur_c2 = 0x1b873593u;                                      \
+  uint32_t _mur_k1 = 0;                                                \
+  const uint8_t *_mur_tail;                                            \
+  const uint32_t *_mur_blocks = (const uint32_t*)(_mur_data+(_mur_nblocks*4)); \
+  int _mur_i;                                                          \
+  for(_mur_i = -_mur_nblocks; _mur_i!=0; _mur_i++) {                   \
+    _mur_k1 = MUR_GETBLOCK(_mur_blocks,_mur_i);                        \
+    _mur_k1 *= _mur_c1;                                                \
+    _mur_k1 = MUR_ROTL32(_mur_k1,15);                                  \
+    _mur_k1 *= _mur_c2;                                                \
+                                                                       \
+    _mur_h1 ^= _mur_k1;                                                \
+    _mur_h1 = MUR_ROTL32(_mur_h1,13);                                  \
+    _mur_h1 = (_mur_h1*5U) + 0xe6546b64u;                              \
+  }                                                                    \
+  _mur_tail = (const uint8_t*)(_mur_data + (_mur_nblocks*4));          \
+  _mur_k1=0;                                                           \
+  switch((keylen) & 3U) {                                              \
+    case 3: _mur_k1 ^= (uint32_t)_mur_tail[2] << 16; /* FALLTHROUGH */ \
+    case 2: _mur_k1 ^= (uint32_t)_mur_tail[1] << 8;  /* FALLTHROUGH */ \
+    case 1: _mur_k1 ^= (uint32_t)_mur_tail[0];                         \
+    _mur_k1 *= _mur_c1;                                                \
+    _mur_k1 = MUR_ROTL32(_mur_k1,15);                                  \
+    _mur_k1 *= _mur_c2;                                                \
+    _mur_h1 ^= _mur_k1;                                                \
+  }                                                                    \
+  _mur_h1 ^= (uint32_t)(keylen);                                       \
+  MUR_FMIX(_mur_h1);                                                   \
+  hashv = _mur_h1;                                                     \
+} while (0)
+#endif  /* HASH_USING_NO_STRICT_ALIASING */
+
+/* iterate over items in a known bucket to find desired item */
+#define HASH_FIND_IN_BKT(tbl,hh,head,keyptr,keylen_in,hashval,out)               \
+do {                                                                             \
+  if ((head).hh_head != NULL) {                                                  \
+    DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (head).hh_head));                     \
+  } else {                                                                       \
+    (out) = NULL;                                                                \
+  }                                                                              \
+  while ((out) != NULL) {                                                        \
+    if ((out)->hh.hashv == (hashval) && (out)->hh.keylen == (keylen_in)) {       \
+      if (uthash_memcmp((out)->hh.key, keyptr, keylen_in) == 0) {                \
+        break;                                                                   \
+      }                                                                          \
+    }                                                                            \
+    if ((out)->hh.hh_next != NULL) {                                             \
+      DECLTYPE_ASSIGN(out, ELMT_FROM_HH(tbl, (out)->hh.hh_next));                \
+    } else {                                                                     \
+      (out) = NULL;                                                              \
+    }                                                                            \
+  }                                                                              \
+} while (0)
+
+/* add an item to a bucket  */
+#define HASH_ADD_TO_BKT(head,addhh)                                              \
+do {                                                                             \
+ head.count++;                                                                   \
+ (addhh)->hh_next = head.hh_head;                                                \
+ (addhh)->hh_prev = NULL;                                                        \
+ if (head.hh_head != NULL) { (head).hh_head->hh_prev = (addhh); }                \
+ (head).hh_head=addhh;                                                           \
+ if ((head.count >= ((head.expand_mult+1U) * HASH_BKT_CAPACITY_THRESH))          \
+     && ((addhh)->tbl->noexpand != 1U)) {                                        \
+       HASH_EXPAND_BUCKETS((addhh)->tbl);                                        \
+ }                                                                               \
+} while (0)
+
+/* remove an item from a given bucket */
+#define HASH_DEL_IN_BKT(hh,head,hh_del)                                          \
+    (head).count--;                                                              \
+    if ((head).hh_head == hh_del) {                                              \
+      (head).hh_head = hh_del->hh_next;                                          \
+    }                                                                            \
+    if (hh_del->hh_prev) {                                                       \
+        hh_del->hh_prev->hh_next = hh_del->hh_next;                              \
+    }                                                                            \
+    if (hh_del->hh_next) {                                                       \
+        hh_del->hh_next->hh_prev = hh_del->hh_prev;                              \
+    }
+
+/* Bucket expansion has the effect of doubling the number of buckets
+ * and redistributing the items into the new buckets. Ideally the
+ * items will distribute more or less evenly into the new buckets
+ * (the extent to which this is true is a measure of the quality of
+ * the hash function as it applies to the key domain).
+ *
+ * With the items distributed into more buckets, the chain length
+ * (item count) in each bucket is reduced. Thus by expanding buckets
+ * the hash keeps a bound on the chain length. This bounded chain
+ * length is the essence of how a hash provides constant time lookup.
+ *
+ * The calculation of tbl->ideal_chain_maxlen below deserves some
+ * explanation. First, keep in mind that we're calculating the ideal
+ * maximum chain length based on the *new* (doubled) bucket count.
+ * In fractions this is just n/b (n=number of items,b=new num buckets).
+ * Since the ideal chain length is an integer, we want to calculate
+ * ceil(n/b). We don't depend on floating point arithmetic in this
+ * hash, so to calculate ceil(n/b) with integers we could write
+ *
+ *      ceil(n/b) = (n/b) + ((n%b)?1:0)
+ *
+ * and in fact a previous version of this hash did just that.
+ * But now we have improved things a bit by recognizing that b is
+ * always a power of two. We keep its base 2 log handy (call it lb),
+ * so now we can write this with a bit shift and logical AND:
+ *
+ *      ceil(n/b) = (n>>lb) + ( (n & (b-1)) ? 1:0)
+ *
+ */
+#define HASH_EXPAND_BUCKETS(tbl)                                                 \
+do {                                                                             \
+    unsigned _he_bkt;                                                            \
+    unsigned _he_bkt_i;                                                          \
+    struct UT_hash_handle *_he_thh, *_he_hh_nxt;                                 \
+    UT_hash_bucket *_he_new_buckets, *_he_newbkt;                                \
+    _he_new_buckets = (UT_hash_bucket*)uthash_malloc(                            \
+             2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket));            \
+    if (!_he_new_buckets) { uthash_fatal( "out of memory"); }                    \
+    memset(_he_new_buckets, 0,                                                   \
+            2UL * tbl->num_buckets * sizeof(struct UT_hash_bucket));             \
+    tbl->ideal_chain_maxlen =                                                    \
+       (tbl->num_items >> (tbl->log2_num_buckets+1U)) +                          \
+       (((tbl->num_items & ((tbl->num_buckets*2U)-1U)) != 0U) ? 1U : 0U);        \
+    tbl->nonideal_items = 0;                                                     \
+    for(_he_bkt_i = 0; _he_bkt_i < tbl->num_buckets; _he_bkt_i++)                \
+    {                                                                            \
+        _he_thh = tbl->buckets[ _he_bkt_i ].hh_head;                             \
+        while (_he_thh != NULL) {                                                \
+           _he_hh_nxt = _he_thh->hh_next;                                        \
+           HASH_TO_BKT( _he_thh->hashv, tbl->num_buckets*2U, _he_bkt);           \
+           _he_newbkt = &(_he_new_buckets[ _he_bkt ]);                           \
+           if (++(_he_newbkt->count) > tbl->ideal_chain_maxlen) {                \
+             tbl->nonideal_items++;                                              \
+             _he_newbkt->expand_mult = _he_newbkt->count /                       \
+                                        tbl->ideal_chain_maxlen;                 \
+           }                                                                     \
+           _he_thh->hh_prev = NULL;                                              \
+           _he_thh->hh_next = _he_newbkt->hh_head;                               \
+           if (_he_newbkt->hh_head != NULL) { _he_newbkt->hh_head->hh_prev =     \
+                _he_thh; }                                                       \
+           _he_newbkt->hh_head = _he_thh;                                        \
+           _he_thh = _he_hh_nxt;                                                 \
+        }                                                                        \
+    }                                                                            \
+    uthash_free( tbl->buckets, tbl->num_buckets*sizeof(struct UT_hash_bucket) ); \
+    tbl->num_buckets *= 2U;                                                      \
+    tbl->log2_num_buckets++;                                                     \
+    tbl->buckets = _he_new_buckets;                                              \
+    tbl->ineff_expands = (tbl->nonideal_items > (tbl->num_items >> 1)) ?         \
+        (tbl->ineff_expands+1U) : 0U;                                            \
+    if (tbl->ineff_expands > 1U) {                                               \
+        tbl->noexpand=1;                                                         \
+        uthash_noexpand_fyi(tbl);                                                \
+    }                                                                            \
+    uthash_expand_fyi(tbl);                                                      \
+} while (0)
+
+
+/* This is an adaptation of Simon Tatham's O(n log(n)) mergesort */
+/* Note that HASH_SORT assumes the hash handle name to be hh.
+ * HASH_SRT was added to allow the hash handle name to be passed in. */
+#define HASH_SORT(head,cmpfcn) HASH_SRT(hh,head,cmpfcn)
+#define HASH_SRT(hh,head,cmpfcn)                                                 \
+do {                                                                             \
+  unsigned _hs_i;                                                                \
+  unsigned _hs_looping,_hs_nmerges,_hs_insize,_hs_psize,_hs_qsize;               \
+  struct UT_hash_handle *_hs_p, *_hs_q, *_hs_e, *_hs_list, *_hs_tail;            \
+  if (head != NULL) {                                                            \
+      _hs_insize = 1;                                                            \
+      _hs_looping = 1;                                                           \
+      _hs_list = &((head)->hh);                                                  \
+      while (_hs_looping != 0U) {                                                \
+          _hs_p = _hs_list;                                                      \
+          _hs_list = NULL;                                                       \
+          _hs_tail = NULL;                                                       \
+          _hs_nmerges = 0;                                                       \
+          while (_hs_p != NULL) {                                                \
+              _hs_nmerges++;                                                     \
+              _hs_q = _hs_p;                                                     \
+              _hs_psize = 0;                                                     \
+              for ( _hs_i = 0; _hs_i  < _hs_insize; _hs_i++ ) {                  \
+                  _hs_psize++;                                                   \
+                  _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ?              \
+                          ((void*)((char*)(_hs_q->next) +                        \
+                          (head)->hh.tbl->hho)) : NULL);                         \
+                  if (! (_hs_q) ) { break; }                                     \
+              }                                                                  \
+              _hs_qsize = _hs_insize;                                            \
+              while ((_hs_psize > 0U) || ((_hs_qsize > 0U) && (_hs_q != NULL))) {\
+                  if (_hs_psize == 0U) {                                         \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ?          \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  } else if ( (_hs_qsize == 0U) || (_hs_q == NULL) ) {           \
+                      _hs_e = _hs_p;                                             \
+                      if (_hs_p != NULL){                                        \
+                        _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ?        \
+                                ((void*)((char*)(_hs_p->next) +                  \
+                                (head)->hh.tbl->hho)) : NULL);                   \
+                       }                                                         \
+                      _hs_psize--;                                               \
+                  } else if ((                                                   \
+                      cmpfcn(DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_p)), \
+                             DECLTYPE(head)(ELMT_FROM_HH((head)->hh.tbl,_hs_q))) \
+                             ) <= 0) {                                           \
+                      _hs_e = _hs_p;                                             \
+                      if (_hs_p != NULL){                                        \
+                        _hs_p = (UT_hash_handle*)((_hs_p->next != NULL) ?        \
+                               ((void*)((char*)(_hs_p->next) +                   \
+                               (head)->hh.tbl->hho)) : NULL);                    \
+                       }                                                         \
+                      _hs_psize--;                                               \
+                  } else {                                                       \
+                      _hs_e = _hs_q;                                             \
+                      _hs_q = (UT_hash_handle*)((_hs_q->next != NULL) ?          \
+                              ((void*)((char*)(_hs_q->next) +                    \
+                              (head)->hh.tbl->hho)) : NULL);                     \
+                      _hs_qsize--;                                               \
+                  }                                                              \
+                  if ( _hs_tail != NULL ) {                                      \
+                      _hs_tail->next = ((_hs_e != NULL) ?                        \
+                            ELMT_FROM_HH((head)->hh.tbl,_hs_e) : NULL);          \
+                  } else {                                                       \
+                      _hs_list = _hs_e;                                          \
+                  }                                                              \
+                  if (_hs_e != NULL) {                                           \
+                  _hs_e->prev = ((_hs_tail != NULL) ?                            \
+                     ELMT_FROM_HH((head)->hh.tbl,_hs_tail) : NULL);              \
+                  }                                                              \
+                  _hs_tail = _hs_e;                                              \
+              }                                                                  \
+              _hs_p = _hs_q;                                                     \
+          }                                                                      \
+          if (_hs_tail != NULL){                                                 \
+            _hs_tail->next = NULL;                                               \
+          }                                                                      \
+          if ( _hs_nmerges <= 1U ) {                                             \
+              _hs_looping=0;                                                     \
+              (head)->hh.tbl->tail = _hs_tail;                                   \
+              DECLTYPE_ASSIGN(head,ELMT_FROM_HH((head)->hh.tbl, _hs_list));      \
+          }                                                                      \
+          _hs_insize *= 2U;                                                      \
+      }                                                                          \
+      HASH_FSCK(hh,head);                                                        \
+ }                                                                               \
+} while (0)
+
+/* This function selects items from one hash into another hash.
+ * The end result is that the selected items have dual presence
+ * in both hashes. There is no copy of the items made; rather
+ * they are added into the new hash through a secondary hash
+ * hash handle that must be present in the structure. */
+#define HASH_SELECT(hh_dst, dst, hh_src, src, cond)                              \
+do {                                                                             \
+  unsigned _src_bkt, _dst_bkt;                                                   \
+  void *_last_elt=NULL, *_elt;                                                   \
+  UT_hash_handle *_src_hh, *_dst_hh, *_last_elt_hh=NULL;                         \
+  ptrdiff_t _dst_hho = ((char*)(&(dst)->hh_dst) - (char*)(dst));                 \
+  if (src != NULL) {                                                             \
+    for(_src_bkt=0; _src_bkt < (src)->hh_src.tbl->num_buckets; _src_bkt++) {     \
+      for(_src_hh = (src)->hh_src.tbl->buckets[_src_bkt].hh_head;                \
+          _src_hh != NULL;                                                       \
+          _src_hh = _src_hh->hh_next) {                                          \
+          _elt = ELMT_FROM_HH((src)->hh_src.tbl, _src_hh);                       \
+          if (cond(_elt)) {                                                      \
+            _dst_hh = (UT_hash_handle*)(((char*)_elt) + _dst_hho);               \
+            _dst_hh->key = _src_hh->key;                                         \
+            _dst_hh->keylen = _src_hh->keylen;                                   \
+            _dst_hh->hashv = _src_hh->hashv;                                     \
+            _dst_hh->prev = _last_elt;                                           \
+            _dst_hh->next = NULL;                                                \
+            if (_last_elt_hh != NULL) { _last_elt_hh->next = _elt; }             \
+            if (dst == NULL) {                                                   \
+              DECLTYPE_ASSIGN(dst,_elt);                                         \
+              HASH_MAKE_TABLE(hh_dst,dst);                                       \
+            } else {                                                             \
+              _dst_hh->tbl = (dst)->hh_dst.tbl;                                  \
+            }                                                                    \
+            HASH_TO_BKT(_dst_hh->hashv, _dst_hh->tbl->num_buckets, _dst_bkt);    \
+            HASH_ADD_TO_BKT(_dst_hh->tbl->buckets[_dst_bkt],_dst_hh);            \
+            (dst)->hh_dst.tbl->num_items++;                                      \
+            _last_elt = _elt;                                                    \
+            _last_elt_hh = _dst_hh;                                              \
+          }                                                                      \
+      }                                                                          \
+    }                                                                            \
+  }                                                                              \
+  HASH_FSCK(hh_dst,dst);                                                         \
+} while (0)
+
+#define HASH_CLEAR(hh,head)                                                      \
+do {                                                                             \
+  if (head != NULL) {                                                            \
+    uthash_free((head)->hh.tbl->buckets,                                         \
+                (head)->hh.tbl->num_buckets*sizeof(struct UT_hash_bucket));      \
+    HASH_BLOOM_FREE((head)->hh.tbl);                                             \
+    uthash_free((head)->hh.tbl, sizeof(UT_hash_table));                          \
+    (head)=NULL;                                                                 \
+  }                                                                              \
+} while (0)
+
+#define HASH_OVERHEAD(hh,head)                                                   \
+ ((head != NULL) ? (                                                             \
+ (size_t)(((head)->hh.tbl->num_items   * sizeof(UT_hash_handle))   +             \
+          ((head)->hh.tbl->num_buckets * sizeof(UT_hash_bucket))   +             \
+           sizeof(UT_hash_table)                                   +             \
+           (HASH_BLOOM_BYTELEN))) : 0U)
+
+#ifdef NO_DECLTYPE
+#define HASH_ITER(hh,head,el,tmp)                                                \
+for(((el)=(head)), ((*(char**)(&(tmp)))=(char*)((head!=NULL)?(head)->hh.next:NULL)); \
+  (el) != NULL; ((el)=(tmp)), ((*(char**)(&(tmp)))=(char*)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#else
+#define HASH_ITER(hh,head,el,tmp)                                                \
+for(((el)=(head)), ((tmp)=DECLTYPE(el)((head!=NULL)?(head)->hh.next:NULL));      \
+  (el) != NULL; ((el)=(tmp)), ((tmp)=DECLTYPE(el)((tmp!=NULL)?(tmp)->hh.next:NULL)))
+#endif
+
+/* obtain a count of items in the hash */
+#define HASH_COUNT(head) HASH_CNT(hh,head)
+#define HASH_CNT(hh,head) ((head != NULL)?((head)->hh.tbl->num_items):0U)
+
+typedef struct UT_hash_bucket {
+   struct UT_hash_handle *hh_head;
+   unsigned count;
+
+   /* expand_mult is normally set to 0. In this situation, the max chain length
+    * threshold is enforced at its default value, HASH_BKT_CAPACITY_THRESH. (If
+    * the bucket's chain exceeds this length, bucket expansion is triggered).
+    * However, setting expand_mult to a non-zero value delays bucket expansion
+    * (that would be triggered by additions to this particular bucket)
+    * until its chain length reaches a *multiple* of HASH_BKT_CAPACITY_THRESH.
+    * (The multiplier is simply expand_mult+1). The whole idea of this
+    * multiplier is to reduce bucket expansions, since they are expensive, in
+    * situations where we know that a particular bucket tends to be overused.
+    * It is better to let its chain length grow to a longer yet-still-bounded
+    * value, than to do an O(n) bucket expansion too often.
+    */
+   unsigned expand_mult;
+
+} UT_hash_bucket;
+
+/* random signature used only to find hash tables in external analysis */
+#define HASH_SIGNATURE 0xa0111fe1u
+#define HASH_BLOOM_SIGNATURE 0xb12220f2u
+
+typedef struct UT_hash_table {
+   UT_hash_bucket *buckets;
+   unsigned num_buckets, log2_num_buckets;
+   unsigned num_items;
+   struct UT_hash_handle *tail; /* tail hh in app order, for fast append    */
+   ptrdiff_t hho; /* hash handle offset (byte pos of hash handle in element */
+
+   /* in an ideal situation (all buckets used equally), no bucket would have
+    * more than ceil(#items/#buckets) items. that's the ideal chain length. */
+   unsigned ideal_chain_maxlen;
+
+   /* nonideal_items is the number of items in the hash whose chain position
+    * exceeds the ideal chain maxlen. these items pay the penalty for an uneven
+    * hash distribution; reaching them in a chain traversal takes >ideal steps */
+   unsigned nonideal_items;
+
+   /* ineffective expands occur when a bucket doubling was performed, but
+    * afterward, more than half the items in the hash had nonideal chain
+    * positions. If this happens on two consecutive expansions we inhibit any
+    * further expansion, as it's not helping; this happens when the hash
+    * function isn't a good fit for the key domain. When expansion is inhibited
+    * the hash will still work, albeit no longer in constant time. */
+   unsigned ineff_expands, noexpand;
+
+   uint32_t signature; /* used only to find hash tables in external analysis */
+#ifdef HASH_BLOOM
+   uint32_t bloom_sig; /* used only to test bloom exists in external analysis */
+   uint8_t *bloom_bv;
+   uint8_t bloom_nbits;
+#endif
+
+} UT_hash_table;
+
+typedef struct UT_hash_handle {
+   struct UT_hash_table *tbl;
+   void *prev;                       /* prev element in app order      */
+   void *next;                       /* next element in app order      */
+   struct UT_hash_handle *hh_prev;   /* previous hh in bucket order    */
+   struct UT_hash_handle *hh_next;   /* next hh in bucket order        */
+   void *key;                        /* ptr to enclosing struct's key  */
+   unsigned keylen;                  /* enclosing struct's key len     */
+   unsigned hashv;                   /* result of hash-fcn(key)        */
+} UT_hash_handle;
+
+#endif /* UTHASH_H */
diff --git a/server/utils.c b/server/utils.c
new file mode 100644
index 0000000..14a60c7
--- /dev/null
+++ b/server/utils.c
@@ -0,0 +1,448 @@
+/*
+ * utils.c - Misc utilities
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#ifndef __MINGW32__
+#include <pwd.h>
+#include <grp.h>
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "utils.h"
+
+#ifdef HAVE_SETRLIMIT
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif
+
+#define INT_DIGITS 19           /* enough for 64 bit integer */
+
+#ifdef LIB_ONLY
+FILE *logfile;
+#endif
+
+#ifdef HAS_SYSLOG
+int use_syslog = 0;
+#endif
+
+#ifndef __MINGW32__
+void
+ERROR(const char *s)
+{
+    char *msg = strerror(errno);
+    LOGE("%s: %s", s, msg);
+}
+
+#endif
+
+int use_tty = 1;
+
+char *
+ss_itoa(int i)
+{
+    /* Room for INT_DIGITS digits, - and '\0' */
+    static char buf[INT_DIGITS + 2];
+    char *p = buf + INT_DIGITS + 1;     /* points to terminating '\0' */
+    if (i >= 0) {
+        do {
+            *--p = '0' + (i % 10);
+            i   /= 10;
+        } while (i != 0);
+        return p;
+    } else {                     /* i < 0 */
+        do {
+            *--p = '0' - (i % 10);
+            i   /= 10;
+        } while (i != 0);
+        *--p = '-';
+    }
+    return p;
+}
+
+int
+ss_isnumeric(const char *s) {
+    if (!s || !*s)
+        return 0;
+    while (isdigit(*s))
+        ++s;
+    return *s == '\0';
+}
+
+/*
+ * setuid() and setgid() for a specified user.
+ */
+int
+run_as(const char *user)
+{
+#ifndef __MINGW32__
+    if (user[0]) {
+        /* Convert user to a long integer if it is a non-negative number.
+         * -1 means it is a user name. */
+        long uid = -1;
+        if (ss_isnumeric(user)) {
+            errno = 0;
+            char *endptr;
+            uid = strtol(user, &endptr, 10);
+            if (errno || endptr == user)
+                uid = -1;
+        }
+
+#ifdef HAVE_GETPWNAM_R
+        struct passwd pwdbuf, *pwd;
+        memset(&pwdbuf, 0, sizeof(struct passwd));
+        size_t buflen;
+        int err;
+
+        for (buflen = 128;; buflen *= 2) {
+            char buf[buflen];  /* variable length array */
+
+            /* Note that we use getpwnam_r() instead of getpwnam(),
+             * which returns its result in a statically allocated buffer and
+             * cannot be considered thread safe. */
+            err = uid >= 0 ? getpwuid_r((uid_t)uid, &pwdbuf, buf, buflen, &pwd)
+                : getpwnam_r(user, &pwdbuf, buf, buflen, &pwd);
+
+            if (err == 0 && pwd) {
+                /* setgid first, because we may not be allowed to do it anymore after setuid */
+                if (setgid(pwd->pw_gid) != 0) {
+                    LOGE(
+                        "Could not change group id to that of run_as user '%s': %s",
+                        pwd->pw_name, strerror(errno));
+                    return 0;
+                }
+
+                if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
+                    LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name);
+                    return 0;
+                }
+
+                if (setuid(pwd->pw_uid) != 0) {
+                    LOGE(
+                        "Could not change user id to that of run_as user '%s': %s",
+                        pwd->pw_name, strerror(errno));
+                    return 0;
+                }
+                break;
+            } else if (err != ERANGE) {
+                if (err) {
+                    LOGE("run_as user '%s' could not be found: %s", user,
+                            strerror(err));
+                } else {
+                    LOGE("run_as user '%s' could not be found.", user);
+                }
+                return 0;
+            } else if (buflen >= 16 * 1024) {
+                /* If getpwnam_r() seems defective, call it quits rather than
+                 * keep on allocating ever larger buffers until we crash. */
+                LOGE(
+                    "getpwnam_r() requires more than %u bytes of buffer space.",
+                    (unsigned)buflen);
+                return 0;
+            }
+            /* Else try again with larger buffer. */
+        }
+#else
+        /* No getpwnam_r() :-(  We'll use getpwnam() and hope for the best. */
+        struct passwd *pwd;
+
+        if (!(pwd = uid >=0 ? getpwuid((uid_t)uid) : getpwnam(user))) {
+            LOGE("run_as user %s could not be found.", user);
+            return 0;
+        }
+        /* setgid first, because we may not allowed to do it anymore after setuid */
+        if (setgid(pwd->pw_gid) != 0) {
+            LOGE("Could not change group id to that of run_as user '%s': %s",
+                 pwd->pw_name, strerror(errno));
+            return 0;
+        }
+        if (initgroups(pwd->pw_name, pwd->pw_gid) == -1) {
+            LOGE("Could not change supplementary groups for user '%s'.", pwd->pw_name);
+            return 0;
+        }
+        if (setuid(pwd->pw_uid) != 0) {
+            LOGE("Could not change user id to that of run_as user '%s': %s",
+                 pwd->pw_name, strerror(errno));
+            return 0;
+        }
+#endif
+    }
+
+#endif // __MINGW32__
+    return 1;
+}
+
+char *
+ss_strndup(const char *s, size_t n)
+{
+    size_t len = strlen(s);
+    char *ret;
+
+    if (len <= n) {
+        return strdup(s);
+    }
+
+    ret = ss_malloc(n + 1);
+    strncpy(ret, s, n);
+    ret[n] = '\0';
+    return ret;
+}
+
+void
+FATAL(const char *msg)
+{
+    LOGE("%s", msg);
+    exit(-1);
+}
+
+void *
+ss_malloc(size_t size)
+{
+    void *tmp = malloc(size);
+    if (tmp == NULL)
+        exit(EXIT_FAILURE);
+    return tmp;
+}
+
+void *
+ss_realloc(void *ptr, size_t new_size)
+{
+    void *new = realloc(ptr, new_size);
+    if (new == NULL) {
+        free(ptr);
+        ptr = NULL;
+        exit(EXIT_FAILURE);
+    }
+    return new;
+}
+
+void
+usage()
+{
+    printf("\n");
+    printf("shadowsocks-libev %s with %s\n\n", VERSION, USING_CRYPTO);
+    printf(
+        "  maintained by Max Lv <max.c.lv@gmail.com> and Linus Yang <laokongzi@gmail.com>\n\n");
+    printf("  usage:\n\n");
+#ifdef MODULE_LOCAL
+    printf("    ss-local\n");
+#elif MODULE_REMOTE
+    printf("    ss-server\n");
+#elif MODULE_TUNNEL
+    printf("    ss-tunnel\n");
+#elif MODULE_REDIR
+    printf("    ss-redir\n");
+#elif MODULE_MANAGER
+    printf("    ss-manager\n");
+#endif
+    printf("\n");
+    printf(
+        "       -s <server_host>           Host name or IP address of your remote server.\n");
+    printf(
+        "       -p <server_port>           Port number of your remote server.\n");
+    printf(
+        "       -l <local_port>            Port number of your local server.\n");
+    printf(
+        "       -k <password>              Password of your remote server.\n");
+    printf(
+        "       -m <encrypt_method>        Encrypt method: table, rc4, rc4-md5,\n");
+    printf(
+        "                                  aes-128-cfb, aes-192-cfb, aes-256-cfb,\n");
+    printf(
+        "                                  aes-128-ctr, aes-192-ctr, aes-256-ctr,\n");
+    printf(
+        "                                  bf-cfb, camellia-128-cfb, camellia-192-cfb,\n");
+    printf(
+        "                                  camellia-256-cfb, cast5-cfb, des-cfb,\n");
+    printf(
+        "                                  idea-cfb, rc2-cfb, seed-cfb, salsa20,\n");
+    printf(
+        "                                  chacha20 and chacha20-ietf.\n");
+    printf(
+        "                                  The default cipher is rc4-md5.\n");
+    printf("\n");
+    printf(
+        "       [-a <user>]                Run as another user.\n");
+    printf(
+        "       [-f <pid_file>]            The file path to store pid.\n");
+    printf(
+        "       [-t <timeout>]             Socket timeout in seconds.\n");
+    printf(
+        "       [-c <config_file>]         The path to config file.\n");
+#ifdef HAVE_SETRLIMIT
+    printf(
+        "       [-n <number>]              Max number of open files.\n");
+#endif
+#ifndef MODULE_REDIR
+    printf(
+        "       [-i <interface>]           Network interface to bind.\n");
+#endif
+    printf(
+        "       [-b <local_address>]       Local address to bind.\n");
+    printf("\n");
+    printf(
+        "       [-u]                       Enable UDP relay.\n");
+#ifdef MODULE_REDIR
+    printf(
+        "                                  TPROXY is required in redir mode.\n");
+#endif
+    printf(
+        "       [-U]                       Enable UDP relay and disable TCP relay.\n");
+    printf(
+        "       [-A]                       Enable onetime authentication.\n");
+#ifdef MODULE_REMOTE
+    printf(
+        "       [-6]                       Resovle hostname to IPv6 address first.\n");
+#endif
+    printf("\n");
+#ifdef MODULE_TUNNEL
+    printf(
+        "       [-L <addr>:<port>]         Destination server address and port\n");
+    printf(
+        "                                  for local port forwarding.\n");
+#endif
+#ifdef MODULE_REMOTE
+    printf(
+        "       [-d <addr>]                Name servers for internal DNS resolver.\n");
+#endif
+#if defined(MODULE_REMOTE) || defined(MODULE_LOCAL)
+    printf(
+        "       [--fast-open]              Enable TCP fast open.\n");
+    printf(
+        "                                  with Linux kernel > 3.7.0.\n");
+    printf(
+        "       [--acl <acl_file>]         Path to ACL (Access Control List).\n");
+#endif
+#if defined(MODULE_REMOTE) || defined(MODULE_MANAGER)
+    printf(
+        "       [--manager-address <addr>] UNIX domain socket address.\n");
+#endif
+#ifdef MODULE_MANAGER
+    printf(
+        "       [--executable <path>]      Path to the executable of ss-server.\n");
+#endif
+    printf(
+        "       [--mtu <MTU>]              MTU of your network interface.\n");
+#ifdef __linux__
+    printf(
+        "       [--mptcp]                  Enable Multipath TCP on MPTCP Kernel.\n");
+#ifdef MODULE_REMOTE
+    printf(
+        "       [--firewall]               Setup firewall rules for auto blocking.\n");
+#endif
+#endif
+    printf("\n");
+    printf(
+        "       [-v]                       Verbose mode.\n");
+    printf(
+        "       [-h, --help]               Print this message.\n");
+    printf("\n");
+}
+
+void
+daemonize(const char *path)
+{
+#ifndef __MINGW32__
+    /* Our process ID and Session ID */
+    pid_t pid, sid;
+
+    /* Fork off the parent process */
+    pid = fork();
+    if (pid < 0) {
+        exit(EXIT_FAILURE);
+    }
+
+    /* If we got a good PID, then
+     * we can exit the parent process. */
+    if (pid > 0) {
+        FILE *file = fopen(path, "w");
+        if (file == NULL) {
+            FATAL("Invalid pid file\n");
+        }
+
+        fprintf(file, "%d", (int)pid);
+        fclose(file);
+        exit(EXIT_SUCCESS);
+    }
+
+    /* Change the file mode mask */
+    umask(0);
+
+    /* Open any logs here */
+
+    /* Create a new SID for the child process */
+    sid = setsid();
+    if (sid < 0) {
+        /* Log the failure */
+        exit(EXIT_FAILURE);
+    }
+
+    /* Change the current working directory */
+    if ((chdir("/")) < 0) {
+        /* Log the failure */
+        exit(EXIT_FAILURE);
+    }
+
+    /* Close out the standard file descriptors */
+    close(STDIN_FILENO);
+    close(STDOUT_FILENO);
+    close(STDERR_FILENO);
+#endif
+}
+
+#ifdef HAVE_SETRLIMIT
+int
+set_nofile(int nofile)
+{
+    struct rlimit limit = { nofile, nofile }; /* set both soft and hard limit */
+
+    if (nofile <= 0) {
+        FATAL("nofile must be greater than 0\n");
+    }
+
+    if (setrlimit(RLIMIT_NOFILE, &limit) < 0) {
+        if (errno == EPERM) {
+            LOGE(
+                "insufficient permission to change NOFILE, not starting as root?");
+            return -1;
+        } else if (errno == EINVAL) {
+            LOGE("invalid nofile, decrease nofile and try again");
+            return -1;
+        } else {
+            LOGE("setrlimit failed: %s", strerror(errno));
+            return -1;
+        }
+    }
+
+    return 0;
+}
+
+#endif
diff --git a/server/utils.h b/server/utils.h
new file mode 100644
index 0000000..0fb7f5a
--- /dev/null
+++ b/server/utils.h
@@ -0,0 +1,232 @@
+/*
+ * utils.h - Misc utilities
+ *
+ * Copyright (C) 2013 - 2016, Max Lv <max.c.lv@gmail.com>
+ *
+ * This file is part of the shadowsocks-libev.
+ *
+ * shadowsocks-libev is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * shadowsocks-libev is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with shadowsocks-libev; see the file COPYING. If not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(USE_CRYPTO_OPENSSL)
+
+#include <openssl/opensslv.h>
+#define USING_CRYPTO OPENSSL_VERSION_TEXT
+
+#elif defined(USE_CRYPTO_POLARSSL)
+#include <polarssl/version.h>
+#define USING_CRYPTO POLARSSL_VERSION_STRING_FULL
+
+#elif defined(USE_CRYPTO_MBEDTLS)
+#include <mbedtls/version.h>
+#define USING_CRYPTO MBEDTLS_VERSION_STRING_FULL
+
+#endif
+
+#ifndef _UTILS_H
+#define _UTILS_H
+
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <time.h>
+
+#define PORTSTRLEN 16
+#define SS_ADDRSTRLEN (INET6_ADDRSTRLEN + PORTSTRLEN + 1)
+
+#ifdef ANDROID
+
+#include <android/log.h>
+
+#define USE_TTY()
+#define USE_SYSLOG(ident)
+#define LOGI(...)                                                \
+    ((void)__android_log_print(ANDROID_LOG_DEBUG, "shadowsocks", \
+                               __VA_ARGS__))
+#define LOGE(...)                                                \
+    ((void)__android_log_print(ANDROID_LOG_ERROR, "shadowsocks", \
+                               __VA_ARGS__))
+
+#else
+
+#define STR(x) # x
+#define TOSTR(x) STR(x)
+
+#ifdef LIB_ONLY
+
+extern FILE *logfile;
+
+#define TIME_FORMAT "%Y-%m-%d %H:%M:%S"
+
+#define USE_TTY()
+
+#define USE_SYSLOG(ident)
+
+#define USE_LOGFILE(ident)                                     \
+    do {                                                       \
+        if (ident != NULL) { logfile = fopen(ident, "w+"); } } \
+    while (0)
+
+#define CLOSE_LOGFILE                               \
+    do {                                            \
+        if (logfile != NULL) { fclose(logfile); } } \
+    while (0)
+
+#define LOGI(format, ...)                                                        \
+    do {                                                                         \
+        if (logfile != NULL) {                                                   \
+            time_t now = time(NULL);                                             \
+            char timestr[20];                                                    \
+            strftime(timestr, 20, TIME_FORMAT, localtime(&now));                 \
+            fprintf(logfile, " %s INFO: " format "\n", timestr, ## __VA_ARGS__); \
+            fflush(logfile); }                                                   \
+    }                                                                            \
+    while (0)
+
+#define LOGE(format, ...)                                        \
+    do {                                                         \
+        if (logfile != NULL) {                                   \
+            time_t now = time(NULL);                             \
+            char timestr[20];                                    \
+            strftime(timestr, 20, TIME_FORMAT, localtime(&now)); \
+            fprintf(logfile, " %s ERROR: " format "\n", timestr, \
+                    ## __VA_ARGS__);                             \
+            fflush(logfile); }                                   \
+    }                                                            \
+    while (0)
+
+#elif defined(_WIN32)
+
+#define TIME_FORMAT "%Y-%m-%d %H:%M:%S"
+
+#define USE_TTY()
+
+#define USE_SYSLOG(ident)
+
+#define LOGI(format, ...)                                                   \
+    do {                                                                    \
+        time_t now = time(NULL);                                            \
+        char timestr[20];                                                   \
+        strftime(timestr, 20, TIME_FORMAT, localtime(&now));                \
+        fprintf(stderr, " %s INFO: " format "\n", timestr, ## __VA_ARGS__); \
+        fflush(stderr); }                                                   \
+    while (0)
+
+#define LOGE(format, ...)                                                    \
+    do {                                                                     \
+        time_t now = time(NULL);                                             \
+        char timestr[20];                                                    \
+        strftime(timestr, 20, TIME_FORMAT, localtime(&now));                 \
+        fprintf(stderr, " %s ERROR: " format "\n", timestr, ## __VA_ARGS__); \
+        fflush(stderr); }                                                    \
+    while (0)
+
+#else
+
+#include <syslog.h>
+
+extern int use_tty;
+#define USE_TTY()                        \
+    do {                                 \
+        use_tty = isatty(STDERR_FILENO); \
+    } while (0)                          \
+
+#define HAS_SYSLOG
+extern int use_syslog;
+
+#define TIME_FORMAT "%F %T"
+
+#define USE_SYSLOG(ident)                          \
+    do {                                           \
+        use_syslog = 1;                            \
+        openlog((ident), LOG_CONS | LOG_PID, 0); } \
+    while (0)
+
+#define LOGI(format, ...)                                                        \
+    do {                                                                         \
+        if (use_syslog) {                                                        \
+            syslog(LOG_INFO, format, ## __VA_ARGS__);                            \
+        } else {                                                                 \
+            time_t now = time(NULL);                                             \
+            char timestr[20];                                                    \
+            strftime(timestr, 20, TIME_FORMAT, localtime(&now));                 \
+            if (use_tty) {                                                       \
+                fprintf(stderr, "\e[01;32m %s INFO: \e[0m" format "\n", timestr, \
+                        ## __VA_ARGS__);                                         \
+            } else {                                                             \
+                fprintf(stderr, " %s INFO: " format "\n", timestr,               \
+                        ## __VA_ARGS__);                                         \
+            }                                                                    \
+        }                                                                        \
+    }                                                                            \
+    while (0)
+
+#define LOGE(format, ...)                                                         \
+    do {                                                                          \
+        if (use_syslog) {                                                         \
+            syslog(LOG_ERR, format, ## __VA_ARGS__);                              \
+        } else {                                                                  \
+            time_t now = time(NULL);                                              \
+            char timestr[20];                                                     \
+            strftime(timestr, 20, TIME_FORMAT, localtime(&now));                  \
+            if (use_tty) {                                                        \
+                fprintf(stderr, "\e[01;35m %s ERROR: \e[0m" format "\n", timestr, \
+                        ## __VA_ARGS__);                                          \
+            } else {                                                              \
+                fprintf(stderr, " %s ERROR: " format "\n", timestr,               \
+                        ## __VA_ARGS__);                                          \
+            }                                                                     \
+        } }                                                                       \
+    while (0)
+
+#endif
+/* _WIN32 */
+
+#endif
+
+#ifdef __MINGW32__
+
+#ifdef ERROR
+#undef ERROR
+#endif
+#define ERROR(s) ss_error(s)
+
+#else
+
+void ERROR(const char *s);
+
+#endif
+
+char *ss_itoa(int i);
+int ss_isnumeric(const char *s);
+int run_as(const char *user);
+void FATAL(const char *msg);
+void usage(void);
+void daemonize(const char *path);
+char *ss_strndup(const char *s, size_t n);
+#ifdef HAVE_SETRLIMIT
+int set_nofile(int nofile);
+#endif
+
+void *ss_malloc(size_t size);
+void *ss_realloc(void *ptr, size_t new_size);
+
+#define ss_free(ptr)     \
+    do {                 \
+        free(ptr);       \
+        ptr = NULL;      \
+    } while (0)
+
+#endif // _UTILS_H
diff --git a/server/verify.c b/server/verify.c
new file mode 100644
index 0000000..9e7393d
--- /dev/null
+++ b/server/verify.c
@@ -0,0 +1,188 @@
+
+#include "verify.h"
+
+static int verify_simple_pack_unit_size = 2000;
+
+typedef struct verify_simple_local_data {
+    char * recv_buffer;
+    int recv_buffer_size;
+}verify_simple_local_data;
+
+void verify_simple_local_data_init(verify_simple_local_data* local) {
+    local->recv_buffer = (char*)malloc(16384);
+    local->recv_buffer_size = 0;
+}
+
+obfs * verify_simple_new_obfs() {
+    obfs * self = new_obfs();
+    self->l_data = malloc(sizeof(verify_simple_local_data));
+    verify_simple_local_data_init((verify_simple_local_data*)self->l_data);
+    return self;
+}
+
+void verify_simple_dispose(obfs *self) {
+    verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
+    if (local->recv_buffer != NULL) {
+        free(local->recv_buffer);
+        local->recv_buffer = NULL;
+    }
+    free(local);
+    self->l_data = NULL;
+    dispose_obfs(self);
+}
+
+int verify_simple_pack_data(char *data, int datalength, char *outdata) {
+    unsigned char rand_len = (xorshift128plus() & 0xF) + 1;
+    int out_size = rand_len + datalength + 6;
+    outdata[0] = out_size >> 8;
+    outdata[1] = out_size;
+    outdata[2] = rand_len;
+    memmove(outdata + rand_len + 2, data, datalength);
+    fillcrc32((unsigned char *)outdata, out_size);
+    return out_size;
+}
+
+int verify_simple_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t *capacity) {
+    char *plaindata = *pplaindata;
+    //verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 32);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    while ( len > verify_simple_pack_unit_size ) {
+        pack_len = verify_simple_pack_data(data, verify_simple_pack_unit_size, buffer);
+        buffer += pack_len;
+        data += verify_simple_pack_unit_size;
+        len -= verify_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = verify_simple_pack_data(data, len, buffer);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int verify_simple_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t *capacity) {
+    char *plaindata = *pplaindata;
+    verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+        return -1;
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    while (local->recv_buffer_size > 2) {
+        int length = ((int)recv_buffer[0] << 8) | recv_buffer[1];
+        if (length >= 8192 || length < 7) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            return -1;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        int crc = crc32((unsigned char*)recv_buffer, length);
+        if (crc != -1) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            return -1;
+        }
+        int data_size = length - recv_buffer[2] - 6;
+        memmove(buffer, recv_buffer + 2 + recv_buffer[2], data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int verify_simple_server_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t *capacity) {
+    char *plaindata = *pplaindata;
+    //verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
+    char * out_buffer = (char*)malloc(datalength * 2 + 32);
+    char * buffer = out_buffer;
+    char * data = plaindata;
+    int len = datalength;
+    int pack_len;
+    while ( len > verify_simple_pack_unit_size ) {
+        pack_len = verify_simple_pack_data(data, verify_simple_pack_unit_size, buffer);
+        buffer += pack_len;
+        data += verify_simple_pack_unit_size;
+        len -= verify_simple_pack_unit_size;
+    }
+    if (len > 0) {
+        pack_len = verify_simple_pack_data(data, len, buffer);
+        buffer += pack_len;
+    }
+    len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
+
+int verify_simple_server_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t *capacity) {
+    char *plaindata = *pplaindata;
+    verify_simple_local_data *local = (verify_simple_local_data*)self->l_data;
+    uint8_t * recv_buffer = (uint8_t *)local->recv_buffer;
+    if (local->recv_buffer_size + datalength > 16384)
+    {
+        LOGE("verify_simple: wrong buf length %d", local->recv_buffer_size + datalength);
+        return -1;
+    }
+    memmove(recv_buffer + local->recv_buffer_size, plaindata, datalength);
+    local->recv_buffer_size += datalength;
+
+    char * out_buffer = (char*)malloc(local->recv_buffer_size);
+    char * buffer = out_buffer;
+    while (local->recv_buffer_size > 2) {
+        int length = ((int)recv_buffer[0] << 8) | recv_buffer[1];
+        if (length >= 8192 || length < 7) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            LOGE("verify_simple: wrong length %d", length);
+            return -1;
+        }
+        if (length > local->recv_buffer_size)
+            break;
+
+        int crc = crc32((unsigned char*)recv_buffer, length);
+        if (crc != -1) {
+            free(out_buffer);
+            local->recv_buffer_size = 0;
+            LOGE("verify_simple: wrong crc");
+            return -1;
+        }
+        int data_size = length - recv_buffer[2] - 6;
+        memmove(buffer, recv_buffer + 2 + recv_buffer[2], data_size);
+        buffer += data_size;
+        memmove(recv_buffer, recv_buffer + length, local->recv_buffer_size -= length);
+    }
+    int len = buffer - out_buffer;
+    if (*capacity < len) {
+        *pplaindata = (char*)realloc(*pplaindata, *capacity = len * 2);
+        plaindata = *pplaindata;
+    }
+    memmove(plaindata, out_buffer, len);
+    free(out_buffer);
+    return len;
+}
diff --git a/server/verify.h b/server/verify.h
new file mode 100644
index 0000000..57c6ff9
--- /dev/null
+++ b/server/verify.h
@@ -0,0 +1,19 @@
+/*
+ * verify.h - Define shadowsocksR server's buffers and callbacks
+ *
+ * Copyright (C) 2015 - 2016, Break Wa11 <mmgac001@gmail.com>
+ */
+
+#ifndef _VERIFY_H
+#define _VERIFY_H
+
+obfs * verify_simple_new_obfs();
+void verify_simple_dispose(obfs *self);
+
+int verify_simple_client_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int verify_simple_client_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+int verify_simple_server_pre_encrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+int verify_simple_server_post_decrypt(obfs *self, char **pplaindata, int datalength, size_t* capacity);
+
+#endif // _VERIFY_H
-- 
2.19.1

