From c3e758dc87baf4bfc5803527a976a4314275499b Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sat, 12 Jul 2014 20:28:36 +0800 Subject: [PATCH 01/37] add crypt lib --- Makefile | 5 +- lualib-src/lua-crypt.c | 859 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 863 insertions(+), 1 deletion(-) create mode 100644 lualib-src/lua-crypt.c diff --git a/Makefile b/Makefile index 45631905..63d9085d 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ jemalloc : $(MALLOC_STATICLIB) CSERVICE = snlua logger gate harbor LUA_CLIB = skynet socketdriver int64 bson mongo md5 netpack \ cjson clientsocket memory profile multicast \ - cluster + cluster crypt SKYNET_SRC = skynet_main.c skynet_handle.c skynet_module.c skynet_mq.c \ skynet_server.c skynet_start.c skynet_timer.c skynet_error.c \ @@ -110,6 +110,9 @@ $(LUA_CLIB_PATH)/multicast.so : lualib-src/lua-multicast.c | $(LUA_CLIB_PATH) $(LUA_CLIB_PATH)/cluster.so : lualib-src/lua-cluster.c | $(LUA_CLIB_PATH) $(CC) $(CFLAGS) $(SHARED) -Iskynet-src $^ -o $@ +$(LUA_CLIB_PATH)/crypt.so : lualib-src/lua-crypt.c | $(LUA_CLIB_PATH) + $(CC) $(CFLAGS) $(SHARED) $^ -o $@ + clean : rm -f $(SKYNET_BUILD_PATH)/skynet $(CSERVICE_PATH)/*.so $(LUA_CLIB_PATH)/*.so diff --git a/lualib-src/lua-crypt.c b/lualib-src/lua-crypt.c new file mode 100644 index 00000000..34a190e7 --- /dev/null +++ b/lualib-src/lua-crypt.c @@ -0,0 +1,859 @@ +#include +#include + +#include +#include +#include +#include + +#define SMALL_CHUNK 256 + +/* the eight DES S-boxes */ + +uint32_t SB1[64] = { + 0x01010400, 0x00000000, 0x00010000, 0x01010404, + 0x01010004, 0x00010404, 0x00000004, 0x00010000, + 0x00000400, 0x01010400, 0x01010404, 0x00000400, + 0x01000404, 0x01010004, 0x01000000, 0x00000004, + 0x00000404, 0x01000400, 0x01000400, 0x00010400, + 0x00010400, 0x01010000, 0x01010000, 0x01000404, + 0x00010004, 0x01000004, 0x01000004, 0x00010004, + 0x00000000, 0x00000404, 0x00010404, 0x01000000, + 0x00010000, 0x01010404, 0x00000004, 0x01010000, + 0x01010400, 0x01000000, 0x01000000, 0x00000400, + 0x01010004, 0x00010000, 0x00010400, 0x01000004, + 0x00000400, 0x00000004, 0x01000404, 0x00010404, + 0x01010404, 0x00010004, 0x01010000, 0x01000404, + 0x01000004, 0x00000404, 0x00010404, 0x01010400, + 0x00000404, 0x01000400, 0x01000400, 0x00000000, + 0x00010004, 0x00010400, 0x00000000, 0x01010004 +}; + +static uint32_t SB2[64] = { + 0x80108020, 0x80008000, 0x00008000, 0x00108020, + 0x00100000, 0x00000020, 0x80100020, 0x80008020, + 0x80000020, 0x80108020, 0x80108000, 0x80000000, + 0x80008000, 0x00100000, 0x00000020, 0x80100020, + 0x00108000, 0x00100020, 0x80008020, 0x00000000, + 0x80000000, 0x00008000, 0x00108020, 0x80100000, + 0x00100020, 0x80000020, 0x00000000, 0x00108000, + 0x00008020, 0x80108000, 0x80100000, 0x00008020, + 0x00000000, 0x00108020, 0x80100020, 0x00100000, + 0x80008020, 0x80100000, 0x80108000, 0x00008000, + 0x80100000, 0x80008000, 0x00000020, 0x80108020, + 0x00108020, 0x00000020, 0x00008000, 0x80000000, + 0x00008020, 0x80108000, 0x00100000, 0x80000020, + 0x00100020, 0x80008020, 0x80000020, 0x00100020, + 0x00108000, 0x00000000, 0x80008000, 0x00008020, + 0x80000000, 0x80100020, 0x80108020, 0x00108000 +}; + +static uint32_t SB3[64] = { + 0x00000208, 0x08020200, 0x00000000, 0x08020008, + 0x08000200, 0x00000000, 0x00020208, 0x08000200, + 0x00020008, 0x08000008, 0x08000008, 0x00020000, + 0x08020208, 0x00020008, 0x08020000, 0x00000208, + 0x08000000, 0x00000008, 0x08020200, 0x00000200, + 0x00020200, 0x08020000, 0x08020008, 0x00020208, + 0x08000208, 0x00020200, 0x00020000, 0x08000208, + 0x00000008, 0x08020208, 0x00000200, 0x08000000, + 0x08020200, 0x08000000, 0x00020008, 0x00000208, + 0x00020000, 0x08020200, 0x08000200, 0x00000000, + 0x00000200, 0x00020008, 0x08020208, 0x08000200, + 0x08000008, 0x00000200, 0x00000000, 0x08020008, + 0x08000208, 0x00020000, 0x08000000, 0x08020208, + 0x00000008, 0x00020208, 0x00020200, 0x08000008, + 0x08020000, 0x08000208, 0x00000208, 0x08020000, + 0x00020208, 0x00000008, 0x08020008, 0x00020200 +}; + +static uint32_t SB4[64] = { + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802080, 0x00800081, 0x00800001, 0x00002001, + 0x00000000, 0x00802000, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00800080, 0x00800001, + 0x00000001, 0x00002000, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002001, 0x00002080, + 0x00800081, 0x00000001, 0x00002080, 0x00800080, + 0x00002000, 0x00802080, 0x00802081, 0x00000081, + 0x00800080, 0x00800001, 0x00802000, 0x00802081, + 0x00000081, 0x00000000, 0x00000000, 0x00802000, + 0x00002080, 0x00800080, 0x00800081, 0x00000001, + 0x00802001, 0x00002081, 0x00002081, 0x00000080, + 0x00802081, 0x00000081, 0x00000001, 0x00002000, + 0x00800001, 0x00002001, 0x00802080, 0x00800081, + 0x00002001, 0x00002080, 0x00800000, 0x00802001, + 0x00000080, 0x00800000, 0x00002000, 0x00802080 +}; + +static uint32_t SB5[64] = { + 0x00000100, 0x02080100, 0x02080000, 0x42000100, + 0x00080000, 0x00000100, 0x40000000, 0x02080000, + 0x40080100, 0x00080000, 0x02000100, 0x40080100, + 0x42000100, 0x42080000, 0x00080100, 0x40000000, + 0x02000000, 0x40080000, 0x40080000, 0x00000000, + 0x40000100, 0x42080100, 0x42080100, 0x02000100, + 0x42080000, 0x40000100, 0x00000000, 0x42000000, + 0x02080100, 0x02000000, 0x42000000, 0x00080100, + 0x00080000, 0x42000100, 0x00000100, 0x02000000, + 0x40000000, 0x02080000, 0x42000100, 0x40080100, + 0x02000100, 0x40000000, 0x42080000, 0x02080100, + 0x40080100, 0x00000100, 0x02000000, 0x42080000, + 0x42080100, 0x00080100, 0x42000000, 0x42080100, + 0x02080000, 0x00000000, 0x40080000, 0x42000000, + 0x00080100, 0x02000100, 0x40000100, 0x00080000, + 0x00000000, 0x40080000, 0x02080100, 0x40000100 +}; + +static uint32_t SB6[64] = { + 0x20000010, 0x20400000, 0x00004000, 0x20404010, + 0x20400000, 0x00000010, 0x20404010, 0x00400000, + 0x20004000, 0x00404010, 0x00400000, 0x20000010, + 0x00400010, 0x20004000, 0x20000000, 0x00004010, + 0x00000000, 0x00400010, 0x20004010, 0x00004000, + 0x00404000, 0x20004010, 0x00000010, 0x20400010, + 0x20400010, 0x00000000, 0x00404010, 0x20404000, + 0x00004010, 0x00404000, 0x20404000, 0x20000000, + 0x20004000, 0x00000010, 0x20400010, 0x00404000, + 0x20404010, 0x00400000, 0x00004010, 0x20000010, + 0x00400000, 0x20004000, 0x20000000, 0x00004010, + 0x20000010, 0x20404010, 0x00404000, 0x20400000, + 0x00404010, 0x20404000, 0x00000000, 0x20400010, + 0x00000010, 0x00004000, 0x20400000, 0x00404010, + 0x00004000, 0x00400010, 0x20004010, 0x00000000, + 0x20404000, 0x20000000, 0x00400010, 0x20004010 +}; + +static uint32_t SB7[64] = { + 0x00200000, 0x04200002, 0x04000802, 0x00000000, + 0x00000800, 0x04000802, 0x00200802, 0x04200800, + 0x04200802, 0x00200000, 0x00000000, 0x04000002, + 0x00000002, 0x04000000, 0x04200002, 0x00000802, + 0x04000800, 0x00200802, 0x00200002, 0x04000800, + 0x04000002, 0x04200000, 0x04200800, 0x00200002, + 0x04200000, 0x00000800, 0x00000802, 0x04200802, + 0x00200800, 0x00000002, 0x04000000, 0x00200800, + 0x04000000, 0x00200800, 0x00200000, 0x04000802, + 0x04000802, 0x04200002, 0x04200002, 0x00000002, + 0x00200002, 0x04000000, 0x04000800, 0x00200000, + 0x04200800, 0x00000802, 0x00200802, 0x04200800, + 0x00000802, 0x04000002, 0x04200802, 0x04200000, + 0x00200800, 0x00000000, 0x00000002, 0x04200802, + 0x00000000, 0x00200802, 0x04200000, 0x00000800, + 0x04000002, 0x04000800, 0x00000800, 0x00200002 +}; + +static uint32_t SB8[64] = { + 0x10001040, 0x00001000, 0x00040000, 0x10041040, + 0x10000000, 0x10001040, 0x00000040, 0x10000000, + 0x00040040, 0x10040000, 0x10041040, 0x00041000, + 0x10041000, 0x00041040, 0x00001000, 0x00000040, + 0x10040000, 0x10000040, 0x10001000, 0x00001040, + 0x00041000, 0x00040040, 0x10040040, 0x10041000, + 0x00001040, 0x00000000, 0x00000000, 0x10040040, + 0x10000040, 0x10001000, 0x00041040, 0x00040000, + 0x00041040, 0x00040000, 0x10041000, 0x00001000, + 0x00000040, 0x10040040, 0x00001000, 0x00041040, + 0x10001000, 0x00000040, 0x10000040, 0x10040000, + 0x10040040, 0x10000000, 0x00040000, 0x10001040, + 0x00000000, 0x10041040, 0x00040040, 0x10000040, + 0x10040000, 0x10001000, 0x10001040, 0x00000000, + 0x10041040, 0x00041000, 0x00041000, 0x00001040, + 0x00001040, 0x00040040, 0x10000000, 0x10041000 +}; + +/* PC1: left and right halves bit-swap */ + +static uint32_t LHs[16] = { + 0x00000000, 0x00000001, 0x00000100, 0x00000101, + 0x00010000, 0x00010001, 0x00010100, 0x00010101, + 0x01000000, 0x01000001, 0x01000100, 0x01000101, + 0x01010000, 0x01010001, 0x01010100, 0x01010101 +}; + +static uint32_t RHs[16] = { + 0x00000000, 0x01000000, 0x00010000, 0x01010000, + 0x00000100, 0x01000100, 0x00010100, 0x01010100, + 0x00000001, 0x01000001, 0x00010001, 0x01010001, + 0x00000101, 0x01000101, 0x00010101, 0x01010101, +}; + +/* platform-independant 32-bit integer manipulation macros */ + +#define GET_UINT32(n,b,i) \ +{ \ + (n) = ( (uint32_t) (b)[(i) ] << 24 ) \ + | ( (uint32_t) (b)[(i) + 1] << 16 ) \ + | ( (uint32_t) (b)[(i) + 2] << 8 ) \ + | ( (uint32_t) (b)[(i) + 3] ); \ +} + +#define PUT_UINT32(n,b,i) \ +{ \ + (b)[(i) ] = (uint8_t) ( (n) >> 24 ); \ + (b)[(i) + 1] = (uint8_t) ( (n) >> 16 ); \ + (b)[(i) + 2] = (uint8_t) ( (n) >> 8 ); \ + (b)[(i) + 3] = (uint8_t) ( (n) ); \ +} + +/* Initial Permutation macro */ + +#define DES_IP(X,Y) \ +{ \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + Y = ((Y << 1) | (Y >> 31)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; Y ^= T; X ^= T; \ + X = ((X << 1) | (X >> 31)) & 0xFFFFFFFF; \ +} + +/* Final Permutation macro */ + +#define DES_FP(X,Y) \ +{ \ + X = ((X << 31) | (X >> 1)) & 0xFFFFFFFF; \ + T = (X ^ Y) & 0xAAAAAAAA; X ^= T; Y ^= T; \ + Y = ((Y << 31) | (Y >> 1)) & 0xFFFFFFFF; \ + T = ((Y >> 8) ^ X) & 0x00FF00FF; X ^= T; Y ^= (T << 8); \ + T = ((Y >> 2) ^ X) & 0x33333333; X ^= T; Y ^= (T << 2); \ + T = ((X >> 16) ^ Y) & 0x0000FFFF; Y ^= T; X ^= (T << 16); \ + T = ((X >> 4) ^ Y) & 0x0F0F0F0F; Y ^= T; X ^= (T << 4); \ +} + +/* DES round macro */ + +#define DES_ROUND(X,Y) \ +{ \ + T = *SK++ ^ X; \ + Y ^= SB8[ (T ) & 0x3F ] ^ \ + SB6[ (T >> 8) & 0x3F ] ^ \ + SB4[ (T >> 16) & 0x3F ] ^ \ + SB2[ (T >> 24) & 0x3F ]; \ + \ + T = *SK++ ^ ((X << 28) | (X >> 4)); \ + Y ^= SB7[ (T ) & 0x3F ] ^ \ + SB5[ (T >> 8) & 0x3F ] ^ \ + SB3[ (T >> 16) & 0x3F ] ^ \ + SB1[ (T >> 24) & 0x3F ]; \ +} + +/* DES key schedule */ + +static void +des_main_ks( uint32_t SK[32], const uint8_t key[8] ) { + int i; + uint32_t X, Y, T; + + GET_UINT32( X, key, 0 ); + GET_UINT32( Y, key, 4 ); + + /* Permuted Choice 1 */ + + T = ((Y >> 4) ^ X) & 0x0F0F0F0F; X ^= T; Y ^= (T << 4); + T = ((Y ) ^ X) & 0x10101010; X ^= T; Y ^= (T ); + + X = (LHs[ (X ) & 0xF] << 3) | (LHs[ (X >> 8) & 0xF ] << 2) + | (LHs[ (X >> 16) & 0xF] << 1) | (LHs[ (X >> 24) & 0xF ] ) + | (LHs[ (X >> 5) & 0xF] << 7) | (LHs[ (X >> 13) & 0xF ] << 6) + | (LHs[ (X >> 21) & 0xF] << 5) | (LHs[ (X >> 29) & 0xF ] << 4); + + Y = (RHs[ (Y >> 1) & 0xF] << 3) | (RHs[ (Y >> 9) & 0xF ] << 2) + | (RHs[ (Y >> 17) & 0xF] << 1) | (RHs[ (Y >> 25) & 0xF ] ) + | (RHs[ (Y >> 4) & 0xF] << 7) | (RHs[ (Y >> 12) & 0xF ] << 6) + | (RHs[ (Y >> 20) & 0xF] << 5) | (RHs[ (Y >> 28) & 0xF ] << 4); + + X &= 0x0FFFFFFF; + Y &= 0x0FFFFFFF; + + /* calculate subkeys */ + + for( i = 0; i < 16; i++ ) + { + if( i < 2 || i == 8 || i == 15 ) + { + X = ((X << 1) | (X >> 27)) & 0x0FFFFFFF; + Y = ((Y << 1) | (Y >> 27)) & 0x0FFFFFFF; + } + else + { + X = ((X << 2) | (X >> 26)) & 0x0FFFFFFF; + Y = ((Y << 2) | (Y >> 26)) & 0x0FFFFFFF; + } + + *SK++ = ((X << 4) & 0x24000000) | ((X << 28) & 0x10000000) + | ((X << 14) & 0x08000000) | ((X << 18) & 0x02080000) + | ((X << 6) & 0x01000000) | ((X << 9) & 0x00200000) + | ((X >> 1) & 0x00100000) | ((X << 10) & 0x00040000) + | ((X << 2) & 0x00020000) | ((X >> 10) & 0x00010000) + | ((Y >> 13) & 0x00002000) | ((Y >> 4) & 0x00001000) + | ((Y << 6) & 0x00000800) | ((Y >> 1) & 0x00000400) + | ((Y >> 14) & 0x00000200) | ((Y ) & 0x00000100) + | ((Y >> 5) & 0x00000020) | ((Y >> 10) & 0x00000010) + | ((Y >> 3) & 0x00000008) | ((Y >> 18) & 0x00000004) + | ((Y >> 26) & 0x00000002) | ((Y >> 24) & 0x00000001); + + *SK++ = ((X << 15) & 0x20000000) | ((X << 17) & 0x10000000) + | ((X << 10) & 0x08000000) | ((X << 22) & 0x04000000) + | ((X >> 2) & 0x02000000) | ((X << 1) & 0x01000000) + | ((X << 16) & 0x00200000) | ((X << 11) & 0x00100000) + | ((X << 3) & 0x00080000) | ((X >> 6) & 0x00040000) + | ((X << 15) & 0x00020000) | ((X >> 4) & 0x00010000) + | ((Y >> 2) & 0x00002000) | ((Y << 8) & 0x00001000) + | ((Y >> 14) & 0x00000808) | ((Y >> 9) & 0x00000400) + | ((Y ) & 0x00000200) | ((Y << 7) & 0x00000100) + | ((Y >> 7) & 0x00000020) | ((Y >> 3) & 0x00000011) + | ((Y << 2) & 0x00000004) | ((Y >> 21) & 0x00000002); + } +} + +/* DES 64-bit block encryption/decryption */ + +static void +des_crypt( const uint32_t SK[32], const uint8_t input[8], uint8_t output[8] ) { + uint32_t X, Y, T; + + GET_UINT32( X, input, 0 ); + GET_UINT32( Y, input, 4 ); + + DES_IP( X, Y ); + + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + DES_ROUND( Y, X ); DES_ROUND( X, Y ); + + DES_FP( Y, X ); + + PUT_UINT32( Y, output, 0 ); + PUT_UINT32( X, output, 4 ); +} + +static int +lrandomkey(lua_State *L) { + char tmp[8]; + int i; + for (i=0;i<8;i++) { + tmp[i] = random() & 0xff; + } + lua_pushlstring(L, tmp, 8); + return 1; +} + +static void +des_key(lua_State *L, uint32_t SK[32]) { + size_t keysz = 0; + const void * key = luaL_checklstring(L, 1, &keysz); + if (keysz != 8) { + luaL_error(L, "Invalid key size %d, need 8 bytes", (int)keysz); + } + des_main_ks(SK, key); +} + +static int +ldesencode(lua_State *L) { + uint32_t SK[32]; + des_key(L, SK); + + size_t textsz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); + size_t chunksz = (textsz + 8) & ~7; + uint8_t tmp[SMALL_CHUNK]; + uint8_t *buffer = tmp; + if (chunksz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, chunksz); + } + int i; + for (i=0;i<(int)textsz-7;i+=8) { + des_crypt(SK, text+i, buffer+i); + } + int bytes = textsz - i; + uint8_t tail[8]; + int j; + for (j=0;j<8;j++) { + if (j < bytes) { + tail[j] = text[i+j]; + } else if (j==bytes) { + tail[j] = 0x80; + } else { + tail[j] = 0; + } + } + des_crypt(SK, tail, buffer+i); + lua_pushlstring(L, (const char *)buffer, chunksz); + + return 1; +} + +static int +ldesdecode(lua_State *L) { + uint32_t ESK[32]; + des_key(L, ESK); + uint32_t SK[32]; + int i; + for( i = 0; i < 32; i += 2 ) { + SK[i] = ESK[30 - i]; + SK[i + 1] = ESK[31 - i]; + } + size_t textsz = 0; + const uint8_t *text = (const uint8_t *)luaL_checklstring(L, 2, &textsz); + if ((textsz & 7) || textsz == 0) { + return luaL_error(L, "Invalid des crypt text length %d", (int)textsz); + } + uint8_t tmp[SMALL_CHUNK]; + uint8_t *buffer = tmp; + if (textsz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, textsz); + } + for (i=0;i=textsz-8;i--) { + if (buffer[i] == 0) { + padding++; + } else if (buffer[i] == 0x80) { + break; + } else { + return luaL_error(L, "Invalid des crypt text"); + } + } + if (padding > 8) { + return luaL_error(L, "Invalid des crypt text"); + } + lua_pushlstring(L, (const char *)buffer, textsz - padding); + return 1; +} + + +static void +Hash(const char * str, int sz, uint8_t key[8]) { + uint32_t djb_hash = 5381L; + uint32_t js_hash = 1315423911L; + + int i; + for (i=0;i> 2)); + } + + key[0] = djb_hash & 0xff; + key[1] = (djb_hash >> 8) & 0xff; + key[2] = (djb_hash >> 16) & 0xff; + key[3] = (djb_hash >> 24) & 0xff; + + key[4] = js_hash & 0xff; + key[5] = (js_hash >> 8) & 0xff; + key[6] = (js_hash >> 16) & 0xff; + key[7] = (js_hash >> 24) & 0xff; +} + +static int +lhashkey(lua_State *L) { + size_t sz = 0; + const char * key = luaL_checklstring(L, 1, &sz); + uint8_t realkey[8]; + Hash(key,(int)sz,realkey); + lua_pushlstring(L, (const char *)realkey, 8); + return 1; +} + +static int +ltohex(lua_State *L) { + static char hex[] = "0123456789abcdef"; + size_t sz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (sz > SMALL_CHUNK/2) { + buffer = lua_newuserdata(L, sz * 2); + } + int i; + for (i=0;i> 4]; + buffer[i*2+1] = hex[text[i] & 0xf]; + } + lua_pushlstring(L, buffer, sz * 2); + return 1; +} + +#define HEX(v,c) { char tmp = (char) c; if (tmp >= '0' && tmp <= '9') { v = tmp-'0'; } else { v = tmp - 'a' + 10; } } + +static int +lfromhex(lua_State *L) { + size_t sz = 0; + const char * text = luaL_checklstring(L, 1, &sz); + if (sz & 2) { + return luaL_error(L, "Invalid hex text size %d", (int)sz); + } + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (sz > SMALL_CHUNK*2) { + buffer = lua_newuserdata(L, sz / 2); + } + int i; + for (i=0;i 16 || low > 16) { + return luaL_error(L, "Invalid hex text", text); + } + buffer[i/2] = hi<<4 | low; + } + lua_pushlstring(L, buffer, i/2); + return 1; +} + +// Constants are the integer part of the sines of integers (in radians) * 2^32. +const uint32_t k[64] = { +0xd76aa478, 0xe8c7b756, 0x242070db, 0xc1bdceee , +0xf57c0faf, 0x4787c62a, 0xa8304613, 0xfd469501 , +0x698098d8, 0x8b44f7af, 0xffff5bb1, 0x895cd7be , +0x6b901122, 0xfd987193, 0xa679438e, 0x49b40821 , +0xf61e2562, 0xc040b340, 0x265e5a51, 0xe9b6c7aa , +0xd62f105d, 0x02441453, 0xd8a1e681, 0xe7d3fbc8 , +0x21e1cde6, 0xc33707d6, 0xf4d50d87, 0x455a14ed , +0xa9e3e905, 0xfcefa3f8, 0x676f02d9, 0x8d2a4c8a , +0xfffa3942, 0x8771f681, 0x6d9d6122, 0xfde5380c , +0xa4beea44, 0x4bdecfa9, 0xf6bb4b60, 0xbebfbc70 , +0x289b7ec6, 0xeaa127fa, 0xd4ef3085, 0x04881d05 , +0xd9d4d039, 0xe6db99e5, 0x1fa27cf8, 0xc4ac5665 , +0xf4292244, 0x432aff97, 0xab9423a7, 0xfc93a039 , +0x655b59c3, 0x8f0ccc92, 0xffeff47d, 0x85845dd1 , +0x6fa87e4f, 0xfe2ce6e0, 0xa3014314, 0x4e0811a1 , +0xf7537e82, 0xbd3af235, 0x2ad7d2bb, 0xeb86d391 }; + +// r specifies the per-round shift amounts +const uint32_t r[] = {7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, + 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, + 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, + 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21}; + +// leftrotate function definition +#define LEFTROTATE(x, c) (((x) << (c)) | ((x) >> (32 - (c)))) + +static void +hmac(uint32_t x[2], uint32_t y[2], uint32_t result[2]) { + uint32_t w[16]; + uint32_t a, b, c, d, f, g, temp; + int i; + + a = 0x67452301u; + b = 0xefcdab89u; + c = 0x98badcfeu; + d = 0x10325476u; + + for (i=0;i<16;i+=4) { + w[i] = x[1]; + w[i+1] = x[0]; + w[i+2] = y[1]; + w[i+3] = y[0]; + } + + for(i = 0; i<64; i++) { + if (i < 16) { + f = (b & c) | ((~b) & d); + g = i; + } else if (i < 32) { + f = (d & b) | ((~d) & c); + g = (5*i + 1) % 16; + } else if (i < 48) { + f = b ^ c ^ d; + g = (3*i + 5) % 16; + } else { + f = c ^ (b | (~d)); + g = (7*i) % 16; + } + + temp = d; + d = c; + c = b; + b = b + LEFTROTATE((a + f + k[i] + w[g]), r[i]); + a = temp; + + } + + result[0] = c^d; + result[1] = a^b; +} + +static void +read64(lua_State *L, uint32_t xx[2], uint32_t yy[2]) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid hmac x"); + } + const uint8_t *y = (const uint8_t *)luaL_checklstring(L, 2, &sz); + if (sz != 8) { + luaL_error(L, "Invalid hmac y"); + } + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + yy[0] = y[0] | y[1]<<8 | y[2]<<16 | y[3]<<24; + yy[1] = y[4] | y[5]<<8 | y[6]<<16 | y[7]<<24; +} + +static int +lhmac64(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint32_t result[2]; + hmac(x,y,result); + uint8_t tmp[8]; + tmp[0] = result[0] & 0xff; + tmp[1] = (result[0] >> 8 )& 0xff; + tmp[2] = (result[0] >> 16 )& 0xff; + tmp[3] = (result[0] >> 24 )& 0xff; + tmp[4] = result[1] & 0xff; + tmp[5] = (result[1] >> 8 )& 0xff; + tmp[6] = (result[1] >> 16 )& 0xff; + tmp[7] = (result[1] >> 24 )& 0xff; + + lua_pushlstring(L, (const char *)tmp, 8); + return 1; +} + +// powmodp64 for DH-key exchange + +// The biggest 64bit prime +#define P 0xffffffffffffffc5ull + +static inline uint64_t +mul_mod_p(uint64_t a, uint64_t b) { + uint64_t m = 0; + while(b) { + if(b&1) { + uint64_t t = P-a; + if ( m >= t) { + m -= t; + } else { + m += a; + } + } + if (a >= P - a) { + a = a * 2 - P; + } else { + a = a * 2; + } + b>>=1; + } + return m; +} + +static inline uint64_t +pow_mod_p(uint64_t a, uint64_t b) { + if (b==1) { + return a; + } + uint64_t t = pow_mod_p(a, b>>1); + t = mul_mod_p(t,t); + if (b % 2) { + t = mul_mod_p(t, a); + } + return t; +} + +// calc a^b % p +static uint64_t +powmodp(uint64_t a, uint64_t b) { + if (a > P) + a%=P; + return pow_mod_p(a,b); +} + +static void +push64(lua_State *L, uint64_t r) { + uint8_t tmp[8]; + tmp[0] = r & 0xff; + tmp[1] = (r >> 8 )& 0xff; + tmp[2] = (r >> 16 )& 0xff; + tmp[3] = (r >> 24 )& 0xff; + tmp[4] = (r >> 32 )& 0xff; + tmp[5] = (r >> 40 )& 0xff; + tmp[6] = (r >> 48 )& 0xff; + tmp[7] = (r >> 56 )& 0xff; + + lua_pushlstring(L, (const char *)tmp, 8); +} + +static int +ldhsecret(lua_State *L) { + uint32_t x[2], y[2]; + read64(L, x, y); + uint64_t r = powmodp((uint64_t)x[0] | (uint64_t)x[1]<<32, + (uint64_t)y[0] | (uint64_t)y[1]<<32); + + push64(L, r); + + return 1; +} + +#define G 5 + +static int +ldhexchange(lua_State *L) { + size_t sz = 0; + const uint8_t *x = (const uint8_t *)luaL_checklstring(L, 1, &sz); + if (sz != 8) { + luaL_error(L, "Invalid hmac x"); + } + uint32_t xx[2]; + xx[0] = x[0] | x[1]<<8 | x[2]<<16 | x[3]<<24; + xx[1] = x[4] | x[5]<<8 | x[6]<<16 | x[7]<<24; + + uint64_t r = powmodp(5, (uint64_t)xx[0] | (uint64_t)xx[1]<<32); + push64(L, r); + return 1; +} + +// base64 + +static int +lb64encode(lua_State *L) { + static const char* encoding = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + size_t sz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); + int encode_sz = (sz + 2)/3*4; + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (encode_sz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, encode_sz); + } + int i,j; + j=0; + for (i=0;i> 18]; + buffer[j+1] = encoding[(v >> 12) & 0x3f]; + buffer[j+2] = encoding[(v >> 6) & 0x3f]; + buffer[j+3] = encoding[(v) & 0x3f]; + j+=4; + } + int padding = sz-i; + uint32_t v; + switch(padding) { + case 1 : + v = text[i]; + buffer[j] = encoding[v >> 2]; + buffer[j+1] = encoding[(v & 3) << 4]; + buffer[j+2] = '='; + buffer[j+3] = '='; + break; + case 2 : + v = text[i] << 8 | text[i+1]; + buffer[j] = encoding[v >> 10]; + buffer[j+1] = encoding[(v >> 4) & 0x3f]; + buffer[j+2] = encoding[(v & 0xf) << 2]; + buffer[j+3] = '='; + break; + } + lua_pushlstring(L, buffer, encode_sz); + return 1; +} + +static inline int +b64index(uint8_t c) { + static const int decoding[] = {62,-1,-1,-1,63,52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-2,-1,-1,-1,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1,-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51}; + int decoding_size = sizeof(decoding)/sizeof(decoding[0]); + if (c<43) { + return -1; + } + c -= 43; + if (c>=decoding_size) + return -1; + return decoding[c]; +} + +static int +lb64decode(lua_State *L) { + size_t sz = 0; + const uint8_t * text = (const uint8_t *)luaL_checklstring(L, 1, &sz); + int decode_sz = (sz+3)/4*3; + char tmp[SMALL_CHUNK]; + char *buffer = tmp; + if (decode_sz > SMALL_CHUNK) { + buffer = lua_newuserdata(L, decode_sz); + } + int i,j; + int output = 0; + for (i=0;i=sz) { + return luaL_error(L, "Invalid base64 text"); + } + c[j] = b64index(text[i]); + if (c[j] == -1) { + ++i; + continue; + } + if (c[j] == -2) { + ++padding; + } + ++i; + ++j; + } + uint32_t v; + switch (padding) { + case 0: + v = (unsigned)c[0] << 18 | c[1] << 12 | c[2] << 6 | c[3]; + buffer[output] = v >> 16; + buffer[output+1] = (v >> 8) & 0xff; + buffer[output+2] = v & 0xff; + output += 3; + break; + case 1: + if (c[3] != -2 || (c[2] & 3)!=0) { + return luaL_error(L, "Invalid base64 text"); + } + v = (unsigned)c[0] << 10 | c[1] << 4 | c[2] >> 2 ; + buffer[output] = v >> 8; + buffer[output+1] = v & 0xff; + output += 2; + break; + case 2: + if (c[3] != -2 || c[2] != -2 || (c[1] & 0xf) !=0) { + return luaL_error(L, "Invalid base64 text"); + } + v = (unsigned)c[0] << 2 | c[1] >> 4; + buffer[output] = v; + ++ output; + break; + default: + return luaL_error(L, "Invalid base64 text"); + } + } + lua_pushlstring(L, buffer, output); + return 1; +} + +int +luaopen_crypt(lua_State *L) { + luaL_checkversion(L); + srandom(time(NULL)); + luaL_Reg l[] = { + { "hashkey", lhashkey }, + { "randomkey", lrandomkey }, + { "desencode", ldesencode }, + { "desdecode", ldesdecode }, + { "hexencode", ltohex }, + { "hexdecode", lfromhex }, + { "hmac64", lhmac64 }, + { "dhexchange", ldhexchange }, + { "dhsecret", ldhsecret }, + { "base64encode", lb64encode }, + { "base64decode", lb64decode }, + { NULL, NULL }, + }; + luaL_newlib(L,l); + return 1; +} From 4284cfc37206735079d2f733825e05b2b917c039 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 10:32:21 +0800 Subject: [PATCH 02/37] loginserver and example --- examples/config.login | 8 +++ examples/login/client.lua | 59 +++++++++++++++++ examples/login/logind.lua | 25 +++++++ examples/login/main.lua | 5 ++ lualib/loginserver.lua | 133 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 230 insertions(+) create mode 100644 examples/config.login create mode 100644 examples/login/client.lua create mode 100644 examples/login/logind.lua create mode 100644 examples/login/main.lua create mode 100644 lualib/loginserver.lua diff --git a/examples/config.login b/examples/config.login new file mode 100644 index 00000000..c72dcb27 --- /dev/null +++ b/examples/config.login @@ -0,0 +1,8 @@ +thread = 8 +logger = nil +harbor = 0 +start = "main" +bootstrap = "snlua bootstrap" -- The service for bootstrap +luaservice = "./service/?.lua;./examples/login/?.lua" +lualoader = "lualib/loader.lua" +cpath = "./cservice/?.so" diff --git a/examples/login/client.lua b/examples/login/client.lua new file mode 100644 index 00000000..36ee3e2a --- /dev/null +++ b/examples/login/client.lua @@ -0,0 +1,59 @@ +package.cpath = "luaclib/?.so" + +local socket = require "clientsocket" +local cjson = require "cjson" +local crypt = require "crypt" + +local last +local fd = assert(socket.connect("127.0.0.1", 8001)) +local input = {} + +local function readline() + local line = table.remove(input, 1) + if line then + return line + end + + while true do + local status + status, last = socket.readline(fd, last, input) + if status == nil then + error "Server closed" + end + if not status then + socket.usleep(100) + else + local line = table.remove(input, 1) + if line then + return line + end + end + end +end + +local challenge = crypt.base64decode(readline()) + +local clientkey = crypt.randomkey() +socket.writeline(fd, crypt.base64encode(crypt.dhexchange(clientkey))) +local secret = crypt.dhsecret(crypt.base64decode(readline()), clientkey) + +print("sceret is ", crypt.hexencode(secret)) + +local hmac = crypt.hmac64(challenge, secret) +socket.writeline(fd, crypt.base64encode(hmac)) + +local token = { + user = "hello", + pass = "password", +} + +local etoken = crypt.desencode(secret, cjson.encode(token)) +local b = crypt.base64encode(etoken) +socket.writeline(fd, crypt.base64encode(etoken)) + +print(readline()) + + + + + diff --git a/examples/login/logind.lua b/examples/login/logind.lua new file mode 100644 index 00000000..3c8a3835 --- /dev/null +++ b/examples/login/logind.lua @@ -0,0 +1,25 @@ +local login = require "loginserver" +local json = require "cjson" +local crypt = require "crypt" + +local server = { + host = "127.0.0.1", + port = 8001, +} + +function server.auth_handler(token) + token = json.decode(token) + assert(token.user) + assert(token.pass == "password") + return "sample", token.user +end + +function server.login_handler(server, uid, secret) + print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) +end + +function server.logout_handler(server, uid) + print(string.format("%s@%s is logout", uid, server)) +end + +login(server) diff --git a/examples/login/main.lua b/examples/login/main.lua new file mode 100644 index 00000000..493b2af4 --- /dev/null +++ b/examples/login/main.lua @@ -0,0 +1,5 @@ +local skynet = require "skynet" + +skynet.start(function() + skynet.newservice "logind" +end) diff --git a/lualib/loginserver.lua b/lualib/loginserver.lua new file mode 100644 index 00000000..658ae1cd --- /dev/null +++ b/lualib/loginserver.lua @@ -0,0 +1,133 @@ +local skynet = require "skynet" +local socket = require "socket" +local crypt = require "crypt" +local datacenter = require "datacenter" + +local function launch_slave(auth_handler) + local cmd = {} + + -- set socket buffer limit (8K) + -- If the attacker send large package, close the socket + socket.limit(8192) + + function cmd.auth(fd, addr) + skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) + socket.start(fd) + local challenge = crypt.randomkey() + socket.write(fd, crypt.base64encode(challenge).."\n") + + local handshake = assert(socket.readline(fd), "socket closed") + local clientkey = crypt.base64decode(handshake) + if #clientkey ~= 8 then + error "Invalid client key" + end + local serverkey = crypt.randomkey() + socket.write(fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n") + + local secret = crypt.dhsecret(clientkey, serverkey) + + local response = assert(socket.readline(fd), "socket closed") + local hmac = crypt.hmac64(challenge, secret) + + if hmac ~= crypt.base64decode(response) then + socket.write(fd, "400 Bad Request\n") + error "challenge failed" + end + + local etoken = assert(socket.readline(fd), "socket closed") + + local token = crypt.desdecode(secret, crypt.base64decode(etoken)) + + local ok, server, uid = pcall(auth_handler,token) + + socket.abandon(fd) + return ok, server, uid, secret + end + + skynet.dispatch("lua", function(_,_,command,...) + local f = assert(cmd[command]) + skynet.ret(skynet.pack(f(...))) + end) +end + +local function accept(conf, s, fd, addr) + local ok, server, uid, secret = skynet.call(s, "lua", "auth", fd, addr) + if not ok then + socket.write(fd, "401 Unauthorized\n") + error(server) + end + + socket.start(fd) + + local ok, err = pcall(conf.login_handler, server, uid, secret) + if ok then + socket.write(fd, "200 OK\n") + else + socket.write(fd, "406 Not Acceptable\n") + error(err) + end +end + +local function launch_master(conf) + local instance = conf.instance or 8 + assert(instance > 0) + local host = conf.host or "0.0.0.0" + local port = assert(tonumber(conf.port)) + local slave = {} + local balance = 1 + local cmd = {} + + function cmd.logout(server, uid) + conf.logout_handler(server, uid) + end + + if conf.init then + conf.init() + conf.init = nil + end + + for i=1,instance do + slave[i] = skynet.newservice(SERVICE_NAME) + end + + skynet.dispatch("lua", function(_,_,command, ...) + local f = assert(cmd[command]) + skynet.ret(skynet.pack(f(...))) + end) + + local id = socket.listen(host, port) + skynet.error(string.format("login server listen at : %s %d", host, port)) + socket.start(id , function(fd, addr) + local s = slave[balance] + balance = balance + 1 + if balance > instance then + balance = 1 + end + local ok, err = pcall(accept, conf, s, fd, addr) + if not ok then + skynet.error(string.format("invalid client (fd = %d) error = %s", fd, err)) + end + socket.close(fd) + end) +end + +local function login (conf) + skynet.start(function() + local loginmaster = datacenter.get "login_master" + if loginmaster then + local auth_handler = assert(conf.auth_handler) + launch_master = nil + conf = nil + launch_slave(auth_handler) + else + launch_slave = nil + conf.auth_handler = nil + assert(conf.login_handler) + assert(conf.logout_handler) + datacenter.set("login_master", skynet.self()) + launch_master(conf) + end + end) +end + +return login From 04cb72d1a882475161acce7d23acef250a5550a7 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 11:50:30 +0800 Subject: [PATCH 03/37] config.name --- examples/login/logind.lua | 1 + lualib/loginserver.lua | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/login/logind.lua b/examples/login/logind.lua index 3c8a3835..39a60186 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -5,6 +5,7 @@ local crypt = require "crypt" local server = { host = "127.0.0.1", port = 8001, + name = "login_master", } function server.auth_handler(token) diff --git a/lualib/loginserver.lua b/lualib/loginserver.lua index 658ae1cd..1fff5a66 100644 --- a/lualib/loginserver.lua +++ b/lualib/loginserver.lua @@ -112,8 +112,9 @@ local function launch_master(conf) end local function login (conf) + local name = conf.name or "login_master" skynet.start(function() - local loginmaster = datacenter.get "login_master" + local loginmaster = datacenter.get(name) if loginmaster then local auth_handler = assert(conf.auth_handler) launch_master = nil From e1674f04c3364b6865b8774335b7dc638ca648aa Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 14:54:53 +0800 Subject: [PATCH 04/37] add gateserver --- examples/main.lua | 1 + examples/watchdog.lua | 1 - service/gate.lua | 170 ++++++++++++++---------------------------- 3 files changed, 58 insertions(+), 114 deletions(-) diff --git a/examples/main.lua b/examples/main.lua index abbd2751..4a98d2eb 100644 --- a/examples/main.lua +++ b/examples/main.lua @@ -11,6 +11,7 @@ skynet.start(function() skynet.call(watchdog, "lua", "start", { port = 8888, maxclient = max_client, + nodelay = true, }) print("Watchdog listen on ", 8888) diff --git a/examples/watchdog.lua b/examples/watchdog.lua index e28ab9fb..8ead2253 100644 --- a/examples/watchdog.lua +++ b/examples/watchdog.lua @@ -33,7 +33,6 @@ function SOCKET.data(fd, msg) end function CMD.start(conf) - skynet.call(gate, "lua", "nodelay", true) skynet.call(gate, "lua", "open" , conf) end diff --git a/service/gate.lua b/service/gate.lua index 772f135a..7758175f 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -1,39 +1,41 @@ local skynet = require "skynet" +local gateserver = require "gateserver" local netpack = require "netpack" -local socketdriver = require "socketdriver" -local socket -local queue local watchdog -local maxclient -local client_number = 0 -local CMD = setmetatable({}, { __gc = function() netpack.clear(queue) end }) -local nodelay = false - local connection = {} -- fd -> connection : { fd , client, agent , ip, mode } local forwarding = {} -- agent -> connection -function CMD.open( source , conf ) - assert(not socket) - local address = conf.address or "0.0.0.0" - local port = assert(conf.port) - maxclient = conf.maxclient or 1024 +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, +} + +local handler = {} + +function handler.open(source, conf) watchdog = conf.watchdog or source - socket = socketdriver.listen(address, port) - socketdriver.start(socket) + maxclient = conf.maxclient or 1024 end -function CMD.nodelay(source, v) - if v ~= false then - v = true +function handler.message(fd, msg, sz) + -- recv a package, forward it + local c = connection[fd] + local agent = c.agent + if agent then + skynet.redirect(agent, c.client, "client", 0, msg, sz) + else + skynet.send(watchdog, "lua", "socket", "data", fd, netpack.tostring(msg, sz)) end - nodelay = v end -function CMD.close() - assert(socket) - socketdriver.close(socket) - socket = nil +function handler.connect(fd, addr) + local c = { + fd = fd, + ip = msg, + } + connection[fd] = c + skynet.send(watchdog, "lua", "socket", "open", fd, addr) end local function unforward(c) @@ -44,28 +46,47 @@ local function unforward(c) end end -local function start(c) - if not c.mode then - c.mode = "open" - socketdriver.start(c.fd) +local function close_fd(fd) + local c = connection[fd] + if c then + unforward(c) + connection[fd] = nil + end +end + +function handler.close(fd) + close_fd(fd) + skynet.send(watchdog, "lua", "socket", "close", fd) +end + +function handler.error(fd, msg) + close_fd(fd) + skynet.send(watchdog, "lua", "socket", "error", fd, msg) +end + +local CMD = {} + +local function unforward(c) + if c.agent then + forwarding[c.agent] = nil + c.agent = nil + c.client = nil end end function CMD.forward(source, fd, client, address) local c = assert(connection[fd]) unforward(c) - start(c) - c.client = client or 0 c.agent = address or source - forwarding[c.agent] = c + gateserver.openclient(fd) end function CMD.accept(source, fd) local c = assert(connection[fd]) unforward(c) - start(c) + gateserver.openclient(fd) end function CMD.kick(source, fd) @@ -78,89 +99,12 @@ function CMD.kick(source, fd) assert(c) - if c.mode ~= "close" then - c.mode = "close" - socketdriver.close(c.fd) - end + gateserver.closeclient(fd) end -local MSG = {} - -function MSG.data(fd, msg, sz) - -- recv a package, forward it - local c = connection[fd] - local agent = c.agent - if agent then - skynet.redirect(agent, c.client, "client", 0, msg, sz) - else - skynet.send(watchdog, "lua", "socket", "data", fd, netpack.tostring(msg, sz)) - end +function handler.command(cmd, source, ...) + local f = assert(CMD[cmd]) + return f(source, ...) end -function MSG.more() - for fd, msg, sz in netpack.pop, queue do - MSG.data(fd, msg, sz) - end -end - -function MSG.open(fd, msg) - if client_number >= maxclient then - socketdriver.close(fd) - return - end - local c = { - fd = fd, - ip = msg, - } - connection[fd] = c - client_number = client_number + 1 - if nodelay then - socketdriver.nodelay(fd) - end - skynet.send(watchdog, "lua", "socket", "open", fd, msg) -end - -local function close_fd(fd, message) - local c = connection[fd] - if c then - unforward(c) - connection[fd] = nil - client_number = client_number - 1 - end -end - -function MSG.close(fd) - close_fd(fd) - skynet.send(watchdog, "lua", "socket", "close", fd) -end - -function MSG.error(fd, msg) - close_fd(fd) - skynet.send(watchdog, "lua", "socket", "error", fd, msg) -end - -skynet.register_protocol { - name = "socket", - id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6 - unpack = function ( msg, sz ) - return netpack.filter( queue, msg, sz) - end, - dispatch = function (_, _, q, type, ...) - queue = q - if type then - MSG[type](...) - end - end -} - -skynet.register_protocol { - name = "client", - id = skynet.PTYPE_CLIENT, -} - -skynet.start(function() - skynet.dispatch("lua", function (_, address, cmd, ...) - local f = assert(CMD[cmd]) - skynet.ret(skynet.pack(f(address, ...))) - end) -end) +gateserver.start(handler) From 0cdd71c0c202e4e0994ce5e28354dc5ae91555b9 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 19:36:44 +0800 Subject: [PATCH 05/37] msggate example --- examples/login/client.lua | 57 +++++++++++ examples/login/logind.lua | 26 ++++- examples/login/main.lua | 9 +- examples/login/msgagent.lua | 27 ++++++ examples/login/msggate.lua | 187 ++++++++++++++++++++++++++++++++++++ lualib/gateserver.lua | 130 +++++++++++++++++++++++++ lualib/loginserver.lua | 36 +++---- service/gate.lua | 1 - 8 files changed, 448 insertions(+), 25 deletions(-) create mode 100644 examples/login/msgagent.lua create mode 100644 examples/login/msggate.lua create mode 100644 lualib/gateserver.lua diff --git a/examples/login/client.lua b/examples/login/client.lua index 36ee3e2a..85a8ab36 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -1,5 +1,18 @@ package.cpath = "luaclib/?.so" +--[[ Status code + +200 OK + +400 Bad Request (通常是登陆协议错误) +401 Unauthorized (通常是登陆服务器或游戏服务器验证错误) +403 Forbidden (通常是连接游戏服务器的 index 已经过期) +404 Not Found (通常是游戏服务器未获得登陆服务器的通知) +406 Not Acceptable (通常是登陆服务器转发游戏服务器拒绝登陆) +412 Precondition Failed (通常是遗漏了和游戏服务器前次通讯的请求) + +]] + local socket = require "clientsocket" local cjson = require "cjson" local crypt = require "crypt" @@ -43,6 +56,7 @@ local hmac = crypt.hmac64(challenge, secret) socket.writeline(fd, crypt.base64encode(hmac)) local token = { + server = "sample", user = "hello", pass = "password", } @@ -53,6 +67,49 @@ socket.writeline(fd, crypt.base64encode(etoken)) print(readline()) +socket.close(fd) + +----- connect to game server + +local input = {} +local fd = assert(socket.connect("127.0.0.1", 8888)) + +local function readpackage() + local line = table.remove(input, 1) + if line then + return line + end + + while true do + local status + status, last = socket.recv(fd, last, input) + if status == nil then + error "Server closed" + end + if not status then + socket.usleep(100) + else + local line = table.remove(input, 1) + if line then + return line + end + end + end +end + +local index = 0 +local request = 0 +local handshake = string.format("%s@%s:%d:%d", crypt.base64encode(token.user), crypt.base64encode(token.server) , index, request) +local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) + +socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) + +print(readpackage()) + +socket.send(fd , "echo") +print(readpackage()) + + diff --git a/examples/login/logind.lua b/examples/login/logind.lua index 39a60186..759a046e 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -1,6 +1,7 @@ local login = require "loginserver" local json = require "cjson" local crypt = require "crypt" +local skynet = require "skynet" local server = { host = "127.0.0.1", @@ -8,19 +9,40 @@ local server = { name = "login_master", } +local server_list = {} +local user_online = {} + function server.auth_handler(token) token = json.decode(token) assert(token.user) assert(token.pass == "password") - return "sample", token.user + return token.server, token.user end function server.login_handler(server, uid, secret) print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) + local u = user_online[uid] + if u then + local gameserver = server_list[u.server] + skynet.call(gameserver, "lua", "kick", server, uid) + end + local gameserver = assert(server_list[server]) + skynet.call(gameserver, "lua", "login", server, uid, secret) end -function server.logout_handler(server, uid) +local CMD = {} + +function CMD.register_gate(source, name) + server_list[name] = source +end + +function CMD.logout(source, uid, server) print(string.format("%s@%s is logout", uid, server)) end +function server.command_handler(command, source, ...) + local f = assert(CMD[command]) + return f(source, ...) +end + login(server) diff --git a/examples/login/main.lua b/examples/login/main.lua index 493b2af4..6359cb96 100644 --- a/examples/login/main.lua +++ b/examples/login/main.lua @@ -1,5 +1,12 @@ local skynet = require "skynet" skynet.start(function() - skynet.newservice "logind" + local loginserver = skynet.newservice "logind" + local gate = skynet.newservice "msggate" + skynet.call(gate, "lua", "open" , { + port = 8888, + maxclient = 64, + loginserver = loginserver, + servername = "sample", + }) end) diff --git a/examples/login/msgagent.lua b/examples/login/msgagent.lua new file mode 100644 index 00000000..30545c68 --- /dev/null +++ b/examples/login/msgagent.lua @@ -0,0 +1,27 @@ +local skynet = require "skynet" + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, + unpack = skynet.tostring, +} + +local gate + +local CMD = {} + +function CMD.init(source , uid, server) + gate = source + skynet.error(string.format("%s is coming", uid)) +end + +skynet.start(function() + skynet.dispatch("lua", function(_, source, command, ...) + local f = assert(CMD[command]) + skynet.ret(skynet.pack(f(source, ...))) + end) + + skynet.dispatch("client", function(_,_, msg) + skynet.ret(msg) + end) +end) diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua new file mode 100644 index 00000000..cdda670a --- /dev/null +++ b/examples/login/msggate.lua @@ -0,0 +1,187 @@ +local skynet = require "skynet" +local gateserver = require "gateserver" +local netpack = require "netpack" +local crypt = require "crypt" +local socketdriver = require "socketdriver" +local datacenter = require "datacenter" + +local users = {} + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, +} + +local handler = {} +local handshake = {} +local connection = {} +local agent = {} +local login_master + +-- launch an agent service to handle message from client +local function launch_agent(c) + local agent = assert(skynet.newservice "msgagent") + skynet.call(agent, "lua", "init", c.uid, c.server) + return agent +end + +function handler.connect(fd, addr) + skynet.error(string.format("new connection from %s (fd=%d)", addr, fd)) + handshake[fd] = true + gateserver.openclient(fd) +end + +local function auth(fd, msg, sz) +-- base64(base64(uid)@base64(server):index:request:base64(hmac) + local message = netpack.tostring(msg, sz) + local username, index, request , hmac = string.match(message, "([^:]*):([^:]*):([^:]*):([^:]*)") + local content = users[username] + if content == nil then + return "404 Not Found" + end + local idx = assert(tonumber(index)) + local req = assert(tonumber(request)) + hmac = crypt.base64decode(hmac) + + if idx < content.index then + return "403 Forbidden" + end + if req > content.request or req < content.reserved then + return "412 Precondition Failed" + end + + local text = string.format("%s:%s:%s", username, index, request) + local v = crypt.hmac64(crypt.hashkey(text), content.secret) + if v ~= hmac then + return "401 Unauthorized" + end + + content.index = idx + for i=content.reserved, request do + content.response[i] = nil + end + content.reserved = request + content.reqest = request + connection[fd] = content + + if content.fd then + local last_fd = content.fd + connection[last_fd] = nil + gateserver.closeclient(last_fd) + end + content.fd = fd + + if content.agent == nil then + content.agent = true + local ok, agent = pcall(launch_agent, content) + if ok then + content.agent = agent + else + content.agent = nil + skynet.error(string.format("Launch agent %s failed : %s", content.uid, agent)) + connection[fd] = nil + gateserver.closeclient(fd) + end + end +end + +function handler.message(fd, msg, sz) + if handshake[fd] then + local ok, result = pcall(auth, fd, msg, sz) + if not ok then + result = "400 Bad Request" + end + + local close = result ~= nil + + if result == nil then + result = "200 OK" + end + + socketdriver.send(fd, netpack.pack(result)) + + if close then + gateserver.closeclient(fd) + end + handshake[fd] = nil + else + local c = connection[fd] + if c == nil or c.agent == nil then + local message = netpack.tostring(msg, sz) + skynet.error(string.format("Unknown fd = %d, message (%s) size = %d", fd, crypt.hexencode(message):sub(1,80), #message)) + return + end + c.request = c.request + 1 + local ret = c.response[c.request] + if ret == nil then + local ok, msg, sz = pcall(skynet.rawcall, c.agent, "client", msg, sz) + if ok then + ret = netpack.pack_string(msg, sz) + else + skynet.error(string.format("%s request error : %s", c.username, msg)) + ret = netpack.pack_string "" + end + c.response[c.request] = ret + end + socketdriver.send(c.fd, ret) + end +end + +function handler.close(fd) + handshake[fd] = nil + local c = connection[fd] + if c then + c.fd = nil + end +end + +handler.error = handler.close + +function handler.open(source, conf) + login_master = assert(conf.loginserver) + local servername = assert(conf.servername) + skynet.call(login_master, "lua", "register_gate", servername) +end + + +local CMD = {} + +function CMD.login(source, server, uid, secret) + local username = crypt.base64encode(uid) .. '@' .. crypt.base64encode(server) + users[username] = { + server = server, + uid = uid, + secret = secret, + index = 0, + request = 0, + reserved = 0, + response = {}, + } +end + +function CMD.logout(source) + local c = agent[source] + if c then + skynet.call(login_master, "lua", "logout", c.server, c.uid) + if c.fd then + gateserver.closeclient(c.fd) + end + end +end + +function CMD.kick(source, server, uid) + local username = crypt.base64encode(uid) .. '@' .. crypt.base64encode(server) + local u = users[username] + if u and u.agent then + skynet.call(u.agent, "logout") + skynet.kill(u.agent) + u.agent = nil + end +end + +function handler.command(cmd, source, ...) + local f = assert(CMD[cmd]) + return f(source, ...) +end + +gateserver.start(handler) diff --git a/lualib/gateserver.lua b/lualib/gateserver.lua new file mode 100644 index 00000000..84f33f88 --- /dev/null +++ b/lualib/gateserver.lua @@ -0,0 +1,130 @@ +local skynet = require "skynet" +local netpack = require "netpack" +local socketdriver = require "socketdriver" + +local gateserver = {} + +local socket -- listen socket +local queue -- message queue +local maxclient -- max client +local client_number = 0 +local CMD = setmetatable({}, { __gc = function() netpack.clear(queue) end }) +local nodelay = false + +local connection = {} + +function gateserver.openclient(fd) + if connection[fd] then + socketdriver.start(fd) + end +end + +function gateserver.closeclient(fd) + local c = connection[fd] + if c then + connection[fd] = false + socketdriver.close(fd) + end +end + +function gateserver.start(handler) + assert(handler.message) + assert(handler.connect) + + function CMD.open( source, conf ) + assert(not socket) + local address = conf.address or "0.0.0.0" + local port = assert(conf.port) + maxclient = conf.maxclient or 1024 + nodelay = conf.nodelay + socket = socketdriver.listen(address, port) + socketdriver.start(socket) + if handler.open then + handler.open(source, conf) + end + end + + function CMD.close() + assert(socket) + socketdriver.close(socket) + socket = nil + end + + local MSG = {} + + function MSG.data(fd, msg, sz) + if connection[fd] then + handler.message(fd, msg, sz) + end + end + + function MSG.more() + for fd, msg, sz in netpack.pop, queue do + if connection[fd] then + handler.message(fd, msg, sz) + end + end + end + + function MSG.open(fd, msg) + if client_number >= maxclient then + socketdriver.close(fd) + return + end + if nodelay then + socketdriver.nodelay(fd) + end + connection[fd] = true + client_number = client_number + 1 + handler.connect(fd, msg) + end + + local function close_fd(fd) + local c = connection[fd] + if c ~= nil then + connection[fd] = nil + client_number = client_number - 1 + end + end + + function MSG.close(fd) + if handler.close then + handler.close(fd) + end + close_fd(fd) + end + + function MSG.error(fd, msg) + if handler.error then + handler.error(fd) + end + close_fd(fd) + end + + skynet.register_protocol { + name = "socket", + id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6 + unpack = function ( msg, sz ) + return netpack.filter( queue, msg, sz) + end, + dispatch = function (_, _, q, type, ...) + queue = q + if type then + MSG[type](...) + end + end + } + + skynet.start(function() + skynet.dispatch("lua", function (_, address, cmd, ...) + local f = CMD[cmd] + if f then + skynet.ret(skynet.pack(f(address, ...))) + else + skynet.ret(skynet.pack(handler.command(cmd, address, ...))) + end + end) + end) +end + +return gateserver \ No newline at end of file diff --git a/lualib/loginserver.lua b/lualib/loginserver.lua index 1fff5a66..7f022508 100644 --- a/lualib/loginserver.lua +++ b/lualib/loginserver.lua @@ -1,7 +1,6 @@ local skynet = require "skynet" local socket = require "socket" local crypt = require "crypt" -local datacenter = require "datacenter" local function launch_slave(auth_handler) local cmd = {} @@ -75,32 +74,26 @@ local function launch_master(conf) local port = assert(tonumber(conf.port)) local slave = {} local balance = 1 - local cmd = {} - function cmd.logout(server, uid) - conf.logout_handler(server, uid) - end - - if conf.init then - conf.init() - conf.init = nil - end + skynet.dispatch("lua", function(_,source,command, ...) + if command == "register_slave" then + table.insert(slave, source) + skynet.ret(skynet.pack(nil)) + else + skynet.ret(skynet.pack(conf.command_handler(command, source, ...))) + end + end) for i=1,instance do - slave[i] = skynet.newservice(SERVICE_NAME) + skynet.newservice(SERVICE_NAME) end - skynet.dispatch("lua", function(_,_,command, ...) - local f = assert(cmd[command]) - skynet.ret(skynet.pack(f(...))) - end) - local id = socket.listen(host, port) skynet.error(string.format("login server listen at : %s %d", host, port)) socket.start(id , function(fd, addr) local s = slave[balance] balance = balance + 1 - if balance > instance then + if balance > #slave then balance = 1 end local ok, err = pcall(accept, conf, s, fd, addr) @@ -112,10 +105,11 @@ local function launch_master(conf) end local function login (conf) - local name = conf.name or "login_master" + local name = "." .. (conf.name or "login") skynet.start(function() - local loginmaster = datacenter.get(name) + local loginmaster = skynet.localname(name) if loginmaster then + skynet.call(loginmaster, "lua", "register_slave") local auth_handler = assert(conf.auth_handler) launch_master = nil conf = nil @@ -124,8 +118,8 @@ local function login (conf) launch_slave = nil conf.auth_handler = nil assert(conf.login_handler) - assert(conf.logout_handler) - datacenter.set("login_master", skynet.self()) + assert(conf.command_handler) + skynet.register(name) launch_master(conf) end end) diff --git a/service/gate.lua b/service/gate.lua index 7758175f..665be9d0 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -15,7 +15,6 @@ local handler = {} function handler.open(source, conf) watchdog = conf.watchdog or source - maxclient = conf.maxclient or 1024 end function handler.message(fd, msg, sz) From 3611bfe1afa6afe90342ceee7e767349fdf51986 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 19:44:23 +0800 Subject: [PATCH 06/37] create gamefw --- examples/login/logind.lua | 2 +- examples/login/msggate.lua | 2 +- lualib/{ => gamefw}/gateserver.lua | 0 lualib/{ => gamefw}/loginserver.lua | 0 service/gate.lua | 2 +- 5 files changed, 3 insertions(+), 3 deletions(-) rename lualib/{ => gamefw}/gateserver.lua (100%) rename lualib/{ => gamefw}/loginserver.lua (100%) diff --git a/examples/login/logind.lua b/examples/login/logind.lua index 759a046e..2ff79c48 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -1,4 +1,4 @@ -local login = require "loginserver" +local login = require "gamefw.loginserver" local json = require "cjson" local crypt = require "crypt" local skynet = require "skynet" diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua index cdda670a..85c3d4af 100644 --- a/examples/login/msggate.lua +++ b/examples/login/msggate.lua @@ -1,5 +1,5 @@ local skynet = require "skynet" -local gateserver = require "gateserver" +local gateserver = require "gamefw.gateserver" local netpack = require "netpack" local crypt = require "crypt" local socketdriver = require "socketdriver" diff --git a/lualib/gateserver.lua b/lualib/gamefw/gateserver.lua similarity index 100% rename from lualib/gateserver.lua rename to lualib/gamefw/gateserver.lua diff --git a/lualib/loginserver.lua b/lualib/gamefw/loginserver.lua similarity index 100% rename from lualib/loginserver.lua rename to lualib/gamefw/loginserver.lua diff --git a/service/gate.lua b/service/gate.lua index 665be9d0..e1f6f1d8 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -1,5 +1,5 @@ local skynet = require "skynet" -local gateserver = require "gateserver" +local gateserver = require "gamefw.gateserver" local netpack = require "netpack" local watchdog From 0d1c3a64d17eb41b55cd4b8ff31499f612efe88a Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 21:02:23 +0800 Subject: [PATCH 07/37] bugfix --- examples/login/client.lua | 10 ++++++-- examples/login/logind.lua | 44 ++++++++++++++++++++++++----------- examples/login/msggate.lua | 4 ++-- lualib-src/lua-crypt.c | 2 +- lualib/gamefw/loginserver.lua | 38 ++++++++++++++++-------------- 5 files changed, 63 insertions(+), 35 deletions(-) diff --git a/examples/login/client.lua b/examples/login/client.lua index 85a8ab36..7a72a036 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -14,7 +14,6 @@ package.cpath = "luaclib/?.so" ]] local socket = require "clientsocket" -local cjson = require "cjson" local crypt = require "crypt" local last @@ -61,7 +60,14 @@ local token = { pass = "password", } -local etoken = crypt.desencode(secret, cjson.encode(token)) +local function encode_token(token) + return string.format("%s@%s:%s", + crypt.base64encode(token.user), + crypt.base64encode(token.server), + crypt.base64encode(token.pass)) +end + +local etoken = crypt.desencode(secret, encode_token(token)) local b = crypt.base64encode(etoken) socket.writeline(fd, crypt.base64encode(etoken)) diff --git a/examples/login/logind.lua b/examples/login/logind.lua index 2ff79c48..b28c6721 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -1,5 +1,4 @@ local login = require "gamefw.loginserver" -local json = require "cjson" local crypt = require "crypt" local skynet = require "skynet" @@ -12,32 +11,51 @@ local server = { local server_list = {} local user_online = {} +local server_mt = {} +server_mt.__index = server_mt + +function server_mt:kick(uid) + skynet.call(self.address, "lua", "kick", self.name, uid) +end + +function server_mt:login(uid, secret) + skynet.call(self.address, "lua", "login", self.name, uid, secret) +end + function server.auth_handler(token) - token = json.decode(token) - assert(token.user) - assert(token.pass == "password") - return token.server, token.user + -- the token is base64(user)@base64(server):base64(password) + local user, server, password = token:match("([^@]+)@([^:]+):(.+)") + user = crypt.base64decode(user) + server = crypt.base64decode(server) + password = crypt.base64decode(password) + assert(password == "password") + return server, user end function server.login_handler(server, uid, secret) print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) local u = user_online[uid] if u then - local gameserver = server_list[u.server] - skynet.call(gameserver, "lua", "kick", server, uid) + u:kick(uid) end - local gameserver = assert(server_list[server]) - skynet.call(gameserver, "lua", "login", server, uid, secret) + assert(user_online[uid] == nil, "kick failed") + local gameserver = assert(server_list[server], "Unknown server") + gameserver:login(uid, secret) + user_online[uid] = gameserver end local CMD = {} -function CMD.register_gate(source, name) - server_list[name] = source +function CMD.register_gate(server, address) + server_list[server] = setmetatable( { name = server, address = address }, server_mt ) end -function CMD.logout(source, uid, server) - print(string.format("%s@%s is logout", uid, server)) +function CMD.logout(uid) + local u = user_online[uid] + if u then + print(string.format("%s@%s is logout", uid, u.name)) + user_online[uid] = nil + end end function server.command_handler(command, source, ...) diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua index 85c3d4af..fb74ffc2 100644 --- a/examples/login/msggate.lua +++ b/examples/login/msggate.lua @@ -140,7 +140,7 @@ handler.error = handler.close function handler.open(source, conf) login_master = assert(conf.loginserver) local servername = assert(conf.servername) - skynet.call(login_master, "lua", "register_gate", servername) + skynet.call(login_master, "lua", "register_gate", servername, skynet.self()) end @@ -162,7 +162,7 @@ end function CMD.logout(source) local c = agent[source] if c then - skynet.call(login_master, "lua", "logout", c.server, c.uid) + skynet.call(login_master, "lua", "logout", c.uid) if c.fd then gateserver.closeclient(c.fd) end diff --git a/lualib-src/lua-crypt.c b/lualib-src/lua-crypt.c index 34a190e7..5d2f12f9 100644 --- a/lualib-src/lua-crypt.c +++ b/lualib-src/lua-crypt.c @@ -729,7 +729,7 @@ lb64encode(lua_State *L) { } int i,j; j=0; - for (i=0;i> 18]; buffer[j+1] = encoding[(v >> 12) & 0x3f]; diff --git a/lualib/gamefw/loginserver.lua b/lualib/gamefw/loginserver.lua index 7f022508..a7384105 100644 --- a/lualib/gamefw/loginserver.lua +++ b/lualib/gamefw/loginserver.lua @@ -2,18 +2,21 @@ local skynet = require "skynet" local socket = require "socket" local crypt = require "crypt" -local function launch_slave(auth_handler) - local cmd = {} +local function write(fd, text) + assert(socket.write(fd, text), "socket error") +end +local function launch_slave(auth_handler) -- set socket buffer limit (8K) -- If the attacker send large package, close the socket socket.limit(8192) - function cmd.auth(fd, addr) + local function auth(fd, addr) + fd = assert(tonumber(fd)) skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) socket.start(fd) local challenge = crypt.randomkey() - socket.write(fd, crypt.base64encode(challenge).."\n") + write(fd, crypt.base64encode(challenge).."\n") local handshake = assert(socket.readline(fd), "socket closed") local clientkey = crypt.base64decode(handshake) @@ -21,7 +24,7 @@ local function launch_slave(auth_handler) error "Invalid client key" end local serverkey = crypt.randomkey() - socket.write(fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n") + write(fd, crypt.base64encode(crypt.dhexchange(serverkey)).."\n") local secret = crypt.dhsecret(clientkey, serverkey) @@ -29,7 +32,7 @@ local function launch_slave(auth_handler) local hmac = crypt.hmac64(challenge, secret) if hmac ~= crypt.base64decode(response) then - socket.write(fd, "400 Bad Request\n") + write(fd, "400 Bad Request\n") error "challenge failed" end @@ -43,26 +46,27 @@ local function launch_slave(auth_handler) return ok, server, uid, secret end - skynet.dispatch("lua", function(_,_,command,...) - local f = assert(cmd[command]) - skynet.ret(skynet.pack(f(...))) + skynet.dispatch("lua", function(_,_,...) + skynet.ret(skynet.pack(auth(...))) end) end local function accept(conf, s, fd, addr) - local ok, server, uid, secret = skynet.call(s, "lua", "auth", fd, addr) + -- call slave auth + local ok, server, uid, secret = skynet.call(s, "lua", fd, addr) + socket.start(fd) + if not ok then - socket.write(fd, "401 Unauthorized\n") + write(fd, "401 Unauthorized\n") error(server) end - socket.start(fd) - local ok, err = pcall(conf.login_handler, server, uid, secret) if ok then - socket.write(fd, "200 OK\n") + err = err or "" + write(fd, "200 "..crypt.base64encode(err).."\n") else - socket.write(fd, "406 Not Acceptable\n") + write(fd, "406 Not Acceptable\n") error(err) end end @@ -80,7 +84,7 @@ local function launch_master(conf) table.insert(slave, source) skynet.ret(skynet.pack(nil)) else - skynet.ret(skynet.pack(conf.command_handler(command, source, ...))) + skynet.ret(skynet.pack(conf.command_handler(command, ...))) end end) @@ -104,7 +108,7 @@ local function launch_master(conf) end) end -local function login (conf) +local function login(conf) local name = "." .. (conf.name or "login") skynet.start(function() local loginmaster = skynet.localname(name) From ad58ed8a2692b45351a7f0e16dffbf1509296df0 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sun, 13 Jul 2014 21:25:44 +0800 Subject: [PATCH 08/37] add subid --- examples/login/client.lua | 9 ++++++--- examples/login/logind.lua | 7 ++++--- examples/login/msggate.lua | 6 ++++-- 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/examples/login/client.lua b/examples/login/client.lua index 7a72a036..537f614b 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -71,10 +71,13 @@ local etoken = crypt.desencode(secret, encode_token(token)) local b = crypt.base64encode(etoken) socket.writeline(fd, crypt.base64encode(etoken)) -print(readline()) - +local result = readline() +local code = tonumber(string.sub(result, 1, 3)) +assert(code == 200) socket.close(fd) +local subid = crypt.base64decode(string.sub(result, 5)) + ----- connect to game server local input = {} @@ -105,7 +108,7 @@ end local index = 0 local request = 0 -local handshake = string.format("%s@%s:%d:%d", crypt.base64encode(token.user), crypt.base64encode(token.server) , index, request) +local handshake = string.format("%s#%s@%s:%d:%d", crypt.base64encode(token.user), crypt.base64encode(subid),crypt.base64encode(token.server) , index, request) local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) diff --git a/examples/login/logind.lua b/examples/login/logind.lua index b28c6721..df7f5fd8 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -15,11 +15,11 @@ local server_mt = {} server_mt.__index = server_mt function server_mt:kick(uid) - skynet.call(self.address, "lua", "kick", self.name, uid) + return skynet.call(self.address, "lua", "kick", self.name, uid) end function server_mt:login(uid, secret) - skynet.call(self.address, "lua", "login", self.name, uid, secret) + return skynet.call(self.address, "lua", "login", self.name, uid, secret) end function server.auth_handler(token) @@ -40,8 +40,9 @@ function server.login_handler(server, uid, secret) end assert(user_online[uid] == nil, "kick failed") local gameserver = assert(server_list[server], "Unknown server") - gameserver:login(uid, secret) + local subid = gameserver:login(uid, secret) user_online[uid] = gameserver + return tostring(subid) end local CMD = {} diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua index fb74ffc2..a7315f4f 100644 --- a/examples/login/msggate.lua +++ b/examples/login/msggate.lua @@ -32,7 +32,7 @@ function handler.connect(fd, addr) end local function auth(fd, msg, sz) --- base64(base64(uid)@base64(server):index:request:base64(hmac) +-- base64(base64(uid)@base64(server)#base64(subid):index:request:base64(hmac) local message = netpack.tostring(msg, sz) local username, index, request , hmac = string.match(message, "([^:]*):([^:]*):([^:]*):([^:]*)") local content = users[username] @@ -147,7 +147,8 @@ end local CMD = {} function CMD.login(source, server, uid, secret) - local username = crypt.base64encode(uid) .. '@' .. crypt.base64encode(server) + local subid = "1" + local username = crypt.base64encode(uid) .. '#'..crypt.base64encode(subid)..'@' .. crypt.base64encode(server) users[username] = { server = server, uid = uid, @@ -157,6 +158,7 @@ function CMD.login(source, server, uid, secret) reserved = 0, response = {}, } + return subid end function CMD.logout(source) From 9feaf15b3d982259236bab27c481d4720f0f23b8 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Mon, 14 Jul 2014 14:14:05 +0800 Subject: [PATCH 09/37] add netpack.pack_padding --- examples/login/msggate.lua | 2 +- lualib-src/lua-netpack.c | 43 +++++++++++++++++++++++++++++++------- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua index a7315f4f..32b3518f 100644 --- a/examples/login/msggate.lua +++ b/examples/login/msggate.lua @@ -32,7 +32,7 @@ function handler.connect(fd, addr) end local function auth(fd, msg, sz) --- base64(base64(uid)@base64(server)#base64(subid):index:request:base64(hmac) +-- base64(uid)@base64(server)#base64(subid):index:request:base64(hmac) local message = netpack.tostring(msg, sz) local username, index, request , hmac = string.match(message, "([^:]*):([^:]*):([^:]*):([^:]*)") local content = users[username] diff --git a/lualib-src/lua-netpack.c b/lualib-src/lua-netpack.c index a95ed4d7..432818e7 100644 --- a/lualib-src/lua-netpack.c +++ b/lualib-src/lua-netpack.c @@ -393,13 +393,13 @@ lpop(lua_State *L) { */ static const char * -tolstring(lua_State *L, size_t *sz) { +tolstring(lua_State *L, size_t *sz, int index) { const char * ptr; - if (lua_isuserdata(L,1)) { - ptr = (const char *)lua_touserdata(L,1); - *sz = (size_t)luaL_checkinteger(L, 2); + if (lua_isuserdata(L,index)) { + ptr = (const char *)lua_touserdata(L,index); + *sz = (size_t)luaL_checkinteger(L, index+1); } else { - ptr = luaL_checklstring(L, 1, sz); + ptr = luaL_checklstring(L, index, sz); } return ptr; } @@ -413,7 +413,7 @@ write_size(uint8_t * buffer, int len) { static int lpack(lua_State *L) { size_t len; - const char * ptr = tolstring(L, &len); + const char * ptr = tolstring(L, &len, 1); if (len > 0x10000) { return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); } @@ -433,7 +433,7 @@ lpack_string(lua_State *L) { uint8_t tmp[SMALLSTRING+2]; size_t len; uint8_t *buffer; - const char * ptr = tolstring(L, &len); + const char * ptr = tolstring(L, &len, 1); if (len > 0x10000) { return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); } @@ -451,6 +451,34 @@ lpack_string(lua_State *L) { return 1; } +static int +lpack_padding(lua_State *L) { + uint8_t tmp[SMALLSTRING+2]; + size_t content_sz; + uint8_t *buffer; + const char * ptr = tolstring(L, &content_sz, 2); + size_t header_sz = 0; + const char * header = luaL_checklstring(L,1,&header_sz); + size_t len = header_sz + content_sz; + + if (len > 0x10000) { + return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); + } + + if (len <= SMALLSTRING) { + buffer = tmp; + } else { + buffer = lua_newuserdata(L, len + 2); + } + + write_size(buffer, len); + memcpy(buffer+2, header, header_sz); + memcpy(buffer+2+header_sz, ptr, content_sz); + lua_pushlstring(L, (const char *)buffer, len+2); + + return 1; +} + static int ltostring(lua_State *L) { void * ptr = lua_touserdata(L, 1); @@ -471,6 +499,7 @@ luaopen_netpack(lua_State *L) { { "pop", lpop }, { "pack", lpack }, { "pack_string", lpack_string }, + { "pack_padding", lpack_padding }, { "clear", lclear }, { "tostring", ltostring }, { NULL, NULL }, From dbf492b76192e2d83595c7c27692594d0d0feeb2 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Tue, 15 Jul 2014 12:05:58 +0800 Subject: [PATCH 10/37] add snax.gateserver snax.loginserver snax.msgserver --- HISTORY.md | 5 + examples/login/client.lua | 73 +++--- examples/login/gated.lua | 88 +++++++ examples/login/logind.lua | 41 ++-- examples/login/main.lua | 6 +- examples/login/msgagent.lua | 31 ++- examples/login/msggate.lua | 189 --------------- lualib-src/lua-netpack.c | 25 +- lualib/skynet.lua | 7 +- lualib/{gamefw => snax}/gateserver.lua | 6 +- lualib/{gamefw => snax}/loginserver.lua | 47 +++- lualib/snax/msgserver.lua | 299 ++++++++++++++++++++++++ service/gate.lua | 13 +- 13 files changed, 562 insertions(+), 268 deletions(-) create mode 100644 examples/login/gated.lua delete mode 100644 examples/login/msggate.lua rename lualib/{gamefw => snax}/gateserver.lua (96%) rename lualib/{gamefw => snax}/loginserver.lua (74%) create mode 100644 lualib/snax/msgserver.lua diff --git a/HISTORY.md b/HISTORY.md index 5926bc02..5c52c767 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,8 @@ +Dev version +----------- +* skynet.exit will quit service immediately. +* Add snax.gateserver, snax.loginserver, snax.msgserver + v0.4.2 (2014-7-14) ----------- * Bugfix : invalid negative socket id diff --git a/examples/login/client.lua b/examples/login/client.lua index 537f614b..82b0a182 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -1,18 +1,5 @@ package.cpath = "luaclib/?.so" ---[[ Status code - -200 OK - -400 Bad Request (通常是登陆协议错误) -401 Unauthorized (通常是登陆服务器或游戏服务器验证错误) -403 Forbidden (通常是连接游戏服务器的 index 已经过期) -404 Not Found (通常是游戏服务器未获得登陆服务器的通知) -406 Not Acceptable (通常是登陆服务器转发游戏服务器拒绝登陆) -412 Precondition Failed (通常是遗漏了和游戏服务器前次通讯的请求) - -]] - local socket = require "clientsocket" local crypt = require "crypt" @@ -72,16 +59,38 @@ local b = crypt.base64encode(etoken) socket.writeline(fd, crypt.base64encode(etoken)) local result = readline() +print(result) local code = tonumber(string.sub(result, 1, 3)) assert(code == 200) socket.close(fd) local subid = crypt.base64decode(string.sub(result, 5)) +print("login ok, subid=", subid) + ----- connect to game server +local session = 0 + +local function send_request(v) + session = session + 1 + local s = string.char(bit32.extract(session,24,8), bit32.extract(session,16,8), bit32.extract(session,8,8), bit32.extract(session,0,8)) + socket.send(fd , v..s) + return session, v +end + +local function recv_response(v) + local content = v:sub(1,-6) + local ok = v:sub(-5,-5):byte() + local session = 0 + for i=-4,-1 do + local c = v:byte(i) + session = session + bit32.lshift(c,(-1-i) * 8) + end + return ok ~=0 , session, content +end + local input = {} -local fd = assert(socket.connect("127.0.0.1", 8888)) local function readpackage() local line = table.remove(input, 1) @@ -106,20 +115,32 @@ local function readpackage() end end -local index = 0 -local request = 0 -local handshake = string.format("%s#%s@%s:%d:%d", crypt.base64encode(token.user), crypt.base64encode(subid),crypt.base64encode(token.server) , index, request) -local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) - -socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) - -print(readpackage()) - -socket.send(fd , "echo") -print(readpackage()) - +local index = 1 +local function echo(text) + print("connect") + local fd = assert(socket.connect("127.0.0.1", 8888)) + input = {} + local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) + local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) + socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) + + print(readpackage()) + print("===>",send_request(text)) + print("===>",send_request(text .. " again")) + print("<===",recv_response(readpackage())) + print("<===",recv_response(readpackage())) + + print("disconnect") + socket.close(fd) +end + +echo "hello" + +index = index + 1 + +echo "world" diff --git a/examples/login/gated.lua b/examples/login/gated.lua new file mode 100644 index 00000000..aeaa2013 --- /dev/null +++ b/examples/login/gated.lua @@ -0,0 +1,88 @@ +local msgserver = require "snax.msgserver" +local crypt = require "crypt" +local skynet = require "skynet" + +local loginservice = tonumber(...) + +local server = {} +local users = {} +local username_map = {} +local internal_id = 0 + +-- login server disallow multi login, so login_handler never be reentry +-- call by login server +function server.login_handler(uid, secret) + if users[uid] then + error(string.format("%s is already login", uid)) + end + + internal_id = internal_id + 1 + local username = msgserver.username(uid, internal_id, servername) + + -- you can use a pool to alloc new agent + local agent = skynet.newservice "msgagent" + local u = { + username = username, + agent = agent, + uid = uid, + subid = internal_id, + } + + -- trash subid (no used) + skynet.call(agent, "lua", "login", uid, internal_id, secret) + + users[uid] = u + username_map[username] = u + + msgserver.login(username, secret) + + -- you should return unique subid + return internal_id +end + +-- call by agent +function server.logout_handler(uid, subid) + local u = users[uid] + if u then + local username = msgserver.username(uid, subid, servername) + assert(u.username == username) + msgserver.logout(u.username) + users[uid] = nil + username_map[u.username] = nil + skynet.call(loginservice, "lua", "logout",uid, subid) + end +end + +-- call by login server +function server.kick_handler(uid, subid) + local u = users[uid] + if u then + local username = msgserver.username(uid, subid, servername) + assert(u.username == username) + -- NOTICE: logout may call skynet.exit, so you should use pcall. + pcall(skynet.call, u.agent, "lua", "logout") + end +end + +-- call by self (when socket disconnect) +function server.disconnect_handler(username) + local u = username_map[username] + if u then + skynet.call(u.agent, "lua", "afk") + end +end + +-- call by self (when recv a request from client) +function server.request_handler(username, msg, sz) + local u = username_map[username] + return skynet.tostring(skynet.rawcall(u.agent, "client", msg, sz)) +end + +-- call by self (when gate open) +function server.register_handler(name) + servername = name + skynet.call(loginservice, "lua", "register_gate", servername, skynet.self()) +end + +msgserver.start(server) + diff --git a/examples/login/logind.lua b/examples/login/logind.lua index df7f5fd8..e6e4c10f 100644 --- a/examples/login/logind.lua +++ b/examples/login/logind.lua @@ -1,26 +1,17 @@ -local login = require "gamefw.loginserver" +local login = require "snax.loginserver" local crypt = require "crypt" local skynet = require "skynet" local server = { host = "127.0.0.1", port = 8001, + multilogin = false, -- disallow multilogin name = "login_master", } local server_list = {} local user_online = {} - -local server_mt = {} -server_mt.__index = server_mt - -function server_mt:kick(uid) - return skynet.call(self.address, "lua", "kick", self.name, uid) -end - -function server_mt:login(uid, secret) - return skynet.call(self.address, "lua", "login", self.name, uid, secret) -end +local user_login = {} function server.auth_handler(token) -- the token is base64(user)@base64(server):base64(password) @@ -34,27 +25,31 @@ end function server.login_handler(server, uid, secret) print(string.format("%s@%s is login, secret is %s", uid, server, crypt.hexencode(secret))) - local u = user_online[uid] - if u then - u:kick(uid) - end - assert(user_online[uid] == nil, "kick failed") local gameserver = assert(server_list[server], "Unknown server") - local subid = gameserver:login(uid, secret) - user_online[uid] = gameserver - return tostring(subid) + -- only one can login, because disallow multilogin + local last = user_online[uid] + if last then + skynet.call(last.address, "lua", "kick", uid, last.subid) + end + if user_online[uid] then + error(string.format("user %s is already online", uid)) + end + + local subid = tostring(skynet.call(gameserver, "lua", "login", uid, secret)) + user_online[uid] = { address = gameserver, subid = subid , server = server} + return subid end local CMD = {} function CMD.register_gate(server, address) - server_list[server] = setmetatable( { name = server, address = address }, server_mt ) + server_list[server] = address end -function CMD.logout(uid) +function CMD.logout(uid, subid) local u = user_online[uid] if u then - print(string.format("%s@%s is logout", uid, u.name)) + print(string.format("%s@%s is logout", uid, u.server)) user_online[uid] = nil end end diff --git a/examples/login/main.lua b/examples/login/main.lua index 6359cb96..1eef6f4d 100644 --- a/examples/login/main.lua +++ b/examples/login/main.lua @@ -1,12 +1,12 @@ local skynet = require "skynet" skynet.start(function() - local loginserver = skynet.newservice "logind" - local gate = skynet.newservice "msggate" + local loginserver = skynet.newservice("logind") + local gate = skynet.newservice("gated", loginserver) + skynet.call(gate, "lua", "open" , { port = 8888, maxclient = 64, - loginserver = loginserver, servername = "sample", }) end) diff --git a/examples/login/msgagent.lua b/examples/login/msgagent.lua index 30545c68..90b4ac08 100644 --- a/examples/login/msgagent.lua +++ b/examples/login/msgagent.lua @@ -7,21 +7,46 @@ skynet.register_protocol { } local gate +local userid, subid local CMD = {} -function CMD.init(source , uid, server) +function CMD.login(source, uid, sid, secret) + -- you may use secret to make a encrypted data stream + skynet.error(string.format("%s is login", uid)) gate = source - skynet.error(string.format("%s is coming", uid)) + userid = uid + subid = sid + -- you may load user data from database +end + +local function logout() + if gate then + skynet.call(gate, "lua", "logout", userid, subid) + end + skynet.exit() +end + +function CMD.logout(source) + -- NOTICE: The logout MAY be reentry + skynet.error(string.format("%s is logout", userid)) + logout() +end + +function CMD.afk(source) + -- the connection is broken, but the user may back + skynet.error(string.format("AFK")) end skynet.start(function() - skynet.dispatch("lua", function(_, source, command, ...) + -- If you want to fork a work thread , you MUST do it in CMD.login + skynet.dispatch("lua", function(session, source, command, ...) local f = assert(CMD[command]) skynet.ret(skynet.pack(f(source, ...))) end) skynet.dispatch("client", function(_,_, msg) + -- the simple ehco service skynet.ret(msg) end) end) diff --git a/examples/login/msggate.lua b/examples/login/msggate.lua deleted file mode 100644 index 32b3518f..00000000 --- a/examples/login/msggate.lua +++ /dev/null @@ -1,189 +0,0 @@ -local skynet = require "skynet" -local gateserver = require "gamefw.gateserver" -local netpack = require "netpack" -local crypt = require "crypt" -local socketdriver = require "socketdriver" -local datacenter = require "datacenter" - -local users = {} - -skynet.register_protocol { - name = "client", - id = skynet.PTYPE_CLIENT, -} - -local handler = {} -local handshake = {} -local connection = {} -local agent = {} -local login_master - --- launch an agent service to handle message from client -local function launch_agent(c) - local agent = assert(skynet.newservice "msgagent") - skynet.call(agent, "lua", "init", c.uid, c.server) - return agent -end - -function handler.connect(fd, addr) - skynet.error(string.format("new connection from %s (fd=%d)", addr, fd)) - handshake[fd] = true - gateserver.openclient(fd) -end - -local function auth(fd, msg, sz) --- base64(uid)@base64(server)#base64(subid):index:request:base64(hmac) - local message = netpack.tostring(msg, sz) - local username, index, request , hmac = string.match(message, "([^:]*):([^:]*):([^:]*):([^:]*)") - local content = users[username] - if content == nil then - return "404 Not Found" - end - local idx = assert(tonumber(index)) - local req = assert(tonumber(request)) - hmac = crypt.base64decode(hmac) - - if idx < content.index then - return "403 Forbidden" - end - if req > content.request or req < content.reserved then - return "412 Precondition Failed" - end - - local text = string.format("%s:%s:%s", username, index, request) - local v = crypt.hmac64(crypt.hashkey(text), content.secret) - if v ~= hmac then - return "401 Unauthorized" - end - - content.index = idx - for i=content.reserved, request do - content.response[i] = nil - end - content.reserved = request - content.reqest = request - connection[fd] = content - - if content.fd then - local last_fd = content.fd - connection[last_fd] = nil - gateserver.closeclient(last_fd) - end - content.fd = fd - - if content.agent == nil then - content.agent = true - local ok, agent = pcall(launch_agent, content) - if ok then - content.agent = agent - else - content.agent = nil - skynet.error(string.format("Launch agent %s failed : %s", content.uid, agent)) - connection[fd] = nil - gateserver.closeclient(fd) - end - end -end - -function handler.message(fd, msg, sz) - if handshake[fd] then - local ok, result = pcall(auth, fd, msg, sz) - if not ok then - result = "400 Bad Request" - end - - local close = result ~= nil - - if result == nil then - result = "200 OK" - end - - socketdriver.send(fd, netpack.pack(result)) - - if close then - gateserver.closeclient(fd) - end - handshake[fd] = nil - else - local c = connection[fd] - if c == nil or c.agent == nil then - local message = netpack.tostring(msg, sz) - skynet.error(string.format("Unknown fd = %d, message (%s) size = %d", fd, crypt.hexencode(message):sub(1,80), #message)) - return - end - c.request = c.request + 1 - local ret = c.response[c.request] - if ret == nil then - local ok, msg, sz = pcall(skynet.rawcall, c.agent, "client", msg, sz) - if ok then - ret = netpack.pack_string(msg, sz) - else - skynet.error(string.format("%s request error : %s", c.username, msg)) - ret = netpack.pack_string "" - end - c.response[c.request] = ret - end - socketdriver.send(c.fd, ret) - end -end - -function handler.close(fd) - handshake[fd] = nil - local c = connection[fd] - if c then - c.fd = nil - end -end - -handler.error = handler.close - -function handler.open(source, conf) - login_master = assert(conf.loginserver) - local servername = assert(conf.servername) - skynet.call(login_master, "lua", "register_gate", servername, skynet.self()) -end - - -local CMD = {} - -function CMD.login(source, server, uid, secret) - local subid = "1" - local username = crypt.base64encode(uid) .. '#'..crypt.base64encode(subid)..'@' .. crypt.base64encode(server) - users[username] = { - server = server, - uid = uid, - secret = secret, - index = 0, - request = 0, - reserved = 0, - response = {}, - } - return subid -end - -function CMD.logout(source) - local c = agent[source] - if c then - skynet.call(login_master, "lua", "logout", c.uid) - if c.fd then - gateserver.closeclient(c.fd) - end - end -end - -function CMD.kick(source, server, uid) - local username = crypt.base64encode(uid) .. '@' .. crypt.base64encode(server) - local u = users[username] - if u and u.agent then - skynet.call(u.agent, "logout") - skynet.kill(u.agent) - u.agent = nil - end -end - -function handler.command(cmd, source, ...) - local f = assert(CMD[cmd]) - return f(source, ...) -end - -gateserver.start(handler) diff --git a/lualib-src/lua-netpack.c b/lualib-src/lua-netpack.c index 432818e7..4012c9bc 100644 --- a/lualib-src/lua-netpack.c +++ b/lualib-src/lua-netpack.c @@ -457,9 +457,9 @@ lpack_padding(lua_State *L) { size_t content_sz; uint8_t *buffer; const char * ptr = tolstring(L, &content_sz, 2); - size_t header_sz = 0; - const char * header = luaL_checklstring(L,1,&header_sz); - size_t len = header_sz + content_sz; + size_t cookie_sz = 0; + const char * cookie = luaL_checklstring(L,1,&cookie_sz); + size_t len = cookie_sz + content_sz; if (len > 0x10000) { return luaL_error(L, "Invalid size (too long) of data : %d", (int)len); @@ -472,8 +472,8 @@ lpack_padding(lua_State *L) { } write_size(buffer, len); - memcpy(buffer+2, header, header_sz); - memcpy(buffer+2+header_sz, ptr, content_sz); + memcpy(buffer+2, ptr, content_sz); + memcpy(buffer+2+content_sz, cookie, cookie_sz); lua_pushlstring(L, (const char *)buffer, len+2); return 1; @@ -486,8 +486,19 @@ ltostring(lua_State *L) { if (ptr == NULL) { lua_pushliteral(L, ""); } else { - lua_pushlstring(L, (const char *)ptr, size); - skynet_free(ptr); + if (lua_isnumber(L, 3)) { + int offset = lua_tointeger(L, 3); + if (offset < 0) { + return luaL_error(L, "Invalid offset %d", offset); + } + if (offset > size) { + offset = size; + } + lua_pushlstring(L, (const char *)ptr + offset, size-offset); + } else { + lua_pushlstring(L, (const char *)ptr, size); + skynet_free(ptr); + } } return 1; } diff --git a/lualib/skynet.lua b/lualib/skynet.lua index 2b984e2a..5e8e95b0 100644 --- a/lualib/skynet.lua +++ b/lualib/skynet.lua @@ -127,7 +127,7 @@ function suspend(co, result, command, param, size) if not result then local session = session_coroutine_id[co] local addr = session_coroutine_address[co] - if session and session ~= 0 then + if session then c.send(addr, skynet.PTYPE_ERROR, session, "") end session_coroutine_id[co] = nil @@ -151,6 +151,9 @@ function suspend(co, result, command, param, size) -- coroutine exit session_coroutine_id[co] = nil session_coroutine_address[co] = nil + elseif command == "QUIT" then + -- service exit + return else error("Unknown command : " .. command .. "\n" .. debug.traceback(co)) end @@ -264,6 +267,8 @@ function skynet.exit() end end c.command("EXIT") + -- quit service + coroutine_yield "QUIT" end function skynet.kill(name) diff --git a/lualib/gamefw/gateserver.lua b/lualib/snax/gateserver.lua similarity index 96% rename from lualib/gamefw/gateserver.lua rename to lualib/snax/gateserver.lua index 84f33f88..fc019143 100644 --- a/lualib/gamefw/gateserver.lua +++ b/lualib/snax/gateserver.lua @@ -40,7 +40,7 @@ function gateserver.start(handler) socket = socketdriver.listen(address, port) socketdriver.start(socket) if handler.open then - handler.open(source, conf) + return handler.open(source, conf) end end @@ -88,8 +88,8 @@ function gateserver.start(handler) end function MSG.close(fd) - if handler.close then - handler.close(fd) + if handler.disconnect then + handler.disconnect(fd) end close_fd(fd) end diff --git a/lualib/gamefw/loginserver.lua b/lualib/snax/loginserver.lua similarity index 74% rename from lualib/gamefw/loginserver.lua rename to lualib/snax/loginserver.lua index a7384105..17cb131d 100644 --- a/lualib/gamefw/loginserver.lua +++ b/lualib/snax/loginserver.lua @@ -1,6 +1,35 @@ local skynet = require "skynet" local socket = require "socket" local crypt = require "crypt" +local table = table +local string = string +local assert = assert + +--[[ + +Protocol: + + line (\n) based text protocol + + 1. Server->Client : base64(8bytes random challenge) + 2. Client->Server : base64(8bytes handshake client key) + 3. Server: Gen a 8bytes handshake server key + 4. Server->Client : base64(DH-Exchange(server key)) + 5. Server/Client secret := DH-Secret(client key/server key) + 6. Client->Server : base64(HMAC(challenge, secret)) + 7. Client->Server : DES(secret, base64(token)) + 8. Server : call auth_handler(token) -> server, uid (A user defined method) + 9. Server : call login_handler(server, uid, secret) (A user defined method) + +Error Code: + 400 Bad Request . challenge failed + 401 Unauthorized . unauthorized by auth_handler + 403 Forbidden . login_handler failed + 406 Not Acceptable . already in login (disallow multi login) + +Success: + 200 base64(subid) +]] local function write(fd, text) assert(socket.write(fd, text), "socket error") @@ -51,6 +80,8 @@ local function launch_slave(auth_handler) end) end +local user_login = {} + local function accept(conf, s, fd, addr) -- call slave auth local ok, server, uid, secret = skynet.call(s, "lua", fd, addr) @@ -61,12 +92,24 @@ local function accept(conf, s, fd, addr) error(server) end + if not conf.multilogin then + if user_login[uid] then + write(fd, "406 Not Acceptable\n") + error(string.format("User %s is already login", uid)) + end + + user_login[uid] = true + end + local ok, err = pcall(conf.login_handler, server, uid, secret) + -- unlock login + user_login[uid] = nil + if ok then err = err or "" write(fd, "200 "..crypt.base64encode(err).."\n") else - write(fd, "406 Not Acceptable\n") + write(fd, "403 Forbidden\n") error(err) end end @@ -92,8 +135,8 @@ local function launch_master(conf) skynet.newservice(SERVICE_NAME) end - local id = socket.listen(host, port) skynet.error(string.format("login server listen at : %s %d", host, port)) + local id = socket.listen(host, port) socket.start(id , function(fd, addr) local s = slave[balance] balance = balance + 1 diff --git a/lualib/snax/msgserver.lua b/lualib/snax/msgserver.lua new file mode 100644 index 00000000..d1a84f3a --- /dev/null +++ b/lualib/snax/msgserver.lua @@ -0,0 +1,299 @@ +local skynet = require "skynet" +local gateserver = require "snax.gateserver" +local netpack = require "netpack" +local crypt = require "crypt" +local socketdriver = require "socketdriver" +local assert = assert +local b64encode = crypt.base64encode +local b64decode = crypt.base64decode + +--[[ + +Protocol: + + All the number type is big-endian + + Shakehands (The first package) + + Client -> Server : + + base64(uid)@base64(server)#base64(subid):index:base64(hmac) + + Server -> Client + + XXX ErrorCode + 404 User Not Found + 403 Index Expired + 401 Unauthorized + 400 Bad Request + 200 OK + + Req-Resp + + Client -> Server : Request + word size (Not include self) + string content (size-4) + dword session + + Server -> Client : Response + word size (Not include self) + string content (size-5) + byte ok (1 is ok, 0 is error) + dword session + +API: + server.userid(username) + return uid, subid, server + + server.username(uid, subid, server) + return username + + server.login(username, secret) + update user secret + + server.logout(username) + user logout + + server.ip(username) + return ip when connection establish, or nil + + server.start(conf) + start server + +Supported skynet command: + kick username (may used by loginserver) + login username secret (used by loginserver) + logout username (used by agent) + +Config for server.start: + conf.expired_number : the number of the response message cached after sending out (default is 128) + conf.login_handler(uid, secret) -> subid : the function when a new user login, alloc a subid for it. (may call by login server) + conf.logout_handler(uid, subid) : the functon when a user logout. (may call by agent) + conf.kick_handler(uid, subid) : the functon when a user logout. (may call by login server) + conf.request_handler(username, session, msg, sz) : the function when recv a new request. + conf.register_handler(servername) : call when gate open + conf.disconnect_handler(username) : call when a connection disconnect (afk) +]] + +local server = {} + +skynet.register_protocol { + name = "client", + id = skynet.PTYPE_CLIENT, +} + +local user_online = {} +local handshake = {} +local connection = {} + +function server.userid(username) + -- base64(uid)@base64(server)#base64(subid) + local uid, servername, subid = username:match "([^@]*)@([^#]*)#(.*)" + return b64decode(uid), b64decode(subid), b64decode(servername) +end + +function server.username(uid, subid, servername) + return string.format("%s@%s#%s", b64encode(uid), b64encode(servername), b64encode(tostring(subid))) +end + +function server.logout(username) + local u = user_online[username] + user_online[username] = nil + if u.fd then + gateserver.closeclient(u.fd) + connection[u.fd] = nil + end +end + +function server.login(username, secret) + assert(user_online[username] == nil) + user_online[username] = { + secret = secret, + version = 0, + index = 0, + username = username, + response = {}, -- response cache + } +end + +function server.ip(username) + local u = user_online[username] + if u and u.fd then + return u.ip + end +end + +function server.start(conf) + local expired_number = conf.expired_number or 128 + + local handler = {} + + local CMD = { + login = assert(conf.login_handler), + logout = assert(conf.logout_handler), + kick = assert(conf.kick_handler), + } + + function handler.command(cmd, source, ...) + local f = assert(CMD[cmd]) + return f(...) + end + + function handler.open(source, gateconf) + local servername = assert(gateconf.servername) + return conf.register_handler(servername) + end + + function handler.connect(fd, addr) + handshake[fd] = addr + gateserver.openclient(fd) + end + + function handler.disconnect(fd) + handshake[fd] = nil + local c = connection[fd] + if c then + c.fd = nil + connection[fd] = nil + if conf.disconnect_handler then + conf.disconnect_handler(c.username) + end + end + end + + handler.error = handler.disconnect + + -- atomic , no yield + local function do_auth(fd, message, addr) + local username, index, hmac = string.match(message, "([^:]*):([^:]*):([^:]*)") + local u = user_online[username] + if u == nil then + return "404 User Not Found" + end + local idx = assert(tonumber(index)) + hmac = b64decode(hmac) + + if idx <= u.version then + return "403 Index Expired" + end + + local text = string.format("%s:%s", username, index) + local v = crypt.hmac64(crypt.hashkey(text), u.secret) + if v ~= hmac then + return "401 Unauthorized" + end + + u.version = idx + u.fd = fd + u.ip = addr + connection[fd] = u + end + + local function auth(fd, addr, msg, sz) + local message = netpack.tostring(msg, sz) + local ok, result = pcall(do_auth, fd, message, addr) + if not ok then + skynet.error(result) + result = "400 Bad Request" + end + + local close = result ~= nil + + if result == nil then + result = "200 OK" + end + + socketdriver.send(fd, netpack.pack(result)) + + if close then + gateserver.closeclient(fd) + end + end + + local request_handler = assert(conf.request_handler) + + -- u.response is a struct { message, version, index } + local function retire_response(u) + if u.index >= expired_number * 2 then + local max = 0 + local response = u.response + for k,p in pairs(response) do + if p[3] < expired_number then + response[k] = nil + else + p[3] = p[3] - expired_number + if p[3] > max then + max = p[3] + end + end + end + u.index = max + 1 + end + end + + local function do_request(fd, msg, sz) + local u = assert(connection[fd], "invalid fd") + local msg_sz = sz - 4 + local session = netpack.tostring(msg, sz, msg_sz) + local p = u.response[session] + if p then + -- session can be reuse in the same connection + if p[2] == u.version then + u.response[session] = nil + p = nil + end + end + + if p == nil then + local ok, result = pcall(conf.request_handler, u.username, msg, msg_sz) + -- NOTICE: YIELD here, socket may close. + if not ok then + skynet.error(result) + result = "\0" .. session + else + result = result .. '\1' .. session + end + + p = { netpack.pack_string(result), u.version, u.index } + if u.response[session] then + skynet.error(string.format("Conflict session %s", crypt.hexencode(session))) + end + u.response[session] = p + else + netpack.tostring(msg, sz) -- request before, so free msg + -- resend response, and update index p[3]. + p[3] = u.index + end + u.index = u.index + 1 + -- check connect again + if connection[fd] then + socketdriver.send(fd, p[1]) + end + retire_response(u) + end + + local function request(fd, msg, sz) + local ok, err = pcall(do_request, fd, msg, sz) + -- not atomic, may yield + if not ok then + skynet.error(string.format("Invalid package %s : %s", err, netpack.tostring(msg, sz))) + if connection[fd] then + gateserver.closeclient(fd) + end + end + end + + function handler.message(fd, msg, sz) + local addr = handshake[fd] + if addr then + auth(fd,addr,msg,sz) + handshake[fd] = nil + else + request(fd, msg, sz) + end + end + + return gateserver.start(handler) +end + +return server diff --git a/service/gate.lua b/service/gate.lua index e1f6f1d8..911a126a 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -1,5 +1,5 @@ local skynet = require "skynet" -local gateserver = require "gamefw.gateserver" +local gateserver = require "snax.gateserver" local netpack = require "netpack" local watchdog @@ -53,7 +53,7 @@ local function close_fd(fd) end end -function handler.close(fd) +function handler.disconnect(fd) close_fd(fd) skynet.send(watchdog, "lua", "socket", "close", fd) end @@ -89,15 +89,6 @@ function CMD.accept(source, fd) end function CMD.kick(source, fd) - local c - if fd then - c = connection[fd] - else - c = forwarding[source] - end - - assert(c) - gateserver.closeclient(fd) end From 691bb281639ce596d4ec7300849806248ed9f17b Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 12:08:42 +0800 Subject: [PATCH 11/37] update comment --- lualib/snax/loginserver.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lualib/snax/loginserver.lua b/lualib/snax/loginserver.lua index 17cb131d..5d332200 100644 --- a/lualib/snax/loginserver.lua +++ b/lualib/snax/loginserver.lua @@ -19,7 +19,8 @@ Protocol: 6. Client->Server : base64(HMAC(challenge, secret)) 7. Client->Server : DES(secret, base64(token)) 8. Server : call auth_handler(token) -> server, uid (A user defined method) - 9. Server : call login_handler(server, uid, secret) (A user defined method) + 9. Server : call login_handler(server, uid, secret) ->subid (A user defined method) + 10. Server->Client : 200 base64(subid) Error Code: 400 Bad Request . challenge failed From 37a46607ce3d3bfbda65e767a09abe3db0244236 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 12:17:55 +0800 Subject: [PATCH 12/37] add socketchannel:changhost --- lualib/mongo.lua | 1 + lualib/socketchannel.lua | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lualib/mongo.lua b/lualib/mongo.lua index f9eece35..9760a545 100644 --- a/lualib/mongo.lua +++ b/lualib/mongo.lua @@ -84,6 +84,7 @@ local function mongo_auth(mongoc) end return function() assert(mongoc:auth(user, pass)) + -- todo : If mongoc want to connect to replica set, runCommand ismater here, and call mongoc.__sock:changehost end end diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index e6a061f0..e30114d3 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -175,11 +175,11 @@ local function connect_once(self) end self.__authcoroutine = false - return true + return self.__sock ~= nil end local function try_connect(self , once) - local t = 100 + local t = 0 while not self.__closed do if connect_once(self) then if not once then @@ -292,6 +292,16 @@ function channel:close() end end +function channel:changehost(host, port) + self.__host = host + if port then + self.__port = port + end + if not self.__closed then + close_channel_socket(self) + end +end + channel_meta.__gc = channel.close local function wrapper_socket_function(f) From 0ae1fcf7cd88fd6d3496d5e0e2211a68fea9f1f2 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 13:46:44 +0800 Subject: [PATCH 13/37] add socketchannel backup host --- lualib/socketchannel.lua | 60 ++++++++++++++++++++++++++++------------ 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index e30114d3..34fbff36 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -26,6 +26,7 @@ function socket_channel.channel(desc) local c = { __host = assert(desc.host), __port = assert(desc.port), + __backup = desc.backup, __auth = desc.auth, __response = desc.response, -- It's for session mode __request = {}, -- request seq { response func or session } -- It's for order mode @@ -152,30 +153,51 @@ local function dispatch_function(self) end end +local function connect_backup(self) + if self.__backup then + for _, host in ipairs(self.__backup) do + skynet.error("socket: connect to backup host", host, self.__port) + local fd = socket.open(host, self.__port) + if fd then + self.__host = host + return fd + end + end + end +end + local function connect_once(self) assert(not self.__sock and not self.__authcoroutine) local fd = socket.open(self.__host, self.__port) if not fd then - return false - end - self.__authcoroutine = coroutine.running() - self.__sock = setmetatable( {fd} , channel_socket_meta ) - skynet.fork(dispatch_function(self), self) - - if self.__auth then - local ok , message = pcall(self.__auth, self) - if not ok then - close_channel_socket(self) - if message ~= socket_error then - skynet.error("socket: auth failed", message) - end + fd = connect_backup(self) + if not fd then + return false end - self.__authcoroutine = false - return ok end + while not self.__closed do + self.__authcoroutine = coroutine.running() + self.__sock = setmetatable( {fd} , channel_socket_meta ) + skynet.fork(dispatch_function(self), self) - self.__authcoroutine = false - return self.__sock ~= nil + if self.__auth then + local ok , message = pcall(self.__auth, self) + if not ok then + close_channel_socket(self) + if message ~= socket_error then + skynet.error("socket: auth failed", message) + end + end + self.__authcoroutine = false + return ok + end + + self.__authcoroutine = false + if self.__sock then + return true + end + -- auth may change host, so connect again + end end local function try_connect(self , once) @@ -302,6 +324,10 @@ function channel:changehost(host, port) end end +function channel:changebackup(backup) + self.__backup = backup +end + channel_meta.__gc = channel.close local function wrapper_socket_function(f) From acc175e821ffc385a9576a7706ee3ddd7bad50dd Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 17:18:56 +0800 Subject: [PATCH 14/37] socketchannel backup support host/port --- lualib/socketchannel.lua | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index 34fbff36..80d2d745 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -155,11 +155,18 @@ end local function connect_backup(self) if self.__backup then - for _, host in ipairs(self.__backup) do - skynet.error("socket: connect to backup host", host, self.__port) - local fd = socket.open(host, self.__port) + for _, addr in ipairs(self.__backup) do + local host, port + if type(host) == "table" then + host, port = addr.host, addr.port + else + host = addr + end + skynet.error("socket: connect to backup host", host, port) + local fd = socket.open(host, port) if fd then self.__host = host + self.__port = port return fd end end From 1181730302c8647aa9bf07acba70f40d0a7a47f8 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 17:21:39 +0800 Subject: [PATCH 15/37] socketchannel backup support default port --- lualib/socketchannel.lua | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index 80d2d745..e953fe8f 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -161,6 +161,9 @@ local function connect_backup(self) host, port = addr.host, addr.port else host = addr + if not addr:find(":", 1, true) then + port = self.__port + end end skynet.error("socket: connect to backup host", host, port) local fd = socket.open(host, port) From 9421435ac53623180ace845f9e7220c3f5b281f5 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 17:53:18 +0800 Subject: [PATCH 16/37] remove dup code --- service/gate.lua | 8 -------- 1 file changed, 8 deletions(-) diff --git a/service/gate.lua b/service/gate.lua index 911a126a..5fb22801 100644 --- a/service/gate.lua +++ b/service/gate.lua @@ -65,14 +65,6 @@ end local CMD = {} -local function unforward(c) - if c.agent then - forwarding[c.agent] = nil - c.agent = nil - c.client = nil - end -end - function CMD.forward(source, fd, client, address) local c = assert(connection[fd]) unforward(c) From 75b2feff738fca4f5781d705faff3ecefdd5bd47 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 16 Jul 2014 17:53:53 +0800 Subject: [PATCH 17/37] fix typo --- lualib/socketchannel.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index e953fe8f..4b2f28ad 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -157,7 +157,7 @@ local function connect_backup(self) if self.__backup then for _, addr in ipairs(self.__backup) do local host, port - if type(host) == "table" then + if type(addr) == "table" then host, port = addr.host, addr.port else host = addr From 7d2d107518e753698fb2a1a2b461efd73543e5e9 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Thu, 17 Jul 2014 17:06:47 +0800 Subject: [PATCH 18/37] bugfix: socketchannel auth --- lualib/socketchannel.lua | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index 4b2f28ad..c2886e31 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -177,6 +177,9 @@ local function connect_backup(self) end local function connect_once(self) + if self.__closed then + return false + end assert(not self.__sock and not self.__authcoroutine) local fd = socket.open(self.__host, self.__port) if not fd then @@ -185,29 +188,29 @@ local function connect_once(self) return false end end - while not self.__closed do + + self.__sock = setmetatable( {fd} , channel_socket_meta ) + skynet.fork(dispatch_function(self), self) + + if self.__auth then self.__authcoroutine = coroutine.running() - self.__sock = setmetatable( {fd} , channel_socket_meta ) - skynet.fork(dispatch_function(self), self) - - if self.__auth then - local ok , message = pcall(self.__auth, self) - if not ok then - close_channel_socket(self) - if message ~= socket_error then - skynet.error("socket: auth failed", message) - end + local ok , message = pcall(self.__auth, self) + if not ok then + close_channel_socket(self) + if message ~= socket_error then + skynet.error("socket: auth failed", message) end - self.__authcoroutine = false - return ok end - self.__authcoroutine = false if self.__sock then - return true + return ok + else + -- auth may change host, so connect again + return connect_once(self) end - -- auth may change host, so connect again end + + return true end local function try_connect(self , once) From 6bafd05c658cbce7455399b3cb84e52410fb3253 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Thu, 17 Jul 2014 17:35:36 +0800 Subject: [PATCH 19/37] don't support ip:port --- lualib/socketchannel.lua | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index c2886e31..62818613 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -161,9 +161,7 @@ local function connect_backup(self) host, port = addr.host, addr.port else host = addr - if not addr:find(":", 1, true) then - port = self.__port - end + port = self.__port end skynet.error("socket: connect to backup host", host, port) local fd = socket.open(host, port) @@ -198,6 +196,7 @@ local function connect_once(self) if not ok then close_channel_socket(self) if message ~= socket_error then + self.__authcoroutine = false skynet.error("socket: auth failed", message) end end From a0b536718ccd8541917631deab07daa1f69b5cc5 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Thu, 17 Jul 2014 21:41:08 +0800 Subject: [PATCH 20/37] config can read ENV --- skynet-src/skynet_main.c | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/skynet-src/skynet_main.c b/skynet-src/skynet_main.c index 9c38fd25..4eb18a59 100644 --- a/skynet-src/skynet_main.c +++ b/skynet-src/skynet_main.c @@ -11,6 +11,7 @@ #include #include #include +#include static int optint(const char *key, int opt) { @@ -51,7 +52,6 @@ optstring(const char *key,const char * opt) { static void _init_env(lua_State *L) { - lua_pushglobaltable(L); lua_pushnil(L); /* first key */ while (lua_next(L, -2) != 0) { int keyt = lua_type(L, -2); @@ -83,6 +83,19 @@ int sigign() { return 0; } +static const char * load_config = "\ + local config_name = ...\ + local f = assert(io.open(config_name))\ + local code = assert(f:read \'*a\')\ + local function getenv(name) return assert(os.getenv(name), name) end\ + code = string.gsub(code, \'%$([%w_%d]+)\', getenv)\ + print(code)\ + f:close()\ + local result = {}\ + assert(load(code,\'=(load)\',\'t\',result))()\ + return result\ +"; + int main(int argc, char *argv[]) { const char * config_file = "config"; @@ -98,11 +111,12 @@ main(int argc, char *argv[]) { struct lua_State *L = lua_newstate(skynet_lalloc, NULL); luaL_openlibs(L); // link lua lib - lua_close(L); - L = luaL_newstate(); - - int err = luaL_dofile(L, config_file); + int err = luaL_loadstring(L, load_config); + assert(err == LUA_OK); + lua_pushstring(L, config_file); + + err = lua_pcall(L, 1, 1, 0); if (err) { fprintf(stderr,"%s\n",lua_tostring(L,-1)); lua_close(L); From 8e4e9ed5c183291edbbce2f16497eef8a9f0400f Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Thu, 17 Jul 2014 21:58:09 +0800 Subject: [PATCH 21/37] bugfix: connect auth --- lualib/socketchannel.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index 62818613..1bfa06f5 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -203,10 +203,11 @@ local function connect_once(self) self.__authcoroutine = false if self.__sock then return ok - else + elseif ok then -- auth may change host, so connect again return connect_once(self) end + return ok end return true From e1ca48603ee583a3fc61258e8cd1d9bec889b0ab Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Thu, 17 Jul 2014 22:00:48 +0800 Subject: [PATCH 22/37] simplify code --- lualib/socketchannel.lua | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lualib/socketchannel.lua b/lualib/socketchannel.lua index 1bfa06f5..45aeea54 100644 --- a/lualib/socketchannel.lua +++ b/lualib/socketchannel.lua @@ -201,9 +201,7 @@ local function connect_once(self) end end self.__authcoroutine = false - if self.__sock then - return ok - elseif ok then + if ok and not self.__sock then -- auth may change host, so connect again return connect_once(self) end From d41c9db019ccbc8a70eb221f897184b919d9d8e4 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Fri, 18 Jul 2014 12:09:05 +0800 Subject: [PATCH 23/37] bugfix: update the response fd, when the client reconnect --- examples/login/client.lua | 57 +++++++++++++++++++++---------------- examples/login/msgagent.lua | 1 + lualib/snax/msgserver.lua | 53 +++++++++++++++++++++++----------- 3 files changed, 70 insertions(+), 41 deletions(-) diff --git a/examples/login/client.lua b/examples/login/client.lua index 82b0a182..0ebed6a7 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -70,13 +70,10 @@ print("login ok, subid=", subid) ----- connect to game server -local session = 0 - -local function send_request(v) - session = session + 1 +local function send_request(v, session) local s = string.char(bit32.extract(session,24,8), bit32.extract(session,16,8), bit32.extract(session,8,8), bit32.extract(session,0,8)) socket.send(fd , v..s) - return session, v + return v, session end local function recv_response(v) @@ -87,7 +84,7 @@ local function recv_response(v) local c = v:byte(i) session = session + bit32.lshift(c,(-1-i) * 8) end - return ok ~=0 , session, content + return ok ~=0 , content, session end local input = {} @@ -115,32 +112,44 @@ local function readpackage() end end +local text = "echo" local index = 1 -local function echo(text) - print("connect") - local fd = assert(socket.connect("127.0.0.1", 8888)) - input = {} +print("connect") +local fd = assert(socket.connect("127.0.0.1", 8888)) +input = {} - local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) - local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) +local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) +local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) - socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) +socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) - print(readpackage()) - print("===>",send_request(text)) - print("===>",send_request(text .. " again")) - print("<===",recv_response(readpackage())) - print("<===",recv_response(readpackage())) +print(readpackage()) +print("===>",send_request(text,0)) +-- don't recv response +-- print("<===",recv_response(readpackage())) - print("disconnect") - socket.close(fd) -end - -echo "hello" +print("disconnect") +socket.close(fd) index = index + 1 -echo "world" +print("connect again") +local fd = assert(socket.connect("127.0.0.1", 8888)) +input = {} + +local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) +local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) + +socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) + +print(readpackage()) +print("===>",send_request("fake",0)) -- request again (use last session 0, so the request message is fake) +print("===>",send_request("again",1)) -- request again (use new session) +print("<===",recv_response(readpackage())) +print("<===",recv_response(readpackage())) +print("disconnect") +socket.close(fd) + diff --git a/examples/login/msgagent.lua b/examples/login/msgagent.lua index 90b4ac08..fc3a2d5b 100644 --- a/examples/login/msgagent.lua +++ b/examples/login/msgagent.lua @@ -47,6 +47,7 @@ skynet.start(function() skynet.dispatch("client", function(_,_, msg) -- the simple ehco service + skynet.sleep(10) -- sleep a while skynet.ret(msg) end) end) diff --git a/lualib/snax/msgserver.lua b/lualib/snax/msgserver.lua index d1a84f3a..f6804a60 100644 --- a/lualib/snax/msgserver.lua +++ b/lualib/snax/msgserver.lua @@ -212,18 +212,21 @@ function server.start(conf) local request_handler = assert(conf.request_handler) - -- u.response is a struct { message, version, index } + -- u.response is a struct { return_fd , response, version, index } local function retire_response(u) if u.index >= expired_number * 2 then local max = 0 local response = u.response for k,p in pairs(response) do - if p[3] < expired_number then - response[k] = nil - else - p[3] = p[3] - expired_number - if p[3] > max then - max = p[3] + if p[1] == nil then + -- request complete, check expired + if p[4] < expired_number then + response[k] = nil + else + p[4] = p[4] - expired_number + if p[4] > max then + max = p[4] + end end end end @@ -238,14 +241,23 @@ function server.start(conf) local p = u.response[session] if p then -- session can be reuse in the same connection - if p[2] == u.version then + if p[3] == u.version then + local last = u.response[session] u.response[session] = nil p = nil + if last[2] == nil then + local error_msg = string.format("Conflict session %s", crypt.hexencode(session)) + skynet.error(error_msg) + error(error_msg) + end end end if p == nil then + p = { fd } + u.response[session] = p local ok, result = pcall(conf.request_handler, u.username, msg, msg_sz) + result = result or "" -- NOTICE: YIELD here, socket may close. if not ok then skynet.error(result) @@ -254,21 +266,28 @@ function server.start(conf) result = result .. '\1' .. session end - p = { netpack.pack_string(result), u.version, u.index } - if u.response[session] then - skynet.error(string.format("Conflict session %s", crypt.hexencode(session))) - end - u.response[session] = p + p[2] = netpack.pack_string(result) + p[3] = u.version + p[4] = u.index else netpack.tostring(msg, sz) -- request before, so free msg - -- resend response, and update index p[3]. - p[3] = u.index + -- update version/index, change return fd. + -- resend response. + p[1] = fd + p[3] = u.version + p[4] = u.index + if p[2] == nil then + -- already request, but response is not ready + return + end end u.index = u.index + 1 - -- check connect again + -- the return fd is p[1] (fd may change by multi request) check connect + fd = p[1] if connection[fd] then - socketdriver.send(fd, p[1]) + socketdriver.send(fd, p[2]) end + p[1] = nil retire_response(u) end From a8b80cd73ee1c09e050a182fb69ca8f163253b4d Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Sat, 19 Jul 2014 14:22:24 +0800 Subject: [PATCH 24/37] simplify clientsocket lib --- HISTORY.md | 1 + examples/client.lua | 74 ++++++++++++------ examples/login/client.lua | 124 +++++++++++++++++++----------- examples/simpledb.lua | 6 +- lualib-src/lua-clientsocket.c | 141 +++------------------------------- lualib/snax/loginserver.lua | 32 ++++++-- 6 files changed, 171 insertions(+), 207 deletions(-) diff --git a/HISTORY.md b/HISTORY.md index 5c52c767..a769e314 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -2,6 +2,7 @@ Dev version ----------- * skynet.exit will quit service immediately. * Add snax.gateserver, snax.loginserver, snax.msgserver +* Simplify clientsocket lib v0.4.2 (2014-7-14) ----------- diff --git a/examples/client.lua b/examples/client.lua index 278922e6..d5bace7f 100644 --- a/examples/client.lua +++ b/examples/client.lua @@ -2,30 +2,47 @@ package.cpath = "luaclib/?.so" local socket = require "clientsocket" local cjson = require "cjson" +local bit32 = require "bit32" -local fd = socket.connect("127.0.0.1", 8888) +local fd = assert(socket.connect("127.0.0.1", 8888)) -local last -local result = {} +local function send_package(fd, pack) + local size = #pack + local package = string.format("%c%c%s", + bit32.extract(size,8,8), + bit32.extract(size,0,8), + pack) -local function dispatch() - while true do - local status - status, last = socket.recv(fd, last, result) - if status == nil then - error "Server closed" - end - if not status then - break - end - for _, v in ipairs(result) do - local session,t,str = string.match(v, "(%d+)(.)(.*)") - assert(t == '-' or t == '+') - session = tonumber(session) - local result = cjson.decode(str) - print("Response:",session, result[1], result[2]) - end + socket.send(fd, package) +end + +local function unpack_package(text) + local size = #text + if size < 2 then + return nil, text end + local s = text:byte(1) * 256 + text:byte(2) + if size < s+2 then + return nil, text + end + + return text:sub(3,2+s), text:sub(3+s) +end + +local function recv_package(last) + local result + result, last = unpack_package(last) + if result then + return result, last + end + local r = socket.recv(fd) + if not r then + return nil, last + end + if r == "" then + error "Server closed" + end + return unpack_package(last .. r) end local session = 0 @@ -33,12 +50,25 @@ local session = 0 local function send_request(v) session = session + 1 local str = string.format("%d+%s",session, cjson.encode(v)) - socket.send(fd, str) + send_package(fd, str) print("Request:", session) end +local last = "" + while true do - dispatch() + while true do + local v + v, last = recv_package(last) + if not v then + break + end + local session,t,str = string.match(v, "(%d+)(.)(.*)") + assert(t == '-' or t == '+') + session = tonumber(session) + local result = cjson.decode(str) + print("Response:",session, result[1], result[2]) + end local cmd = socket.readstdin() if cmd then local args = {} diff --git a/examples/login/client.lua b/examples/login/client.lua index 0ebed6a7..dfd66267 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -2,44 +2,65 @@ package.cpath = "luaclib/?.so" local socket = require "clientsocket" local crypt = require "crypt" +local bit32 = require "bit32" -local last local fd = assert(socket.connect("127.0.0.1", 8001)) -local input = {} -local function readline() - local line = table.remove(input, 1) - if line then - return line +local function writeline(fd, text) + socket.send(fd, text .. "\n") +end + +local function unpack_line(text) + local from = text:find("\n", 1, true) + if from then + return text:sub(1, from-1), text:sub(from+1) end + return nil, text +end - while true do - local status - status, last = socket.readline(fd, last, input) - if status == nil then +local last = "" + +local function unpack_f(f) + local function try_recv(fd, last) + local result + result, last = f(last) + if result then + return result, last + end + local r = socket.recv(fd) + if not r then + return nil, last + end + if r == "" then error "Server closed" end - if not status then - socket.usleep(100) - else - local line = table.remove(input, 1) - if line then - return line + return f(last .. r) + end + + return function() + while true do + local result + result, last = try_recv(fd, last) + if result then + return result end + socket.usleep(100) end end end +local readline = unpack_f(unpack_line) + local challenge = crypt.base64decode(readline()) local clientkey = crypt.randomkey() -socket.writeline(fd, crypt.base64encode(crypt.dhexchange(clientkey))) +writeline(fd, crypt.base64encode(crypt.dhexchange(clientkey))) local secret = crypt.dhsecret(crypt.base64decode(readline()), clientkey) print("sceret is ", crypt.hexencode(secret)) local hmac = crypt.hmac64(challenge, secret) -socket.writeline(fd, crypt.base64encode(hmac)) +writeline(fd, crypt.base64encode(hmac)) local token = { server = "sample", @@ -56,7 +77,7 @@ end local etoken = crypt.desencode(secret, encode_token(token)) local b = crypt.base64encode(etoken) -socket.writeline(fd, crypt.base64encode(etoken)) +writeline(fd, crypt.base64encode(etoken)) local result = readline() print(result) @@ -71,8 +92,18 @@ print("login ok, subid=", subid) ----- connect to game server local function send_request(v, session) - local s = string.char(bit32.extract(session,24,8), bit32.extract(session,16,8), bit32.extract(session,8,8), bit32.extract(session,0,8)) - socket.send(fd , v..s) + local size = #v + 4 + local package = string.format("%c%c%s%c%c%c%c", + bit32.extract(size,8,8), + bit32.extract(size,0,8), + v, + bit32.extract(session,24,8), + bit32.extract(session,16,8), + bit32.extract(session,8,8), + bit32.extract(session,0,8) + ) + + socket.send(fd, package) return v, session end @@ -87,29 +118,29 @@ local function recv_response(v) return ok ~=0 , content, session end -local input = {} - -local function readpackage() - local line = table.remove(input, 1) - if line then - return line +local function unpack_package(text) + local size = #text + if size < 2 then + return nil, text + end + local s = text:byte(1) * 256 + text:byte(2) + if size < s+2 then + return nil, text end - while true do - local status - status, last = socket.recv(fd, last, input) - if status == nil then - error "Server closed" - end - if not status then - socket.usleep(100) - else - local line = table.remove(input, 1) - if line then - return line - end - end - end + return text:sub(3,2+s), text:sub(3+s) +end + +local readpackage = unpack_f(unpack_package) + +local function send_package(fd, pack) + local size = #pack + local package = string.format("%c%c%s", + bit32.extract(size,8,8), + bit32.extract(size,0,8), + pack) + + socket.send(fd, package) end local text = "echo" @@ -117,12 +148,13 @@ local index = 1 print("connect") local fd = assert(socket.connect("127.0.0.1", 8888)) -input = {} +last = "" local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) -socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) + +send_package(fd, handshake .. ":" .. crypt.base64encode(hmac)) print(readpackage()) print("===>",send_request(text,0)) @@ -136,12 +168,12 @@ index = index + 1 print("connect again") local fd = assert(socket.connect("127.0.0.1", 8888)) -input = {} +last = "" local handshake = string.format("%s@%s#%s:%d", crypt.base64encode(token.user), crypt.base64encode(token.server),crypt.base64encode(subid) , index) local hmac = crypt.hmac64(crypt.hashkey(handshake), secret) -socket.send(fd, handshake .. ":" .. crypt.base64encode(hmac)) +send_package(fd, handshake .. ":" .. crypt.base64encode(hmac)) print(readpackage()) print("===>",send_request("fake",0)) -- request again (use last session 0, so the request message is fake) diff --git a/examples/simpledb.lua b/examples/simpledb.lua index a11fd8c5..3a7649e6 100644 --- a/examples/simpledb.lua +++ b/examples/simpledb.lua @@ -16,7 +16,11 @@ end skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) local f = command[string.upper(cmd)] - skynet.ret(skynet.pack(f(...))) + if f then + skynet.ret(skynet.pack(f(...))) + else + error(string.format("Unknown command %s", tostring(cmd))) + end end) skynet.register "SIMPLEDB" end) diff --git a/lualib-src/lua-clientsocket.c b/lualib-src/lua-clientsocket.c index 2676c267..89bfe1f8 100644 --- a/lualib-src/lua-clientsocket.c +++ b/lualib-src/lua-clientsocket.c @@ -75,47 +75,12 @@ lsend(lua_State *L) { size_t sz = 0; int fd = luaL_checkinteger(L,1); const char * msg = luaL_checklstring(L, 2, &sz); - uint8_t tmp[sz + 2]; - if (sz >= 0x10000) { - return luaL_error(L, "package too long %d (16bit limited)", (int)sz); - } - tmp[0] = (sz >> 8) & 0xff; - tmp[1] = sz & 0xff; - memcpy(tmp+2, msg, sz); - block_send(L, fd, (const char *)tmp, (int)sz+2); + block_send(L, fd, msg, (int)sz); return 0; } - -static int -unpack(lua_State *L, uint8_t *buffer, int sz, int n) { - int size = 0; - if (sz >= 2) { - size = buffer[0] << 8 | buffer[1]; - if (size > sz - 2) { - goto _block; - } - } else { - goto _block; - } - ++n; - lua_pushlstring(L, (const char *)buffer+2, size); - lua_rawseti(L, 3, n); - buffer += size + 2; - sz -= size + 2; - return unpack(L, buffer, sz, n); -_block: - lua_pushboolean(L, n==0 ? 0:1); - if (sz == 0) { - lua_pushnil(L); - } else { - lua_pushlstring(L, (const char *)buffer, sz); - } - return 2; -} - /* intger fd string last @@ -132,100 +97,26 @@ struct socket_buffer { }; static int -recv_socket(lua_State *L, char *tmp, struct socket_buffer *result) { +lrecv(lua_State *L) { int fd = luaL_checkinteger(L,1); - size_t sz = 0; - const char * last = lua_tolstring(L,2,&sz); - luaL_checktype(L, 3, LUA_TTABLE); - char * buffer; - int r = recv(fd, tmp, CACHE_SIZE, 0); + char buffer[CACHE_SIZE]; + int r = recv(fd, buffer, CACHE_SIZE, 0); if (r == 0) { + lua_pushliteral(L, ""); // close - return 0; + return 1; } if (r < 0) { if (errno == EAGAIN || errno == EINTR) { - lua_pushboolean(L, 0); - lua_pushvalue(L, 2); - return 2; + return 0; } luaL_error(L, "socket error: %s", strerror(errno)); } - if (sz + r <= CACHE_SIZE) { - buffer = tmp; - memmove(buffer + sz, buffer, r); - memcpy(buffer, last, sz); - } else { - buffer = lua_newuserdata(L, r + sz); - memcpy(buffer, last, sz); - memcpy(buffer + sz, tmp, r); - } - - int i; - int n = lua_rawlen(L, 3); - for (i=1;i<=n;i++) { - lua_pushnil(L); - lua_rawseti(L, 3, i); - } - result->buffer = buffer; - result->sz = r + sz; - return -1; + lua_pushlstring(L, buffer, r); + return 1; } -static int -lrecv(lua_State *L) { - struct socket_buffer sb; - char tmp[CACHE_SIZE]; - int ret = recv_socket(L, tmp, &sb); - if (ret < 0) { - return unpack(L, sb.buffer, sb.sz, 0); - } else { - return ret; - } -} - -static int -unpack_line(lua_State *L, uint8_t *buffer, int sz, int n) { - if (sz == 0) - goto _block; - if (buffer[0] == '\n') { - return unpack_line(L, buffer+1, sz-1, n); - } - int i; - for (i=1;i Date: Mon, 21 Jul 2014 11:47:38 +0800 Subject: [PATCH 25/37] mongo driver support rs --- lualib/mongo.lua | 43 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 36 insertions(+), 7 deletions(-) diff --git a/lualib/mongo.lua b/lualib/mongo.lua index 9760a545..aafea055 100644 --- a/lualib/mongo.lua +++ b/lualib/mongo.lua @@ -75,23 +75,51 @@ local function dispatch_reply(so) return reply_id, succ, result end +local function __parse_addr(addr) + local host, port = string.match(addr, "([^:]+):(.+)") + return host, tonumber(port) +end + local function mongo_auth(mongoc) local user = rawget(mongoc, "username") local pass = rawget(mongoc, "password") - if user == nil or pass == nil then - return - end return function() - assert(mongoc:auth(user, pass)) - -- todo : If mongoc want to connect to replica set, runCommand ismater here, and call mongoc.__sock:changehost + if user ~= nil and pass ~= nil then + assert(mongoc:auth(user, pass)) + end + local rs_data = mongoc:runCommand("ismaster") + if rs_data.ok == 1 then + if rs_data.hosts then + local backup = {} + for _, v in ipairs(rs_data.hosts) do + local host, port = __parse_addr(v) + table.insert(backup, {host = host, port = port}) + end + mongoc.__sock:changebackup(backup) + end + if rs_data.ismaster then + return + else + local host, port = __parse_addr(rs_data.primary) + mongoc.host = host + mongoc.port = port + mongoc.__sock:changehost(host, port) + end + end end end function mongo.client( conf ) + local first = conf + local backup = nil + if conf.rs then + first = conf.rs[1] + backup = conf.rs + end local obj = { - host = conf.host, - port = conf.port or 27017, + host = first.host, + port = first.port or 27017, } obj.__id = 0 @@ -100,6 +128,7 @@ function mongo.client( conf ) port = obj.port, response = dispatch_reply, auth = mongo_auth(obj), + backup = backup, } setmetatable(obj, client_meta) obj.__sock:connect(true) -- try connect only once From 39ff6eb2a66d052436d6095595debb73ae81593e Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Mon, 21 Jul 2014 14:27:50 +0800 Subject: [PATCH 26/37] space to tab --- lualib/mongo.lua | 228 +++++++++++++++++++++++------------------------ 1 file changed, 114 insertions(+), 114 deletions(-) diff --git a/lualib/mongo.lua b/lualib/mongo.lua index aafea055..b1e87ca7 100644 --- a/lualib/mongo.lua +++ b/lualib/mongo.lua @@ -1,123 +1,123 @@ local bson = require "bson" local socket = require "socket" -local socketchannel = require "socketchannel" +local socketchannel = require "socketchannel" local skynet = require "skynet" local driver = require "mongo.driver" -local md5 = require "md5" +local md5 = require "md5" local rawget = rawget local assert = assert -local bson_encode = bson.encode -local bson_encode_order = bson.encode_order -local bson_decode = bson.decode +local bson_encode = bson.encode +local bson_encode_order = bson.encode_order +local bson_decode = bson.decode local empty_bson = bson_encode {} -local mongo = {} +local mongo = {} mongo.null = assert(bson.null) mongo.maxkey = assert(bson.maxkey) mongo.minkey = assert(bson.minkey) mongo.type = assert(bson.type) local mongo_cursor = {} -local cursor_meta = { - __index = mongo_cursor, +local cursor_meta = { + __index = mongo_cursor, } local mongo_client = {} -local client_meta = { - __index = function(self, key) - return rawget(mongo_client, key) or self:getDB(key) +local client_meta = { + __index = function(self, key) + return rawget(mongo_client, key) or self:getDB(key) end, __tostring = function (self) local port_string if self.port then - port_string = ":" .. tostring(self.port) + port_string = ":" .. tostring(self.port) else - port_string = "" + port_string = "" end - return "[mongo client : " .. self.host .. port_string .."]" + return "[mongo client : " .. self.host .. port_string .."]" end, - -- DO NOT need disconnect, because channel will shutdown during gc + -- DO NOT need disconnect, because channel will shutdown during gc } local mongo_db = {} -local db_meta = { - __index = function (self, key) - return rawget(mongo_db, key) or self:getCollection(key) +local db_meta = { + __index = function (self, key) + return rawget(mongo_db, key) or self:getCollection(key) end, __tostring = function (self) - return "[mongo db : " .. self.name .. "]" + return "[mongo db : " .. self.name .. "]" end } local mongo_collection = {} -local collection_meta = { - __index = function(self, key) - return rawget(mongo_collection, key) or self:getCollection(key) - end , +local collection_meta = { + __index = function(self, key) + return rawget(mongo_collection, key) or self:getCollection(key) + end , __tostring = function (self) - return "[mongo collection : " .. self.full_name .. "]" + return "[mongo collection : " .. self.full_name .. "]" end } local function dispatch_reply(so) - local len_reply = so:read(4) - local reply = so:read(driver.length(len_reply)) - local result = { result = {} } - local succ, reply_id, document, cursor_id, startfrom = driver.reply(reply, result.result) - result.document = document + local len_reply = so:read(4) + local reply = so:read(driver.length(len_reply)) + local result = { result = {} } + local succ, reply_id, document, cursor_id, startfrom = driver.reply(reply, result.result) + result.document = document result.cursor_id = cursor_id result.startfrom = startfrom - result.data = reply + result.data = reply return reply_id, succ, result end local function __parse_addr(addr) - local host, port = string.match(addr, "([^:]+):(.+)") - return host, tonumber(port) + local host, port = string.match(addr, "([^:]+):(.+)") + return host, tonumber(port) end local function mongo_auth(mongoc) - local user = rawget(mongoc, "username") - local pass = rawget(mongoc, "password") + local user = rawget(mongoc, "username") + local pass = rawget(mongoc, "password") return function() - if user ~= nil and pass ~= nil then - assert(mongoc:auth(user, pass)) - end - local rs_data = mongoc:runCommand("ismaster") - if rs_data.ok == 1 then - if rs_data.hosts then - local backup = {} - for _, v in ipairs(rs_data.hosts) do - local host, port = __parse_addr(v) - table.insert(backup, {host = host, port = port}) - end - mongoc.__sock:changebackup(backup) - end - if rs_data.ismaster then - return - else - local host, port = __parse_addr(rs_data.primary) - mongoc.host = host - mongoc.port = port - mongoc.__sock:changehost(host, port) - end - end + if user ~= nil and pass ~= nil then + assert(mongoc:auth(user, pass)) + end + local rs_data = mongoc:runCommand("ismaster") + if rs_data.ok == 1 then + if rs_data.hosts then + local backup = {} + for _, v in ipairs(rs_data.hosts) do + local host, port = __parse_addr(v) + table.insert(backup, {host = host, port = port}) + end + mongoc.__sock:changebackup(backup) + end + if rs_data.ismaster then + return + else + local host, port = __parse_addr(rs_data.primary) + mongoc.host = host + mongoc.port = port + mongoc.__sock:changehost(host, port) + end + end end end -function mongo.client( conf ) - local first = conf - local backup = nil - if conf.rs then - first = conf.rs[1] - backup = conf.rs - end - local obj = { +function mongo.client( conf ) + local first = conf + local backup = nil + if conf.rs then + first = conf.rs[1] + backup = conf.rs + end + local obj = { host = first.host, port = first.port or 27017, } @@ -128,10 +128,10 @@ function mongo.client( conf ) port = obj.port, response = dispatch_reply, auth = mongo_auth(obj), - backup = backup, + backup = backup, } setmetatable(obj, client_meta) - obj.__sock:connect(true) -- try connect only once + obj.__sock:connect(true) -- try connect only once return obj end @@ -139,32 +139,32 @@ function mongo_client:getDB(dbname) local db = { connection = self, name = dbname, - full_name = dbname, + full_name = dbname, database = false, - __cmd = dbname .. "." .. "$cmd", + __cmd = dbname .. "." .. "$cmd", } - db.database = db + db.database = db - return setmetatable(db, db_meta) + return setmetatable(db, db_meta) end function mongo_client:disconnect() if self.__sock then local so = self.__sock - self.__sock = false + self.__sock = false so:close() end end function mongo_client:genId() local id = self.__id + 1 - self.__id = id + self.__id = id return id end function mongo_client:runCommand(...) if not self.admin then - self.admin = self:getDB "admin" + self.admin = self:getDB "admin" end return self.admin:runCommand(...) end @@ -176,14 +176,14 @@ function mongo_client:auth(user,password) return false end - local key = md5.sumhexa(string.format("%s%s%s",result.nonce,user,password)) + local key = md5.sumhexa(string.format("%s%s%s",result.nonce,user,password)) local result= self:runCommand ("authenticate",1,"user",user,"nonce",result.nonce,"key",key) - return result.ok == 1 + return result.ok == 1 end function mongo_client:logout() local result = self:runCommand "logout" - return result.ok == 1 + return result.ok == 1 end function mongo_db:runCommand(cmd,cmd_v,...) @@ -196,18 +196,18 @@ function mongo_db:runCommand(cmd,cmd_v,...) else bson_cmd = bson_encode_order(cmd,cmd_v,...) end - local pack = driver.query(request_id, 0, self.__cmd, 0, 1, bson_cmd) - -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) - local req = sock:request(pack, request_id) - local doc = req.document + local pack = driver.query(request_id, 0, self.__cmd, 0, 1, bson_cmd) + -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) + local req = sock:request(pack, request_id) + local doc = req.document return bson_decode(doc) end function mongo_db:getCollection(collection) - local col = { + local col = { connection = self.connection, name = collection, - full_name = self.full_name .. "." .. collection, + full_name = self.full_name .. "." .. collection, database = self.database, } self[collection] = setmetatable(col, collection_meta) @@ -218,20 +218,20 @@ mongo_collection.getCollection = mongo_db.getCollection function mongo_collection:insert(doc) if doc._id == nil then - doc._id = bson.objectid() + doc._id = bson.objectid() end local sock = self.connection.__sock local pack = driver.insert(0, self.full_name, bson_encode(doc)) - -- flags support 1: ContinueOnError + -- flags support 1: ContinueOnError sock:request(pack) end function mongo_collection:batch_insert(docs) - for i=1,#docs do + for i=1,#docs do if docs[i]._id == nil then - docs[i]._id = bson.objectid() + docs[i]._id = bson.objectid() end - docs[i] = bson_encode(docs[i]) + docs[i] = bson_encode(docs[i]) end local sock = self.connection.__sock local pack = driver.insert(0, self.full_name, docs) @@ -239,7 +239,7 @@ function mongo_collection:batch_insert(docs) end function mongo_collection:update(selector,update,upsert,multi) - local flags = (upsert and 1 or 0) + (multi and 2 or 0) + local flags = (upsert and 1 or 0) + (multi and 2 or 0) local sock = self.connection.__sock local pack = driver.update(self.full_name, flags, bson_encode(selector), bson_encode(update)) sock:request(pack) @@ -255,24 +255,24 @@ function mongo_collection:findOne(query, selector) local conn = self.connection local request_id = conn:genId() local sock = conn.__sock - local pack = driver.query(request_id, 0, self.full_name, 0, 1, query and bson_encode(query) or empty_bson, selector and bson_encode(selector)) - -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) - local req = sock:request(pack, request_id) - local doc = req.document + local pack = driver.query(request_id, 0, self.full_name, 0, 1, query and bson_encode(query) or empty_bson, selector and bson_encode(selector)) + -- we must hold req (req.data), because req.document is a lightuserdata, it's a pointer to the string (req.data) + local req = sock:request(pack, request_id) + local doc = req.document return bson_decode(doc) end function mongo_collection:find(query, selector) return setmetatable( { __collection = self, - __query = query and bson_encode(query) or empty_bson, + __query = query and bson_encode(query) or empty_bson, __selector = selector and bson_encode(selector), - __ptr = nil, + __ptr = nil, __data = nil, __cursor = nil, __document = {}, - __flags = 0, - } , cursor_meta) + __flags = 0, + } , cursor_meta) end function mongo_cursor:hasNext() @@ -285,42 +285,42 @@ function mongo_cursor:hasNext() local sock = conn.__sock local pack if self.__data == nil then - pack = driver.query(request_id, self.__flags, self.__collection.full_name,0,0,self.__query,self.__selector) + pack = driver.query(request_id, self.__flags, self.__collection.full_name,0,0,self.__query,self.__selector) else if self.__cursor then pack = driver.more(request_id, self.__collection.full_name,0,self.__cursor) else -- no more - self.__document = nil - self.__data = nil + self.__document = nil + self.__data = nil return false end end local ok, result = pcall(sock.request,sock,pack, request_id) - local doc = result.document + local doc = result.document local cursor = result.cursor_id if ok then if doc then - self.__document = result.result - self.__data = result.data + self.__document = result.result + self.__data = result.data self.__ptr = 1 - self.__cursor = cursor + self.__cursor = cursor return true else - self.__document = nil - self.__data = nil - self.__cursor = nil + self.__document = nil + self.__data = nil + self.__cursor = nil return false end else - self.__document = nil - self.__data = nil - self.__cursor = nil + self.__document = nil + self.__data = nil + self.__cursor = nil if doc then - local err = bson_decode(doc) + local err = bson_decode(doc) error(err["$err"]) else error("Reply from mongod error") @@ -333,11 +333,11 @@ end function mongo_cursor:next() if self.__ptr == nil then - error "Call hasNext first" + error "Call hasNext first" end - local r = bson_decode(self.__document[self.__ptr]) - self.__ptr = self.__ptr + 1 - if self.__ptr > #self.__document then + local r = bson_decode(self.__document[self.__ptr]) + self.__ptr = self.__ptr + 1 + if self.__ptr > #self.__document then self.__ptr = nil end From 27ac1642e4cd2bf58734be52acd32d0024c2bfd1 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Mon, 21 Jul 2014 14:29:11 +0800 Subject: [PATCH 27/37] update history --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index a769e314..3d3c26b7 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,6 +3,7 @@ Dev version * skynet.exit will quit service immediately. * Add snax.gateserver, snax.loginserver, snax.msgserver * Simplify clientsocket lib +* mongo driver support replica set v0.4.2 (2014-7-14) ----------- From 7bdbdcd0544c159ac0e0d3913f29b87cdccc96d6 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Mon, 21 Jul 2014 14:33:29 +0800 Subject: [PATCH 28/37] update history --- HISTORY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/HISTORY.md b/HISTORY.md index 3d3c26b7..615bc346 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -4,6 +4,7 @@ Dev version * Add snax.gateserver, snax.loginserver, snax.msgserver * Simplify clientsocket lib * mongo driver support replica set +* config file support read from ENV v0.4.2 (2014-7-14) ----------- From 362d6822bf6d16ea73b93b0cf34229ddb4492293 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Mon, 21 Jul 2014 14:37:22 +0800 Subject: [PATCH 29/37] remove dup line --- lualib-src/lua-bson.c | 1 - 1 file changed, 1 deletion(-) diff --git a/lualib-src/lua-bson.c b/lualib-src/lua-bson.c index 07fb77e2..70619340 100644 --- a/lualib-src/lua-bson.c +++ b/lualib-src/lua-bson.c @@ -1180,7 +1180,6 @@ luaopen_bson(lua_State *L) { { "timestamp", ltimestamp }, { "regex", lregex }, { "binary", lbinary }, - { "regex", lregex }, { "objectid", lobjectid }, { "decode", ldecode }, { NULL, NULL }, From 932b2943dc270386ce4a5d5d85f4a24bd3c87e0a Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Tue, 22 Jul 2014 21:38:44 +0800 Subject: [PATCH 30/37] simple httpd --- examples/simpleweb.lua | 50 +++++++++ lualib/http/httpd.lua | 190 +++++++++++++++++++++++++++++++++++ lualib/http/sockethelper.lua | 45 +++++++++ 3 files changed, 285 insertions(+) create mode 100644 examples/simpleweb.lua create mode 100644 lualib/http/httpd.lua create mode 100644 lualib/http/sockethelper.lua diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua new file mode 100644 index 00000000..f0c2855b --- /dev/null +++ b/examples/simpleweb.lua @@ -0,0 +1,50 @@ +local skynet = require "skynet" +local socket = require "socket" +local httpd = require "http.httpd" +local sockethelper = require "http.sockethelper" + +local mode = ... + +if mode == "agent" then + +skynet.start(function() + skynet.dispatch("lua", function (_,_,id) + socket.start(id) + local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id)) + if code then + if code ~= 200 then + httpd.write_response(sockethelper.writefunc(id), code) + else + httpd.write_response(sockethelper.writefunc(id), code , "Hello world") + end + else + if url == sockethelper.socket_error then + skynet.error("socket closed") + else + skynet.error(url) + end + end + socket.close(id) + end) +end) + +else + +skynet.start(function() + local agent = {} + for i= 1, 20 do + agent[i] = skynet.newservice(SERVICE_NAME, "agent") + end + local balance = 1 + local id = socket.listen("0.0.0.0", 8001) + socket.start(id , function(id, addr) + skynet.error(string.format("%s connected, pass it to agent :%08x", addr, agent[balance])) + skynet.send(agent[balance], "lua", id) + balance = balance + 1 + if balance > #agent then + balance = 1 + end + end) +end) + +end \ No newline at end of file diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua new file mode 100644 index 00000000..4eb9ab40 --- /dev/null +++ b/lualib/http/httpd.lua @@ -0,0 +1,190 @@ +local table = table + +local httpd = {} +local READLIMIT = 8192 -- limit bytes per read + +local http_status_msg = { + [100] = "Continue", + [101] = "Switching Protocols", + [200] = "OK", + [201] = "Created", + [202] = "Accepted", + [203] = "Non-Authoritative Information", + [204] = "No Content", + [205] = "Reset Content", + [206] = "Partial Content", + [300] = "Multiple Choices", + [301] = "Moved Permanently", + [302] = "Found", + [303] = "See Other", + [304] = "Not Modified", + [305] = "Use Proxy", + [307] = "Temporary Redirect", + [400] = "Bad Request", + [401] = "Unauthorized", + [402] = "Payment Required", + [403] = "Forbidden", + [404] = "Not Found", + [405] = "Method Not Allowed", + [406] = "Not Acceptable", + [407] = "Proxy Authentication Required", + [408] = "Request Time-out", + [409] = "Conflict", + [410] = "Gone", + [411] = "Length Required", + [412] = "Precondition Failed", + [413] = "Request Entity Too Large", + [414] = "Request-URI Too Large", + [415] = "Unsupported Media Type", + [416] = "Requested range not satisfiable", + [417] = "Expectation Failed", + [500] = "Internal Server Error", + [501] = "Not Implemented", + [502] = "Bad Gateway", + [503] = "Service Unavailable", + [504] = "Gateway Time-out", + [505] = "HTTP Version not supported", +} + +local function recvheader(readline, header) + local line = readline() + if line == "" then + return header + end + + header = header or {} + + local name, value + repeat + if line:byte(1) == 9 then -- tab, append last line + header[name] = header[name] .. line:sub(2) + else + name, value = line:match "^(.-):%s*(.*)" + assert(name and value) + name = name:lower() + if header[name] then + header[name] = header[name] .. ", " .. value + else + header[name] = value + end + line = readline() + end + until line == "" + + return header +end + +local function recvbody(readbytes, length) + if length < READLIMIT then + return readbytes(length) + end + local tmp = {} + while true do + if length <= READLIMIT then + table.insert(tmp, readbytes(length)) + break + end + table.insert(tmp, readbytes(READLIMIT)) + length = length - READLIMIT + end + return table.concat(tmp) +end + +local function recvchunkedbody(readline, readbytes, header) + local size = assert(tonumber(readline(),16)) + local body = recvbody(readbytes,size) + assert(readbytes(2) == "\r\n") + size = assert(tonumber(readline(),16)) + if size > 0 then + local bodys = { body } + repeat + table.insert(bodys, recvbody(readbytes,size)) + assert(readbytes(2) == "\r\n") + size = assert(tonumber(readline(),16)) + until size <= 0 + body = table.concat(bodys) + end + assert(readbytes(2) == "\r\n") + header = recvheader(readline, header) + return body, header +end + +local function readall(readline, readbytes) + local request = readline() + local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$" + assert(method and url and httpver) + httpver = assert(tonumber(httpver)) + if httpver < 1.0 or httpver > 1.1 then + return 505 -- HTTP Version not supported + end + local header = recvheader(readline) + local length, mode + if header then + length = header["content-length"] + if length then + length = tonumber(length) + end + mode = header["transfer-encoding"] + if mode then + if mode ~= "identity" or mode ~= "chunked" then + return 501 -- Not Implemented + end + end + end + + local body + if mode == "chunked" then + body, header = recvchunkedbody(readline, readbytes, header) + else + -- identity mode + if length then + body = readbody(readbytes, length) + end + end + + return 200, url, method, header, body +end + +function httpd.read_request(readfunc) + local readline = assert(readfunc.readline) + local readbytes = assert(readfunc.readbytes) + local ok, code, url, method, header, body = pcall(readall, readline, readbytes) + if ok then + return code, url, method, header, body + else + return nil, code + end +end + +function httpd.write_response(writefunc, statuscode, bodyfunc, header) + local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode]) + writefunc(statusline) + if header then + for k,v in pairs(header) do + writefunc(string.format("%s: %s\r\n", k,v)) + end + end + local t = type(bodyfunc) + if t == "string" then + writefunc(string.format("content-length: %d\r\n\r\n", #bodyfunc)) + writefunc(bodyfunc) + elseif t == "function" then + writefunc("transfer-encoding: chunked\r\n") + while true do + local s = bodyfunc() + if s then + if s ~= "" then + writefunc(string.format("\r\n%x\r\n", #s)) + writefunc(s) + end + else + writefunc("\r\n0\r\n\r\n") + end + end + else + assert(t == "nil") + writefunc("\r\n") + end +end + +return httpd diff --git a/lualib/http/sockethelper.lua b/lualib/http/sockethelper.lua new file mode 100644 index 00000000..64b82c7e --- /dev/null +++ b/lualib/http/sockethelper.lua @@ -0,0 +1,45 @@ +local socket = require "socket" + +local readline = socket.readline +local readbytes = socket.read +local writebytes = socket.write + +local sockethelper = {} +local socket_error = {} + +sockethelper.socket_error = socket_error + +function sockethelper.readfunc(fd) + local helper = {} + + function helper.readline() + local ret = readline(fd, "\r\n") + if ret then + return ret + else + error(socket_error) + end + end + + function helper.readbytes(sz) + local ret = readbytes(fd, sz) + if ret then + return ret + else + error(socket_error) + end + end + + return helper +end + +function sockethelper.writefunc(fd) + return function(content) + local ok = writebytes(fd, content) + if not ok then + error(socket_error) + end + end +end + +return sockethelper \ No newline at end of file From d2ad63da817f3679cd06e9849344a61e6aeb6dba Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Tue, 22 Jul 2014 21:52:18 +0800 Subject: [PATCH 31/37] read_request must return header table --- lualib/http/httpd.lua | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua index 4eb9ab40..3ced98c6 100644 --- a/lualib/http/httpd.lua +++ b/lualib/http/httpd.lua @@ -52,8 +52,6 @@ local function recvheader(readline, header) return header end - header = header or {} - local name, value repeat if line:byte(1) == 9 then -- tab, append last line @@ -117,18 +115,15 @@ local function readall(readline, readbytes) if httpver < 1.0 or httpver > 1.1 then return 505 -- HTTP Version not supported end - local header = recvheader(readline) - local length, mode - if header then - length = header["content-length"] - if length then - length = tonumber(length) - end - mode = header["transfer-encoding"] - if mode then - if mode ~= "identity" or mode ~= "chunked" then - return 501 -- Not Implemented - end + local header = recvheader(readline, {}) + local length = header["content-length"] + if length then + length = tonumber(length) + end + local mode = header["transfer-encoding"] + if mode then + if mode ~= "identity" or mode ~= "chunked" then + return 501 -- Not Implemented end end @@ -157,7 +152,7 @@ function httpd.read_request(readfunc) end function httpd.write_response(writefunc, statuscode, bodyfunc, header) - local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode]) + local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode] or "") writefunc(statusline) if header then for k,v in pairs(header) do From 551d5048c406c22c59293289a516e6584c45e4e5 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Tue, 22 Jul 2014 23:04:37 +0800 Subject: [PATCH 32/37] simple url parser --- examples/simpleweb.lua | 16 +++++++++++++++- lualib/http/url.lua | 28 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 lualib/http/url.lua diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua index f0c2855b..bfd247eb 100644 --- a/examples/simpleweb.lua +++ b/examples/simpleweb.lua @@ -2,6 +2,7 @@ local skynet = require "skynet" local socket = require "socket" local httpd = require "http.httpd" local sockethelper = require "http.sockethelper" +local urllib = require "http.url" local mode = ... @@ -15,7 +16,20 @@ skynet.start(function() if code ~= 200 then httpd.write_response(sockethelper.writefunc(id), code) else - httpd.write_response(sockethelper.writefunc(id), code , "Hello world") + local tmp = {} + if header.host then + table.insert(tmp, string.format("host: %s", header.host)) + end + local path, query = urllib.parse(url) + table.insert(tmp, string.format("path: %s", path)) + if query then + local q = urllib.parse_query(query) + for k, v in pairs(q) do + table.insert(tmp, string.format("query: %s= %s", k,v)) + end + end + + httpd.write_response(sockethelper.writefunc(id), code , table.concat(tmp,"\n")) end else if url == sockethelper.socket_error then diff --git a/lualib/http/url.lua b/lualib/http/url.lua new file mode 100644 index 00000000..ae74b099 --- /dev/null +++ b/lualib/http/url.lua @@ -0,0 +1,28 @@ +local url = {} + +local function decode_func(c) + return string.char(tonumber(c, 16)) +end + +local function decode(str) + local str = str:gsub('+', ' ') + return str:gsub("%%(..)", decode_func) +end + +function url.parse(u) + local path,query = u:match "([^?]*)%??(.*)" + if path then + path = decode(path) + end + return path, query +end + +function url.parse_query(q) + local r = {} + for k,v in q:gmatch "(.-)=([^&]*)&?" do + r[decode(k)] = decode(v) + end + return r +end + +return url From 18f20425a098b969f17903b8ba938fa3d30a1d39 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 23 Jul 2014 12:19:48 +0800 Subject: [PATCH 33/37] httpd don't need readline now --- lualib-src/lua-socket.c | 5 - lualib/http/httpd.lua | 177 +++++++++++++++++++++++++---------- lualib/http/sockethelper.lua | 16 +--- lualib/snax/loginserver.lua | 9 +- lualib/socket.lua | 55 +++++++---- test/testsocket.lua | 7 +- 6 files changed, 176 insertions(+), 93 deletions(-) diff --git a/lualib-src/lua-socket.c b/lualib-src/lua-socket.c index 8bfefc0e..916fa7a9 100644 --- a/lualib-src/lua-socket.c +++ b/lualib-src/lua-socket.c @@ -23,7 +23,6 @@ struct buffer_node { }; struct socket_buffer { - int limit; int size; int offset; struct buffer_node *head; @@ -66,7 +65,6 @@ lnewpool(lua_State *L, int sz) { static int lnewbuffer(lua_State *L) { struct socket_buffer * sb = lua_newuserdata(L, sizeof(*sb)); - sb->limit = luaL_optint(L,1,BUFFER_LIMIT); sb->size = 0; sb->offset = 0; sb->head = NULL; @@ -129,9 +127,6 @@ lpushbuffer(lua_State *L) { sb->size += sz; lua_pushinteger(L, sb->size); - if (sb->limit > 0 && sb->size > sb->limit) { - return luaL_error(L, "buffer overflow (limit = %d, size = %d)", sb->limit, sb->size); - } return 1; } diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua index 3ced98c6..5333bfd5 100644 --- a/lualib/http/httpd.lua +++ b/lualib/http/httpd.lua @@ -1,7 +1,6 @@ local table = table local httpd = {} -local READLIMIT = 8192 -- limit bytes per read local http_status_msg = { [100] = "Continue", @@ -46,76 +45,150 @@ local http_status_msg = { [505] = "HTTP Version not supported", } -local function recvheader(readline, header) - local line = readline() - if line == "" then - return header +local function recvheader(readbytes, limit, lines, last) + local idx = 1 + local len = 0 + local header = last + while true do + header = header .. readbytes() + if #header + len > limit then + return + end + while true do + local f,e = header:find("\r\n",1, true) + if f then + if f == 1 then + for i = idx, #lines do + lines[i] = nil + end + return header:sub(3) + end + lines[idx] = header:sub(1, f-1) + idx = idx + 1 + len = len + f-1 + header = header:sub(e+1) + else + break + end + end end +end +local function parseheader(lines, from, header) local name, value - repeat + for i=from,#lines do + local line = lines[i] if line:byte(1) == 9 then -- tab, append last line + if name == nil then + return + end header[name] = header[name] .. line:sub(2) else name, value = line:match "^(.-):%s*(.*)" - assert(name and value) + if name == nil or value == nil then + return + end name = name:lower() if header[name] then header[name] = header[name] .. ", " .. value else header[name] = value end - line = readline() end - until line == "" - + end return header end -local function recvbody(readbytes, length) - if length < READLIMIT then - return readbytes(length) - end - local tmp = {} +local function chunksize(readbytes, body) while true do - if length <= READLIMIT then - table.insert(tmp, readbytes(length)) + if #body > 128 then + return + end + body = body .. readbytes() + local f,e = body:find("\r\n",1,true) + if f then + return tonumber(body:sub(1,f-1),16), body:sub(e+1) + end + end +end + +local function readcrln(readbytes, body) + if #body > 2 then + if body:sub(1,2) ~= "\r\n" then + return + end + return body:sub(3) + else + body = body .. readbytes(2-#body) + if body ~= "\r\n" then + return + end + return "" + end +end + +local tmpline = {} + +local function recvchunkedbody(readbytes, bodylimit, header, body) + local result = "" + local size = 0 + + while true do + local sz + sz , body = chunksize(readbytes, body) + if not sz then + return + end + if sz == 0 then break end - table.insert(tmp, readbytes(READLIMIT)) - length = length - READLIMIT + size = size + sz + if bodylimit and size > bodylimit then + return + end + if #body >= sz then + result = result .. body:sub(1,sz) + body = body:sub(sz+1) + else + result = result .. body .. readbytes(sz - #body) + body = "" + end + body = readcrln(readbytes, body) + if not body then + return + end end - return table.concat(tmp) + + body = readcrln(readbytes, body) + if not body then + return + end + body = recvheader(readbytes, 8192, tmpline, body) + if not body then + return + end + + header = parseheader(tmpline,1,header) + + return result, header end -local function recvchunkedbody(readline, readbytes, header) - local size = assert(tonumber(readline(),16)) - local body = recvbody(readbytes,size) - assert(readbytes(2) == "\r\n") - size = assert(tonumber(readline(),16)) - if size > 0 then - local bodys = { body } - repeat - table.insert(bodys, recvbody(readbytes,size)) - assert(readbytes(2) == "\r\n") - size = assert(tonumber(readline(),16)) - until size <= 0 - body = table.concat(bodys) +local function readall(readbytes, bodylimit) + local body = recvheader(readbytes, 8192, tmpline, "") + if not body then + return 413 -- Request Entity Too Large end - assert(readbytes(2) == "\r\n") - header = recvheader(readline, header) - return body, header -end - -local function readall(readline, readbytes) - local request = readline() + local request = assert(tmpline[1]) local method, url, httpver = request:match "^(%a+)%s+(.-)%s+HTTP/([%d%.]+)$" assert(method and url and httpver) httpver = assert(tonumber(httpver)) if httpver < 1.0 or httpver > 1.1 then return 505 -- HTTP Version not supported end - local header = recvheader(readline, {}) + local header = parseheader(tmpline,2,{}) + if not header then + return 400 -- Bad request + end local length = header["content-length"] if length then length = tonumber(length) @@ -127,23 +200,31 @@ local function readall(readline, readbytes) end end - local body if mode == "chunked" then - body, header = recvchunkedbody(readline, readbytes, header) + body, header = recvchunkedbody(readbytes, bodylimit, header, body) + if not body then + return 413 + end else -- identity mode if length then - body = readbody(readbytes, length) + if length > bodylimit then + return 413 + end + if #body >= length then + body = body:sub(1,length) + else + local padding = readbytes(length - #body) + body = body .. padding + end end end return 200, url, method, header, body end -function httpd.read_request(readfunc) - local readline = assert(readfunc.readline) - local readbytes = assert(readfunc.readbytes) - local ok, code, url, method, header, body = pcall(readall, readline, readbytes) +function httpd.read_request(readbytes, bodylimit) + local ok, code, url, method, header, body = pcall(readall, readbytes, bodylimit) if ok then return code, url, method, header, body else diff --git a/lualib/http/sockethelper.lua b/lualib/http/sockethelper.lua index 64b82c7e..14668c8b 100644 --- a/lualib/http/sockethelper.lua +++ b/lualib/http/sockethelper.lua @@ -1,6 +1,5 @@ local socket = require "socket" -local readline = socket.readline local readbytes = socket.read local writebytes = socket.write @@ -10,18 +9,7 @@ local socket_error = {} sockethelper.socket_error = socket_error function sockethelper.readfunc(fd) - local helper = {} - - function helper.readline() - local ret = readline(fd, "\r\n") - if ret then - return ret - else - error(socket_error) - end - end - - function helper.readbytes(sz) + return function (sz) local ret = readbytes(fd, sz) if ret then return ret @@ -29,8 +17,6 @@ function sockethelper.readfunc(fd) error(socket_error) end end - - return helper end function sockethelper.writefunc(fd) diff --git a/lualib/snax/loginserver.lua b/lualib/snax/loginserver.lua index 1ad5b70e..c5de769f 100644 --- a/lualib/snax/loginserver.lua +++ b/lualib/snax/loginserver.lua @@ -47,14 +47,15 @@ local function write(fd, text) end local function launch_slave(auth_handler) - -- set socket buffer limit (8K) - -- If the attacker send large package, close the socket - socket.limit(8192) - local function auth(fd, addr) fd = assert(tonumber(fd)) skynet.error(string.format("connect from %s (fd = %d)", addr, fd)) socket.start(fd) + + -- set socket buffer limit (8K) + -- If the attacker send large package, close the socket + socket.limit(fd, 8192) + local challenge = crypt.randomkey() write(fd, crypt.base64encode(challenge).."\n") diff --git a/lualib/socket.lua b/lualib/socket.lua index 28268747..f54d2175 100644 --- a/lualib/socket.lua +++ b/lualib/socket.lua @@ -2,7 +2,6 @@ local driver = require "socketdriver" local skynet = require "skynet" local assert = assert -local buffer_limit = -1 local socket = {} -- api local buffer_pool = {} -- store all message buffer object local socket_pool = setmetatable( -- store all socket object @@ -48,13 +47,7 @@ socket_message[1] = function(id, size, data) return end - local ok , sz = pcall(driver.push, s.buffer, buffer_pool, data, size) - if not ok then - skynet.error("socket: error on ", id , sz) - driver.clear(s.buffer,buffer_pool) - driver.close(id) - return - end + local sz = driver.push(s.buffer, buffer_pool, data, size) local rr = s.read_required local rrt = type(rr) if rrt == "number" then @@ -63,11 +56,19 @@ socket_message[1] = function(id, size, data) s.read_required = nil wakeup(s) end - elseif rrt == "string" then - -- read line - if driver.readline(s.buffer,nil,rr) then - s.read_required = nil - wakeup(s) + else + if s.buffer_limit and sz > s.buffer_limit then + skynet.error(string.format("socket buffer overlow: fd=%d size=%d", id , sz)) + driver.clear(s.buffer,buffer_pool) + driver.close(id) + return + end + if rrt == "string" then + -- read line + if driver.readline(s.buffer,nil,rr) then + s.read_required = nil + wakeup(s) + end end end end @@ -130,7 +131,7 @@ skynet.register_protocol { local function connect(id, func) local newbuffer if func == nil then - newbuffer = driver.buffer(buffer_limit) + newbuffer = driver.buffer() end local s = { id = id, @@ -206,6 +207,27 @@ end function socket.read(id, sz) local s = socket_pool[id] assert(s) + if sz == nil then + -- read some bytes + local ret = driver.readall(s.buffer, buffer_pool) + if ret ~= "" then + return ret + end + + if not s.connected then + return false, ret + end + assert(not s.read_required) + s.read_required = 0 + suspend(s) + ret = driver.readall(s.buffer, buffer_pool) + if ret ~= "" then + return ret + else + return false, ret + end + end + local ret = driver.pop(s.buffer, buffer_pool, sz) if ret then return ret @@ -325,8 +347,9 @@ function socket.abandon(id) socket_pool[id] = nil end -function socket.limit(limit) - buffer_limit = limit +function socket.limit(id, limit) + local s = assert(socket_pool[id]) + s.buffer_limit = limit end return socket diff --git a/test/testsocket.lua b/test/testsocket.lua index 05313a11..3893cd61 100644 --- a/test/testsocket.lua +++ b/test/testsocket.lua @@ -7,9 +7,9 @@ local function echo(id) socket.start(id) while true do - local str = socket.readline(id,"\n") + local str = socket.read(id) if str then - socket.write(id, str .. "\n") + socket.write(id, str) else socket.close(id) return @@ -21,9 +21,6 @@ if mode == "agent" then id = tonumber(id) skynet.start(function() - -- A small limit, if socket buffer overflow, close the client - socket.limit(64) - skynet.fork(function() echo(id) skynet.exit() From 0188e263ddbf839cca5376ec8480b77391bbad13 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 23 Jul 2014 13:34:30 +0800 Subject: [PATCH 34/37] recv header all, and then split with \r\n --- lualib/http/httpd.lua | 48 +++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua index 5333bfd5..3698bf00 100644 --- a/lualib/http/httpd.lua +++ b/lualib/http/httpd.lua @@ -45,33 +45,37 @@ local http_status_msg = { [505] = "HTTP Version not supported", } -local function recvheader(readbytes, limit, lines, last) - local idx = 1 - local len = 0 - local header = last - while true do - header = header .. readbytes() - if #header + len > limit then - return - end +local function recvheader(readbytes, limit, lines, header) + local result + local e = header:find("\r\n\r\n", 1, true) + if e then + result = header:sub(e+4) + else while true do - local f,e = header:find("\r\n",1, true) - if f then - if f == 1 then - for i = idx, #lines do - lines[i] = nil - end - return header:sub(3) - end - lines[idx] = header:sub(1, f-1) - idx = idx + 1 - len = len + f-1 - header = header:sub(e+1) - else + local bytes = readbytes() + header = header .. bytes + if #header > limit then + return + end + e = header:find("\r\n\r\n", -#bytes-3, true) + if e then + result = header:sub(e+4) break end end end + local idx = 1 + for v in header:gmatch("(.-)\r\n") do + if v == "" then + break + end + lines[idx] = v + idx = idx + 1 + end + for i = idx, #lines do + lines[i] = nil + end + return result end local function parseheader(lines, from, header) From db25d1acc67342641c991aeabbe668a5c70595d4 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 23 Jul 2014 14:04:32 +0800 Subject: [PATCH 35/37] update history --- HISTORY.md | 1 + examples/simpleweb.lua | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 615bc346..c5dcfadc 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,7 @@ Dev version * Simplify clientsocket lib * mongo driver support replica set * config file support read from ENV +* add simple httpd (see examples/simpleweb.lua) v0.4.2 (2014-7-14) ----------- diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua index bfd247eb..4bb175d0 100644 --- a/examples/simpleweb.lua +++ b/examples/simpleweb.lua @@ -11,7 +11,8 @@ if mode == "agent" then skynet.start(function() skynet.dispatch("lua", function (_,_,id) socket.start(id) - local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id)) + -- limit request body size to 8192 (you can pass nil to unlimit) + local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id), 8192) if code then if code ~= 200 then httpd.write_response(sockethelper.writefunc(id), code) From 19e6462376ed82a102dbacbe4be29c3d3b80e3d2 Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Wed, 23 Jul 2014 14:27:42 +0800 Subject: [PATCH 36/37] httpd.write_response capture socket error --- examples/simpleweb.lua | 13 ++++++++++--- lualib/http/httpd.lua | 10 +++++++--- lualib/http/sockethelper.lua | 4 ++-- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua index 4bb175d0..6e6e6438 100644 --- a/examples/simpleweb.lua +++ b/examples/simpleweb.lua @@ -8,6 +8,14 @@ local mode = ... if mode == "agent" then +local function response(id, ...) + local ok, err = httpd.write_response(sockethelper.writefunc(id), ...) + if not ok then + -- if err == sockethelper.socket_error , that means socket closed. + skynet.error(string.format("fd = %d, %s", id, err)) + end +end + skynet.start(function() skynet.dispatch("lua", function (_,_,id) socket.start(id) @@ -15,7 +23,7 @@ skynet.start(function() local code, url, method, header, body = httpd.read_request(sockethelper.readfunc(id), 8192) if code then if code ~= 200 then - httpd.write_response(sockethelper.writefunc(id), code) + response(id, code) else local tmp = {} if header.host then @@ -29,8 +37,7 @@ skynet.start(function() table.insert(tmp, string.format("query: %s= %s", k,v)) end end - - httpd.write_response(sockethelper.writefunc(id), code , table.concat(tmp,"\n")) + response(id, code, table.concat(tmp,"\n")) end else if url == sockethelper.socket_error then diff --git a/lualib/http/httpd.lua b/lualib/http/httpd.lua index 3698bf00..1fe507de 100644 --- a/lualib/http/httpd.lua +++ b/lualib/http/httpd.lua @@ -227,8 +227,8 @@ local function readall(readbytes, bodylimit) return 200, url, method, header, body end -function httpd.read_request(readbytes, bodylimit) - local ok, code, url, method, header, body = pcall(readall, readbytes, bodylimit) +function httpd.read_request(...) + local ok, code, url, method, header, body = pcall(readall, ...) if ok then return code, url, method, header, body else @@ -236,7 +236,7 @@ function httpd.read_request(readbytes, bodylimit) end end -function httpd.write_response(writefunc, statuscode, bodyfunc, header) +local function writeall(writefunc, statuscode, bodyfunc, header) local statusline = string.format("HTTP/1.1 %03d %s\r\n", statuscode, http_status_msg[statuscode] or "") writefunc(statusline) if header then @@ -267,4 +267,8 @@ function httpd.write_response(writefunc, statuscode, bodyfunc, header) end end +function httpd.write_response(...) + return pcall(writeall, ...) +end + return httpd diff --git a/lualib/http/sockethelper.lua b/lualib/http/sockethelper.lua index 14668c8b..89c7ec93 100644 --- a/lualib/http/sockethelper.lua +++ b/lualib/http/sockethelper.lua @@ -4,7 +4,7 @@ local readbytes = socket.read local writebytes = socket.write local sockethelper = {} -local socket_error = {} +local socket_error = setmetatable({} , { __tostring = function() return "[Socket Error]" end }) sockethelper.socket_error = socket_error @@ -28,4 +28,4 @@ function sockethelper.writefunc(fd) end end -return sockethelper \ No newline at end of file +return sockethelper From a56b6aa4258b782772be9f5dab581c228469b5af Mon Sep 17 00:00:00 2001 From: Cloud Wu Date: Fri, 25 Jul 2014 21:10:11 +0800 Subject: [PATCH 37/37] bugfix: don't use string.format to concat binary string --- examples/client.lua | 7 +++---- examples/login/client.lua | 23 ++++++++++------------- examples/simpleweb.lua | 2 ++ 3 files changed, 15 insertions(+), 17 deletions(-) diff --git a/examples/client.lua b/examples/client.lua index d5bace7f..14342556 100644 --- a/examples/client.lua +++ b/examples/client.lua @@ -8,10 +8,9 @@ local fd = assert(socket.connect("127.0.0.1", 8888)) local function send_package(fd, pack) local size = #pack - local package = string.format("%c%c%s", - bit32.extract(size,8,8), - bit32.extract(size,0,8), - pack) + local package = string.char(bit32.extract(size,8,8)) .. + string.char(bit32.extract(size,0,8)).. + pack socket.send(fd, package) end diff --git a/examples/login/client.lua b/examples/login/client.lua index dfd66267..2dfdf25b 100644 --- a/examples/login/client.lua +++ b/examples/login/client.lua @@ -93,15 +93,13 @@ print("login ok, subid=", subid) local function send_request(v, session) local size = #v + 4 - local package = string.format("%c%c%s%c%c%c%c", - bit32.extract(size,8,8), - bit32.extract(size,0,8), - v, - bit32.extract(session,24,8), - bit32.extract(session,16,8), - bit32.extract(session,8,8), - bit32.extract(session,0,8) - ) + local package = string.char(bit32.extract(size,8,8)).. + string.char(bit32.extract(size,0,8)).. + v.. + string.char(bit32.extract(session,24,8)).. + string.char(bit32.extract(session,16,8)).. + string.char(bit32.extract(session,8,8)).. + string.char(bit32.extract(session,0,8)) socket.send(fd, package) return v, session @@ -135,10 +133,9 @@ local readpackage = unpack_f(unpack_package) local function send_package(fd, pack) local size = #pack - local package = string.format("%c%c%s", - bit32.extract(size,8,8), - bit32.extract(size,0,8), - pack) + local package = string.char(bit32.extract(size,8,8)).. + string.char(bit32.extract(size,0,8)).. + pack socket.send(fd, package) end diff --git a/examples/simpleweb.lua b/examples/simpleweb.lua index 6e6e6438..19870aba 100644 --- a/examples/simpleweb.lua +++ b/examples/simpleweb.lua @@ -3,6 +3,8 @@ local socket = require "socket" local httpd = require "http.httpd" local sockethelper = require "http.sockethelper" local urllib = require "http.url" +local table = table +local string = string local mode = ...